diff --git a/.pubnub.yml b/.pubnub.yml index 8ea1cf5b3..6334b6b6b 100644 --- a/.pubnub.yml +++ b/.pubnub.yml @@ -1,8 +1,15 @@ name: c-sharp -version: "7.3.11" +version: "7.3.12" schema: 1 scm: github.com/pubnub/c-sharp changelog: + - date: 2025-05-28 + version: v7.3.12 + changes: + - type: feature + text: "Added new integration tests to better cover all SDK endpoints." + - type: bug + text: "Fixed an issue when upon receiving a message with published with a custom type the CustomMessageType would be null upon receiving." - date: 2025-05-20 version: v7.3.11 changes: @@ -902,7 +909,7 @@ features: - QUERY-PARAM supported-platforms: - - version: Pubnub 'C#' 7.3.11 + version: Pubnub 'C#' 7.3.12 platforms: - Windows 10 and up - Windows Server 2008 and up @@ -913,7 +920,7 @@ supported-platforms: - .Net Framework 4.6.1+ - .Net Framework 6.0 - - version: PubnubPCL 'C#' 7.3.11 + version: PubnubPCL 'C#' 7.3.12 platforms: - Xamarin.Android - Xamarin.iOS @@ -933,7 +940,7 @@ supported-platforms: - .Net Core - .Net 6.0 - - version: PubnubUWP 'C#' 7.3.11 + version: PubnubUWP 'C#' 7.3.12 platforms: - Windows Phone 10 - Universal Windows Apps @@ -957,7 +964,7 @@ sdks: distribution-type: source distribution-repository: GitHub package-name: Pubnub - location: https://github.com/pubnub/c-sharp/releases/tag/v7.3.11.0 + location: https://github.com/pubnub/c-sharp/releases/tag/v7.3.12.0 requires: - name: ".Net" @@ -1240,7 +1247,7 @@ sdks: distribution-type: source distribution-repository: GitHub package-name: PubNubPCL - location: https://github.com/pubnub/c-sharp/releases/tag/v7.3.11.0 + location: https://github.com/pubnub/c-sharp/releases/tag/v7.3.12.0 requires: - name: ".Net Core" @@ -1599,7 +1606,7 @@ sdks: distribution-type: source distribution-repository: GitHub package-name: PubnubUWP - location: https://github.com/pubnub/c-sharp/releases/tag/v7.3.11.0 + location: https://github.com/pubnub/c-sharp/releases/tag/v7.3.12.0 requires: - name: "Universal Windows Platform Development" diff --git a/CHANGELOG b/CHANGELOG index 9a3e39a6c..8d2c934b1 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,9 @@ +v7.3.12 - May 28 2025 +----------------------------- +- Added: added new integration tests to better cover all SDK endpoints. + +- Fixed: fixed an issue when upon receiving a message with published with a custom type the CustomMessageType would be null upon receiving. + v7.3.11 - May 20 2025 ----------------------------- - Fixed: fix missing `heartbeat` and `leave` REST API calls when the event engine is enabled and `presenceTimeout` or `presenceHeartbeatInterval` not set. diff --git a/src/Api/PubnubApi/EventEngine/Common/EventEmitter.cs b/src/Api/PubnubApi/EventEngine/Common/EventEmitter.cs index 54371807e..519f5c807 100644 --- a/src/Api/PubnubApi/EventEngine/Common/EventEmitter.cs +++ b/src/Api/PubnubApi/EventEngine/Common/EventEmitter.cs @@ -132,11 +132,9 @@ public void EmitEvent(object e) { payload = eventData?.Payload; } - - List payloadContainer = new List(); //First item always message + if (currentMessageChannel.Contains("-pnpres") || currentMessageChannel.Contains(".*-pnpres")) { - payloadContainer.Add(payload); jsonFields.Add("payload", payload); } else if (eventData.MessageType == 2) //Objects Simplification events @@ -150,7 +148,6 @@ public void EmitEvent(object e) { if (objectsVersion.CompareTo(2D) == 0) //Process only version=2 for Objects Simplification. Ignore 1. { - payloadContainer.Add(payload); jsonFields.Add("payload", payload); } } @@ -172,7 +169,6 @@ public void EmitEvent(object e) } object decodeMessage = jsonLibrary.DeserializeToObject((decryptMessage == "**DECRYPT ERROR**") ? jsonLibrary.SerializeToJsonString(payload) : decryptMessage); - payloadContainer.Add(decodeMessage); jsonFields.Add("payload", decodeMessage); } else @@ -181,33 +177,27 @@ public void EmitEvent(object e) object payloadJObject = jsonLibrary.BuildJsonObject(payloadJson); if (payloadJObject == null) { - payloadContainer.Add(payload); jsonFields.Add("payload", payload); } else { - payloadContainer.Add(payloadJObject); jsonFields.Add("payload", payloadJObject); } } } var userMetaData = eventData.UserMetadata; - payloadContainer.Add(userMetaData); //Second one always user meta data jsonFields.Add("userMetadata", userMetaData); - payloadContainer.Add(GetTimetokenMetadata(eventData.PublishMetadata).Timetoken); //Third one always Timetoken - 2 jsonFields.Add("publishTimetoken", GetTimetokenMetadata(eventData.PublishMetadata).Timetoken); - payloadContainer.Add(eventData.IssuingClientId); //Fourth one always Publisher - jsonFields.Add("userId", eventData.IssuingClientId); // - 3 + jsonFields.Add("userId", eventData.IssuingClientId); - payloadContainer.Add(currentMessageChannelGroup); - payloadContainer.Add(currentMessageChannel); + jsonFields.Add("currentMessageChannelGroup", currentMessageChannelGroup); + jsonFields.Add("currentMessageChannel", currentMessageChannel); switch (eventData.MessageType) { case 1: { - payloadContainer.Add(eventData.CustomMessageType); jsonFields.Add("customMessageType", eventData.CustomMessageType); ResponseBuilder responseBuilder = new ResponseBuilder(configuration, jsonLibrary); PNMessageResult pnMessageResult = responseBuilder.GetEventResultObject>(jsonFields); @@ -308,7 +298,7 @@ public void EmitEvent(object e) } case 4: { - payloadContainer.Add(eventData.CustomMessageType); + jsonFields.Add("customMessageType", eventData.CustomMessageType); ResponseBuilder responseBuilder =new ResponseBuilder(configuration, jsonLibrary); PNMessageResult filesEvent = responseBuilder.GetEventResultObject>(jsonFields); if (filesEvent != null) @@ -404,7 +394,7 @@ public void EmitEvent(object e) } else { - payloadContainer.Add(eventData.CustomMessageType); + jsonFields.Add("customMessageType", eventData.CustomMessageType); ResponseBuilder responseBuilder =new ResponseBuilder(configuration, jsonLibrary); PNMessageResult userMessage = responseBuilder.GetEventResultObject>(jsonFields); try diff --git a/src/Api/PubnubApi/Properties/AssemblyInfo.cs b/src/Api/PubnubApi/Properties/AssemblyInfo.cs index 22f559815..162488915 100644 --- a/src/Api/PubnubApi/Properties/AssemblyInfo.cs +++ b/src/Api/PubnubApi/Properties/AssemblyInfo.cs @@ -11,8 +11,8 @@ [assembly: AssemblyProduct("Pubnub C# SDK")] [assembly: AssemblyCopyright("Copyright © 2021")] [assembly: AssemblyTrademark("")] -[assembly: AssemblyVersion("7.3.11.0")] -[assembly: AssemblyFileVersion("7.3.11.0")] +[assembly: AssemblyVersion("7.3.12.0")] +[assembly: AssemblyFileVersion("7.3.12.0")] // Setting ComVisible to false makes the types in this assembly not visible // to COM components. If you need to access a type in this assembly from // COM, set the ComVisible attribute to true on that type. diff --git a/src/Api/PubnubApi/PubnubApi.csproj b/src/Api/PubnubApi/PubnubApi.csproj index 64b00cfe4..884897f11 100644 --- a/src/Api/PubnubApi/PubnubApi.csproj +++ b/src/Api/PubnubApi/PubnubApi.csproj @@ -14,7 +14,7 @@ Pubnub - 7.3.11.0 + 7.3.12.0 PubNub C# .NET - Web Data Push API Pandu Masabathula PubNub @@ -22,7 +22,8 @@ http://pubnub.s3.amazonaws.com/2011/powered-by-pubnub/pubnub-icon-600x600.png true https://github.com/pubnub/c-sharp/ - Fix missing `heartbeat` and `leave` REST API calls when the event engine is enabled and `presenceTimeout` or `presenceHeartbeatInterval` not set. + Fixed an issue when upon receiving a message with published with a custom type the CustomMessageType would be null upon receiving. +Added new integration tests to better cover all SDK endpoints. Web Data Push Real-time Notifications ESB Message Broadcasting Distributed Computing PubNub is a Massively Scalable Web Push Service for Web and Mobile Games. This is a cloud-based service for broadcasting messages to thousands of web and mobile clients simultaneously diff --git a/src/Api/PubnubApiPCL/PubnubApiPCL.csproj b/src/Api/PubnubApiPCL/PubnubApiPCL.csproj index ea6d0d349..5feb5d07d 100644 --- a/src/Api/PubnubApiPCL/PubnubApiPCL.csproj +++ b/src/Api/PubnubApiPCL/PubnubApiPCL.csproj @@ -14,7 +14,7 @@ PubnubPCL - 7.3.11.0 + 7.3.12.0 PubNub C# .NET - Web Data Push API Pandu Masabathula PubNub @@ -22,7 +22,8 @@ http://pubnub.s3.amazonaws.com/2011/powered-by-pubnub/pubnub-icon-600x600.png true https://github.com/pubnub/c-sharp/ - Fix missing `heartbeat` and `leave` REST API calls when the event engine is enabled and `presenceTimeout` or `presenceHeartbeatInterval` not set. + Fixed an issue when upon receiving a message with published with a custom type the CustomMessageType would be null upon receiving. +Added new integration tests to better cover all SDK endpoints. Web Data Push Real-time Notifications ESB Message Broadcasting Distributed Computing PubNub is a Massively Scalable Web Push Service for Web and Mobile Games. This is a cloud-based service for broadcasting messages to thousands of web and mobile clients simultaneously diff --git a/src/Api/PubnubApiUWP/PubnubApiUWP.csproj b/src/Api/PubnubApiUWP/PubnubApiUWP.csproj index 5a055ac62..ad7a03bb6 100644 --- a/src/Api/PubnubApiUWP/PubnubApiUWP.csproj +++ b/src/Api/PubnubApiUWP/PubnubApiUWP.csproj @@ -16,7 +16,7 @@ PubnubUWP - 7.3.11.0 + 7.3.12.0 PubNub C# .NET - Web Data Push API Pandu Masabathula PubNub @@ -24,7 +24,8 @@ http://pubnub.s3.amazonaws.com/2011/powered-by-pubnub/pubnub-icon-600x600.png true https://github.com/pubnub/c-sharp/ - Fix missing `heartbeat` and `leave` REST API calls when the event engine is enabled and `presenceTimeout` or `presenceHeartbeatInterval` not set. + Fixed an issue when upon receiving a message with published with a custom type the CustomMessageType would be null upon receiving. +Added new integration tests to better cover all SDK endpoints. Web Data Push Real-time Notifications ESB Message Broadcasting Distributed Computing PubNub is a Massively Scalable Web Push Service for Web and Mobile Games. This is a cloud-based service for broadcasting messages to thousands of web and mobile clients simultaneously diff --git a/src/Api/PubnubApiUnity/PubnubApiUnity.csproj b/src/Api/PubnubApiUnity/PubnubApiUnity.csproj index 4f526191d..eacc34166 100644 --- a/src/Api/PubnubApiUnity/PubnubApiUnity.csproj +++ b/src/Api/PubnubApiUnity/PubnubApiUnity.csproj @@ -15,7 +15,7 @@ PubnubApiUnity - 7.3.11.0 + 7.3.12.0 PubNub C# .NET - Web Data Push API Pandu Masabathula PubNub diff --git a/src/UnitTests/PubnubApi.Tests/EncryptionTests.cs b/src/UnitTests/PubnubApi.Tests/EncryptionTests.cs index 539012f1e..4865daa2f 100644 --- a/src/UnitTests/PubnubApi.Tests/EncryptionTests.cs +++ b/src/UnitTests/PubnubApi.Tests/EncryptionTests.cs @@ -995,14 +995,14 @@ public void TestSubscribeDecryptionOnNonEncryptedMessage() pn.Subscribe().Channels(new[] { "test" }).Execute(); - Thread.Sleep(1000); + Thread.Sleep(2000); CreateTestSender().Publish() .Channel("test") .Message("test") .Execute(new PNPublishResultExt((r,s)=>{})); - bool passed = done.WaitOne(5000); + bool passed = done.WaitOne(6500); Assert.True(passed); } diff --git a/src/UnitTests/PubnubApi.Tests/PubnubApi.Tests.csproj b/src/UnitTests/PubnubApi.Tests/PubnubApi.Tests.csproj index 749bb3b73..7ba4a8e04 100644 --- a/src/UnitTests/PubnubApi.Tests/PubnubApi.Tests.csproj +++ b/src/UnitTests/PubnubApi.Tests/PubnubApi.Tests.csproj @@ -91,6 +91,12 @@ + + + PreserveNewest + + + diff --git a/src/UnitTests/PubnubApi.Tests/TestHarness.cs b/src/UnitTests/PubnubApi.Tests/TestHarness.cs index 8e0610928..992cd7fda 100644 --- a/src/UnitTests/PubnubApi.Tests/TestHarness.cs +++ b/src/UnitTests/PubnubApi.Tests/TestHarness.cs @@ -50,6 +50,8 @@ public static async Task GenerateTestGrantToken(Pubnub pubnub, string presenceTe string channel4 = "hello_my_channel_4"; string group = "hello_my_group"; string channelPattern = "foo.*"; + string channelGroupPattern = "foo.*"; + string uuidPattern = "fuu.*"; var fullAccess = new PNTokenAuthValues() { @@ -116,6 +118,14 @@ public static async Task GenerateTestGrantToken(Pubnub pubnub, string presenceTe { { channelPattern, fullAccess }, { channelPattern+"-pnpres", fullAccess } + }, + Uuids = new Dictionary() + { + {uuidPattern, fullAccess} + }, + ChannelGroups = new Dictionary() + { + {channelGroupPattern, fullAccess} } }) .ExecuteAsync(); diff --git a/src/UnitTests/PubnubApi.Tests/WhenAClientIsPresented.cs b/src/UnitTests/PubnubApi.Tests/WhenAClientIsPresented.cs index f44abddcd..94179f876 100644 --- a/src/UnitTests/PubnubApi.Tests/WhenAClientIsPresented.cs +++ b/src/UnitTests/PubnubApi.Tests/WhenAClientIsPresented.cs @@ -2482,5 +2482,288 @@ public static async Task ThenReceiveCustomObjectPresenceCallback() Assert.IsTrue(receivedPresenceMessage, "ThenReceiveCustomObjectPresenceCallback not received"); } + [Test] + public static async Task ThenWhereNowShouldReturnSubscribedChannel() + { + string subscribedChannel = "hello_my_channel"; + + PNConfiguration config = new PNConfiguration(new UserId("mytestuuid")) + { + PublishKey = PubnubCommon.PublishKey, + SubscribeKey = PubnubCommon.SubscribeKey, + Secure = false + }; + if (PubnubCommon.PAMServerSideRun) + { + config.SecretKey = PubnubCommon.SecretKey; + } + + pubnub = createPubNubInstance(config, authToken); + + pubnub.Subscribe().Channels(new[] { subscribedChannel }).WithPresence().Execute(); + await Task.Delay(3000); + + ManualResetEvent whereNowManualEvent = new ManualResetEvent(false); + PNWhereNowResult whereNowResult = null; + + pubnub.WhereNow().Uuid(config.UserId).Execute(new PNWhereNowResultExt( + (r, s) => { + Debug.WriteLine(pubnub.JsonPluggableLibrary.SerializeToJsonString(r)); + whereNowResult = r; + whereNowManualEvent.Set(); + })); + whereNowManualEvent.WaitOne(manualResetEventWaitTimeout); + + Assert.IsNotNull(whereNowResult, "WhereNow result should not be null"); + Assert.IsNotNull(whereNowResult.Channels, "Channels list should not be null"); + Assert.Contains(subscribedChannel, whereNowResult.Channels, "Subscribed channel should be in the channels list"); + + pubnub.Destroy(); + pubnub.PubnubUnitTest = null; + pubnub = null; + } + + [Test] + public static async Task ThenSetPresenceStateShouldWorkCorrectly() + { + server.ClearRequests(); + + if (PubnubCommon.EnableStubTest) + { + Assert.Ignore("Ignored ThenSetPresenceStateShouldWorkCorrectly"); + return; + } + + string channel = "hello_my_channel"; + string channelGroup = "hello_my_group"; + string customUuid = "mytestuuid"; + + PNConfiguration config = new PNConfiguration(new UserId(customUuid)) + { + PublishKey = PubnubCommon.PublishKey, + SubscribeKey = PubnubCommon.SubscribeKey, + Secure = false + }; + if (PubnubCommon.PAMServerSideRun) + { + config.SecretKey = PubnubCommon.SecretKey; + } + + pubnub = createPubNubInstance(config); + if (!string.IsNullOrEmpty(authToken)) + { + pubnub.SetAuthToken(authToken); + } + + // Test 1: Set state for channel + Dictionary channelState = new Dictionary + { + { "status", "online" }, + { "lastSeen", DateTime.UtcNow.ToString() } + }; + + PNResult setChannelStateResult = await pubnub.SetPresenceState() + .Channels(new[] { channel }) + .State(channelState) + .ExecuteAsync(); + + Assert.IsNotNull(setChannelStateResult.Result, "Set channel state result should not be null"); + Assert.AreEqual(200, setChannelStateResult.Status.StatusCode, "Set channel state status code should be 200"); + Assert.IsFalse(setChannelStateResult.Status.Error, "Set channel state should not have errors"); + Assert.IsNotNull(setChannelStateResult.Result.State, "State should not be null"); + Assert.IsTrue(setChannelStateResult.Result.State.ContainsKey("status"), "State should contain status"); + Assert.AreEqual("online", setChannelStateResult.Result.State["status"].ToString(), "Status should be online"); + + // Test 2: Set state for channel group + Dictionary channelGroupState = new Dictionary + { + { "type", "group" }, + { "members", 5 } + }; + + PNResult setChannelGroupStateResult = await pubnub.SetPresenceState() + .ChannelGroups(new[] { channelGroup }) + .State(channelGroupState) + .ExecuteAsync(); + + Assert.IsNotNull(setChannelGroupStateResult.Result, "Set channel group state result should not be null"); + Assert.AreEqual(200, setChannelGroupStateResult.Status.StatusCode, "Set channel group state status code should be 200"); + Assert.IsFalse(setChannelGroupStateResult.Status.Error, "Set channel group state should not have errors"); + Assert.IsNotNull(setChannelGroupStateResult.Result.State, "State should not be null"); + Assert.IsTrue(setChannelGroupStateResult.Result.State.ContainsKey("type"), "State should contain type"); + Assert.AreEqual("group", setChannelGroupStateResult.Result.State["type"].ToString(), "Type should be group"); + + // Test 3: Set state for both channel and channel group + Dictionary combinedState = new Dictionary + { + { "combined", true }, + { "timestamp", DateTime.UtcNow.ToString() } + }; + + PNResult setCombinedStateResult = await pubnub.SetPresenceState() + .Channels(new[] { channel }) + .ChannelGroups(new[] { channelGroup }) + .State(combinedState) + .ExecuteAsync(); + + Assert.IsNotNull(setCombinedStateResult.Result, "Set combined state result should not be null"); + Assert.AreEqual(200, setCombinedStateResult.Status.StatusCode, "Set combined state status code should be 200"); + Assert.IsFalse(setCombinedStateResult.Status.Error, "Set combined state should not have errors"); + Assert.IsNotNull(setCombinedStateResult.Result.State, "State should not be null"); + Assert.IsTrue(setCombinedStateResult.Result.State.ContainsKey("combined"), "State should contain combined flag"); + Assert.IsTrue((bool)setCombinedStateResult.Result.State["combined"], "Combined flag should be true"); + + // Test 4: Set state with custom UUID + Dictionary customState = new Dictionary + { + { "customUuid", true }, + { "timestamp", DateTime.UtcNow.ToString() } + }; + + PNResult setCustomUuidStateResult = await pubnub.SetPresenceState() + .Channels(new[] { channel }) + .Uuid(customUuid) + .State(customState) + .ExecuteAsync(); + + Assert.IsNotNull(setCustomUuidStateResult.Result, "Set custom UUID state result should not be null"); + Assert.AreEqual(200, setCustomUuidStateResult.Status.StatusCode, "Set custom UUID state status code should be 200"); + Assert.IsFalse(setCustomUuidStateResult.Status.Error, "Set custom UUID state should not have errors"); + Assert.IsNotNull(setCustomUuidStateResult.Result.State, "State should not be null"); + Assert.IsTrue(setCustomUuidStateResult.Result.State.ContainsKey("customUuid"), "State should contain customUuid flag"); + Assert.IsTrue((bool)setCustomUuidStateResult.Result.State["customUuid"], "Custom UUID flag should be true"); + + // Test 5: Set state with query parameters + Dictionary queryParams = new Dictionary + { + { "custom_param", "value" } + }; + + Dictionary queryState = new Dictionary + { + { "hasQueryParams", true } + }; + + PNResult setQueryStateResult = await pubnub.SetPresenceState() + .Channels(new[] { channel }) + .State(queryState) + .QueryParam(queryParams) + .ExecuteAsync(); + + Assert.IsNotNull(setQueryStateResult.Result, "Set query state result should not be null"); + Assert.AreEqual(200, setQueryStateResult.Status.StatusCode, "Set query state status code should be 200"); + Assert.IsFalse(setQueryStateResult.Status.Error, "Set query state should not have errors"); + Assert.IsNotNull(setQueryStateResult.Result.State, "State should not be null"); + Assert.IsTrue(setQueryStateResult.Result.State.ContainsKey("hasQueryParams"), "State should contain hasQueryParams flag"); + Assert.IsTrue((bool)setQueryStateResult.Result.State["hasQueryParams"], "Has query params flag should be true"); + + // Test 6: Verify state persistence with GetPresenceState + PNResult getStateResult = await pubnub.GetPresenceState() + .Channels(new[] { channel }) + .ExecuteAsync(); + + Assert.IsNotNull(getStateResult.Result, "Get state result should not be null"); + Assert.AreEqual(200, getStateResult.Status.StatusCode, "Get state status code should be 200"); + Assert.IsFalse(getStateResult.Status.Error, "Get state should not have errors"); + Assert.IsNotNull(getStateResult.Result.StateByUUID, "State by UUID should not be null"); + Assert.IsTrue(getStateResult.Result.StateByUUID.ContainsKey("hasQueryParams"), "State should contain key \"hasQueryParams\""); + + // Cleanup + pubnub.Destroy(); + pubnub.PubnubUnitTest = null; + pubnub = null; + } + + [Test] + public static async Task ThenHereNowShouldReturnCorrectUserData() + { + string channel = $"foo.tc_{Guid.NewGuid()}"; + string customUuid = $"fuu.tu_{Guid.NewGuid()}"; + Dictionary userState = new Dictionary + { + { "status", "online" }, + { "lastSeen", DateTime.UtcNow.ToString() }, + { "customData", new Dictionary { { "key", "value" } } } + }; + + PNConfiguration config = new PNConfiguration(new UserId(customUuid)) + { + PublishKey = PubnubCommon.PublishKey, + SubscribeKey = PubnubCommon.SubscribeKey, + SecretKey = PubnubCommon.SecretKey, + Secure = false + }; + + pubnub = createPubNubInstance(config); + if (!string.IsNullOrEmpty(authToken)) + { + pubnub.SetAuthToken(authToken); + } + + // Subscribe to channel + pubnub.Subscribe().Channels(new[] { channel }).WithPresence().Execute(); + await Task.Delay(3000); // Wait for subscription to complete + + // Set user state + PNResult setStateResult = await pubnub.SetPresenceState() + .Uuid(customUuid) + .Channels(new[] { channel }) + .State(userState) + .ExecuteAsync(); + + Assert.IsNotNull(setStateResult.Result, "Set state result should not be null"); + Assert.IsFalse(setStateResult.Status.Error, "Set state should not have errors"); + Assert.AreEqual(200, setStateResult.Status.StatusCode, "Set state should return 200 status code"); + + // Get HereNow data + PNResult hereNowResult = await pubnub.HereNow() + .Channels(new[] { channel }) + .IncludeState(true) + .IncludeUUIDs(true) + .ExecuteAsync(); + + Assert.IsNotNull(hereNowResult.Result, "HereNow result should not be null"); + Assert.IsFalse(hereNowResult.Status.Error, "HereNow should not have errors"); + Assert.AreEqual(200, hereNowResult.Status.StatusCode, "HereNow should return 200 status code"); + + // Verify channel data + Assert.IsNotNull(hereNowResult.Result.Channels, "Channels should not be null"); + Assert.IsTrue(hereNowResult.Result.Channels.ContainsKey(channel), "Channel should be present in results"); + + var channelData = hereNowResult.Result.Channels[channel]; + Assert.IsNotNull(channelData, "Channel data should not be null"); + Assert.AreEqual(1, channelData.Occupancy, "Channel should have one occupant"); + Assert.IsNotNull(channelData.Occupants, "Occupants should not be null"); + Assert.AreEqual(1, channelData.Occupants.Count, "Should have one occupant"); + + // Verify user data + var occupant = channelData.Occupants[0]; + Assert.AreEqual(customUuid, occupant.Uuid, "UUID should match"); + Assert.IsNotNull(occupant.State, "State should not be null"); + + // Cast state to dictionary and verify its contents + var stateDict = occupant.State as Dictionary; + Assert.IsNotNull(stateDict, "State should be castable to Dictionary"); + Assert.IsTrue(stateDict.ContainsKey("status"), "State should contain status key"); + Assert.AreEqual("online", stateDict["status"].ToString(), "Status should match"); + Assert.IsTrue(stateDict.ContainsKey("lastSeen"), "State should contain lastSeen key"); + Assert.IsNotNull(stateDict["lastSeen"], "LastSeen should be present"); + Assert.IsTrue(stateDict.ContainsKey("customData"), "State should contain customData key"); + + // Verify nested customData dictionary + var customData = stateDict["customData"] as Dictionary; + Assert.IsNotNull(customData, "CustomData should be castable to Dictionary"); + Assert.IsTrue(customData.ContainsKey("key"), "CustomData should contain key"); + Assert.AreEqual("value", customData["key"].ToString(), "CustomData value should match"); + + // Cleanup + pubnub.Unsubscribe().Channels(new[] { channel }).Execute(); + await Task.Delay(1000); + + pubnub.Destroy(); + pubnub.PubnubUnitTest = null; + pubnub = null; + } + } } diff --git a/src/UnitTests/PubnubApi.Tests/WhenAMessageIsPublished.cs b/src/UnitTests/PubnubApi.Tests/WhenAMessageIsPublished.cs index e76c81214..ed4b826ec 100644 --- a/src/UnitTests/PubnubApi.Tests/WhenAMessageIsPublished.cs +++ b/src/UnitTests/PubnubApi.Tests/WhenAMessageIsPublished.cs @@ -2429,5 +2429,80 @@ public static void ThenNullCallbackShouldThrowException() pubnub.PubnubUnitTest = null; pubnub = null; } + + [Test] + public static void ThenPublishWithCustomMessageTypeAndSubscribeShouldReceiveCorrectMessageType() + { + string channel = "hello_my_channel"; + string message = "some_message_lalala"; + string customType = "customtype"; + + PNConfiguration config = new PNConfiguration(new UserId("mytestuuid")) + { + PublishKey = PubnubCommon.PublishKey, + SubscribeKey = PubnubCommon.SubscribeKey, + SecretKey = PubnubCommon.SecretKey, + Secure = false, + }; + if (PubnubCommon.PAMServerSideRun) + { + config.SecretKey = PubnubCommon.SecretKey; + } + else if (!string.IsNullOrEmpty(authToken) && !PubnubCommon.SuppressAuthKey) + { + config.AuthKey = authToken; + } + pubnub = createPubNubInstance(config, authToken); + + manualResetEventWaitTimeout = 310 * 1000; + + // Subscribe to the channel + ManualResetEvent subscribeManualEvent = new ManualResetEvent(false); + pubnub.Subscribe() + .Channels(new string[] { channel }) + .WithPresence() + .Execute(); + + // Add message listener + string receivedCustomMessageType = null; + pubnub.AddListener(new SubscribeCallbackExt( + (p, m) => { + if (m.Channel.Equals(channel) && m.Message.ToString().Equals(message)) + { + receivedCustomMessageType = m.CustomMessageType; + subscribeManualEvent.Set(); + } + }, + (p, pnPresenceEventResult) => { }, + (p, pnSignalResult) => { }, + (p, pnObjectEventResult) => { }, + (p, pnMessageActionEventResult) => { }, + (p, pnFileEventResult) => { }, + (p, pnStatus) => { } + )); + + // Publish the message + ManualResetEvent publishManualEvent = new ManualResetEvent(false); + pubnub.Publish().Channel(channel).Message(message).CustomMessageType(customType) + .Execute(new PNPublishResultExt((r, s) => + { + if (r != null && s.StatusCode == 200 && !s.Error) + { + publishManualEvent.Set(); + } + + })); + var receivedPublishMessage = publishManualEvent.WaitOne(manualResetEventWaitTimeout); + Assert.IsTrue(receivedPublishMessage, "Publish with CustomMessageType Failed"); + + // Wait for the subscribe message + var receivedSubscribeMessage = subscribeManualEvent.WaitOne(manualResetEventWaitTimeout); + Assert.IsTrue(receivedSubscribeMessage, "Subscribe message not received"); + Assert.AreEqual(customType, receivedCustomMessageType, "Custom message type mismatch"); + + pubnub.Destroy(); + pubnub.PubnubUnitTest = null; + pubnub = null; + } } } diff --git a/src/UnitTests/PubnubApi.Tests/WhenAMessageIsSignaled.cs b/src/UnitTests/PubnubApi.Tests/WhenAMessageIsSignaled.cs index c3edb12f4..eee68ad53 100644 --- a/src/UnitTests/PubnubApi.Tests/WhenAMessageIsSignaled.cs +++ b/src/UnitTests/PubnubApi.Tests/WhenAMessageIsSignaled.cs @@ -661,5 +661,55 @@ public static async Task ThenWithAsyncIgnoreCipherKeyUnencryptSignalListenerShou Assert.IsTrue(internalReceivedMessage, "WhenSubscribedToAChannel --> ThenWithAsyncIgnoreCipherKeyUnencryptSignalListenerShouldGetMessagae Failed"); } } + + [Test] + public static void ThenSignalWithCustomMessageTypeShouldReturnSuccessCodeAndInfo() + { + string channel = "hello_my_channel"; + string message = messageForUnencryptSignal; + string customType = "custom-type"; + + PNConfiguration config = new PNConfiguration(new UserId("mytestuuid")) + { + PublishKey = PubnubCommon.PublishKey, + SubscribeKey = PubnubCommon.SubscribeKey, + Secure = false + }; + if (PubnubCommon.PAMServerSideRun) + { + config.SecretKey = PubnubCommon.SecretKey; + } + else if (!string.IsNullOrEmpty(authKey) && !PubnubCommon.SuppressAuthKey) + { + config.AuthKey = authKey; + } + pubnub = createPubNubInstance(config, authToken); + + manualResetEventWaitTimeout = 310 * 1000; + + ManualResetEvent signalManualEvent = new ManualResetEvent(false); + PNPublishResult result = null; + PNStatus status = null; + + pubnub.Signal().Channel(channel).Message(message).CustomMessageType(customType) + .Execute(new PNPublishResultExt((r, s) => + { + result = r; + status = s; + signalManualEvent.Set(); + })); + var signalCompleted = signalManualEvent.WaitOne(manualResetEventWaitTimeout); + + Assert.True(signalCompleted, "Singal execution callback was not called"); + Assert.IsNotNull(result, "Result should not be null"); + Assert.IsNotNull(status, "Status should not be null"); + Assert.AreEqual(200, status.StatusCode, "StatusCode should be 200"); + Assert.IsFalse(status.Error, "Error should be false"); + Assert.IsTrue(result.Timetoken > 0, "Timetoken should be greater than 0"); + + pubnub.Destroy(); + pubnub.PubnubUnitTest = null; + pubnub = null; + } } } diff --git a/src/UnitTests/PubnubApi.Tests/WhenChannelGroupIsRequested.cs b/src/UnitTests/PubnubApi.Tests/WhenChannelGroupIsRequested.cs index 0bba6ef74..971187a8c 100644 --- a/src/UnitTests/PubnubApi.Tests/WhenChannelGroupIsRequested.cs +++ b/src/UnitTests/PubnubApi.Tests/WhenChannelGroupIsRequested.cs @@ -574,6 +574,120 @@ public static async Task ThenWithAsyncGetAllChannelGroupShouldReturnSuccess() } + [Test] + public static async Task ThenAddChannelsToChannelGroupAndListShouldReturnSuccess() + { + string testChannelGroup = $"foo.tg_{Guid.NewGuid()}"; + string[] testChannels = new[] { + $"foo.tc_1_{Guid.NewGuid()}", + $"foo.tc_2_{Guid.NewGuid()}" + }; + + PNConfiguration config = new PNConfiguration(new UserId("mytestuuid")) + { + PublishKey = PubnubCommon.PublishKey, + SubscribeKey = PubnubCommon.SubscribeKey, + SecretKey = PubnubCommon.SecretKey, + Secure = false + }; + if (PubnubCommon.PAMServerSideRun) + { + config.SecretKey = PubnubCommon.SecretKey; + } + + pubnub = createPubNubInstance(config); + pubnub.SetAuthToken(authToken); + + // Add channels to channel group + PNResult addResult = await pubnub.AddChannelsToChannelGroup() + .Channels(testChannels) + .ChannelGroup(testChannelGroup) + .ExecuteAsync(); + + Assert.IsNotNull(addResult, "Add result should not be null"); + Assert.IsNotNull(addResult.Result, "Add result data should not be null"); + Assert.IsFalse(addResult.Status.Error, "Add operation should not have errors"); + Assert.AreEqual(200, addResult.Status.StatusCode, "Add operation should return 200 status code"); + + // List channels in the channel group to verify + PNResult listResult = await pubnub.ListChannelsForChannelGroup() + .ChannelGroup(testChannelGroup) + .ExecuteAsync(); + + Assert.IsNotNull(listResult, "List result should not be null"); + Assert.IsNotNull(listResult.Result, "List result data should not be null"); + Assert.IsFalse(listResult.Status.Error, "List operation should not have errors"); + Assert.AreEqual(200, listResult.Status.StatusCode, "List operation should return 200 status code"); + Assert.AreEqual(testChannelGroup, listResult.Result.ChannelGroup, "Channel group name should match"); + Assert.IsNotNull(listResult.Result.Channels, "Channels list should not be null"); + Assert.AreEqual(testChannels.Length, listResult.Result.Channels.Count, "Should have correct number of channels"); + + // Verify all test channels are present in the result + foreach (string channel in testChannels) + { + Assert.IsTrue(listResult.Result.Channels.Contains(channel), $"Channel {channel} should be in the list"); + } + + pubnub.Destroy(); + pubnub.PubnubUnitTest = null; + pubnub = null; + } + + [Test] + public static async Task ThenAddExistingChannelToChannelGroupShouldReturnSuccess() + { + string testChannelGroup = $"foo.tg_{Guid.NewGuid()}"; + string testChannel = $"foo.tc_{Guid.NewGuid()}"; + + PNConfiguration config = new PNConfiguration(new UserId("mytestuuid")) + { + PublishKey = PubnubCommon.PublishKey, + SubscribeKey = PubnubCommon.SubscribeKey, + SecretKey = PubnubCommon.SecretKey, + Secure = false + }; + + pubnub = createPubNubInstance(config); + pubnub.SetAuthToken(authToken); + + // First add the channel to the group + PNResult initialAddResult = await pubnub.AddChannelsToChannelGroup() + .Channels(new[] { testChannel }) + .ChannelGroup(testChannelGroup) + .ExecuteAsync(); + + Assert.IsNotNull(initialAddResult, "Initial add result should not be null"); + Assert.IsFalse(initialAddResult.Status.Error, "Initial add operation should not have errors"); + Assert.AreEqual(200, initialAddResult.Status.StatusCode, "Initial add operation should return 200 status code"); + + // Try to add the same channel again + PNResult duplicateAddResult = await pubnub.AddChannelsToChannelGroup() + .Channels(new[] { testChannel }) + .ChannelGroup(testChannelGroup) + .ExecuteAsync(); + + Assert.IsNotNull(duplicateAddResult, "Duplicate add result should not be null"); + Assert.IsFalse(duplicateAddResult.Status.Error, "Duplicate add operation should not have errors"); + Assert.AreEqual(200, duplicateAddResult.Status.StatusCode, "Duplicate add operation should return 200 status code"); + + // Verify the channel group still has exactly one channel + PNResult listResult = await pubnub.ListChannelsForChannelGroup() + .ChannelGroup(testChannelGroup) + .ExecuteAsync(); + + Assert.IsNotNull(listResult, "List result should not be null"); + Assert.IsFalse(listResult.Status.Error, "List operation should not have errors"); + Assert.AreEqual(200, listResult.Status.StatusCode, "List operation should return 200 status code"); + Assert.AreEqual(testChannelGroup, listResult.Result.ChannelGroup, "Channel group name should match"); + Assert.IsNotNull(listResult.Result.Channels, "Channels list should not be null"); + Assert.AreEqual(1, listResult.Result.Channels.Count, "Should have exactly one channel"); + Assert.AreEqual(testChannel, listResult.Result.Channels[0], "Channel should match the added channel"); + + pubnub.Destroy(); + pubnub.PubnubUnitTest = null; + pubnub = null; + } + private class GrantResult : PNCallback { public override void OnResponse(PNAccessManagerGrantResult result, PNStatus status) diff --git a/src/UnitTests/PubnubApi.Tests/WhenFileIsRequested.cs b/src/UnitTests/PubnubApi.Tests/WhenFileIsRequested.cs index b91a8cd98..c48b1ce6d 100644 --- a/src/UnitTests/PubnubApi.Tests/WhenFileIsRequested.cs +++ b/src/UnitTests/PubnubApi.Tests/WhenFileIsRequested.cs @@ -549,5 +549,93 @@ public static async Task ThenWithAsyncDeleteFileShouldReturnSuccess() pubnub = null; Assert.IsTrue(receivedMessage, "WhenFileIsRequested -> ThenWithAsyncDeleteFileShouldReturnSuccess failed."); } + + [Test] + public static async Task ThenUploadingLargeFileShouldReturnError() + { + server.ClearRequests(); + + if (PubnubCommon.EnableStubTest) + { + Assert.Ignore("Ignored ThenUploadingLargeFileShouldReturnError"); + return; + } + + PNConfiguration config = new PNConfiguration(new UserId("mytestuuid_file_tests")) + { + PublishKey = PubnubCommon.PublishKey, + SubscribeKey = PubnubCommon.SubscribeKey, + Secure = false + }; + if (PubnubCommon.PAMServerSideRun) + { + config.SecretKey = PubnubCommon.SecretKey; + } + + pubnub = createPubNubInstance(config); + pubnub.SetAuthToken(token); + + string targetFileUpload = @"file_large.png"; + PNResult sendFileResult = await pubnub.SendFile() + .Channel(channelName) + .File(targetFileUpload) + .Message("This is my large file") + .ExecuteAsync(); + + Assert.IsNotNull(sendFileResult, "Send file result should not be null"); + Assert.IsNotNull(sendFileResult.Status, "Status should not be null"); + Assert.IsTrue(sendFileResult.Status.Error, "Should have error status"); + Assert.IsNull(sendFileResult.Result, "Result should be null for large file"); + Assert.IsTrue(sendFileResult.Status.ErrorData.Information.Contains("File upload failed: Your proposed upload exceeds the maximum allowed size"), "Error message should be about file size"); + + pubnub.Destroy(); + pubnub.PubnubUnitTest = null; + pubnub = null; + } + + //TODO: is the message in content the correct behaviour? + [Test] + public static async Task ThenDownloadingNonExistentFileShouldReturnError() + { + server.ClearRequests(); + + if (PubnubCommon.EnableStubTest) + { + Assert.Ignore("Ignored ThenDownloadingNonExistentFileShouldReturnError"); + return; + } + + PNConfiguration config = new PNConfiguration(new UserId("mytestuuid_file_tests")) + { + PublishKey = PubnubCommon.PublishKey, + SubscribeKey = PubnubCommon.SubscribeKey, + SecretKey = PubnubCommon.SecretKey, + Secure = false + }; + + pubnub = createPubNubInstance(config); + pubnub.SetAuthToken(token); + + string nonExistentFileId = "non-existent-file-id"; + string nonExistentFileName = "non-existent-file.txt"; + + PNResult downloadResult = await pubnub.DownloadFile() + .Channel(channelName) + .FileId(nonExistentFileId) + .FileName(nonExistentFileName) + .ExecuteAsync(); + + //Assert.IsNotNull(downloadResult, "Download result should not be null"); + Assert.IsNotNull(downloadResult.Status, "Status should not be null"); + Assert.AreEqual(downloadResult.Status.StatusCode, 404, "Download status code for nonexistent file should be 404"); + /*Assert.IsTrue(downloadResult.Status.Error, "Should have error status"); + Assert.IsNull(downloadResult.Result, "Result should be null for non-existent file"); + Assert.IsNotNull(downloadResult.Status.ErrorData, "Error data should not be null"); + Assert.IsTrue(downloadResult.Status.ErrorData.Information.Contains("The specified key does not exist."), "Error message should indicate file not found");*/ + + pubnub.Destroy(); + pubnub.PubnubUnitTest = null; + pubnub = null; + } } } \ No newline at end of file diff --git a/src/UnitTests/PubnubApi.Tests/WhenGrantIsRequested.cs b/src/UnitTests/PubnubApi.Tests/WhenGrantIsRequested.cs index f9d245b69..b3f4208f1 100644 --- a/src/UnitTests/PubnubApi.Tests/WhenGrantIsRequested.cs +++ b/src/UnitTests/PubnubApi.Tests/WhenGrantIsRequested.cs @@ -925,7 +925,7 @@ public static void ThenGrantTokenShouldReturnSuccess() receivedGrantMessage = false; - PNConfiguration config = new PNConfiguration(new UserId("mytestuuid")) + var config = new PNConfiguration(new UserId("mytestuuid")) { PublishKey = PubnubCommon.PublishKey, SubscribeKey = PubnubCommon.SubscribeKey, @@ -934,44 +934,42 @@ public static void ThenGrantTokenShouldReturnSuccess() }; pubnub = createPubNubInstance(config); - server.RunOnHttps(config.Secure); try { grantManualEvent = new ManualResetEvent(false); + var grantedResources = new PNTokenResources() + { + Spaces=new Dictionary() { + { "spc1", new PNTokenAuthValues() { Read = true, Write = true, Manage= true, Create = true, Delete=true, Get = true, Update = true, Join = true } } }, + Users = new Dictionary() { + { "usr1", new PNTokenAuthValues() { Read = true, Write = true, Manage= true, Create = true, Delete=true, Get = true, Update = true, Join = true } } }, + }; pubnub.GrantToken() - .Resources(new PNTokenResources() - { - Spaces=new Dictionary() { - { "spc1", new PNTokenAuthValues() { Read = true, Write = true, Manage= true, Create = true, Delete=true, Get = true, Update = true, Join = true } } }, - Users = new Dictionary() { - { "usr1", new PNTokenAuthValues() { Read = true, Write = true, Manage= true, Create = true, Delete=true, Get = true, Update = true, Join = true } } }, - } - ) + .Resources(grantedResources) .TTL(10) .Execute(new PNAccessManagerTokenResultExt((result, status) => { - if (result != null) - { - Console.WriteLine(pubnub.JsonPluggableLibrary.SerializeToJsonString(result)); - } - else - { - Console.WriteLine(pubnub.JsonPluggableLibrary.SerializeToJsonString(status)); - } - receivedGrantMessage = true; + Assert.IsNotNull(result, "Grant result was null"); + Assert.True(status.StatusCode == 200, $"Grant status was {status.StatusCode}, not 200." + + $"\n Status: {pubnub.JsonPluggableLibrary.SerializeToJsonString(status)}"); + + var parsedToken = pubnub.ParseToken(result.Token); + Assert.True(parsedToken.TTL == 10, "Wrong TTL in parsed token."); + Assert.IsNotNull(parsedToken.Resources, "parsedToken.Resources was null"); + Assert.True(parsedToken.Resources.Spaces.ContainsKey("spc1"), "parsedToken.Resources.Spaces did not contain expected key"); + Assert.True(parsedToken.Resources.Users.ContainsKey("usr1"), "parsedToken.Resources.Users did not contain expected key"); + grantManualEvent.Set(); })); - grantManualEvent.WaitOne(); - Thread.Sleep(1000); } catch (Exception ex) { - Console.WriteLine(ex.ToString()); - receivedGrantMessage = true; - grantManualEvent.Set(); + Assert.Fail($"Exception when trying to grant token: {ex}"); } - + + receivedGrantMessage = grantManualEvent.WaitOne(7000); + Assert.True(receivedGrantMessage, "Did not receive grant callback"); } @@ -1024,16 +1022,13 @@ public static async Task ThenWithAsyncGrantTokenShouldReturnSuccess() .TTL(10) .ExecuteAsync(); #endif - if (grantResponse.Result != null && !grantResponse.Status.Error) - { - receivedGrantMessage = true; - } - Assert.IsTrue(receivedGrantMessage, "WhenGrantIsRequested -> ThenWithAsyncGrantTokenShouldReturnSuccess failed."); - + Assert.IsNotNull(grantResponse, "grantResponse should not be null"); + Assert.IsNotNull(grantResponse.Result, $"grantResponse.Result should not be null, error data: {grantResponse.Status?.ErrorData?.Information}"); + Assert.IsNotNull(grantResponse.Status, "grantResponse.Status should not be null"); + Assert.IsFalse(grantResponse.Status.Error, "grantResponse.Status.Error should be false"); } - - //TODO: CLEN-2039 - //[Test] + + [Test] public static void ThenRevokeTokenShouldReturnSuccess() { server.ClearRequests(); @@ -1051,11 +1046,16 @@ public static void ThenRevokeTokenShouldReturnSuccess() }; pubnub = createPubNubInstance(config); - server.RunOnHttps(config.Secure); try { - PNResult grantResult = pubnub.GrantToken().TTL(5).Resources(new PNTokenResources() { Spaces = new Dictionary() { { "spc1", new PNTokenAuthValues() { Read = true } } } }).ExecuteAsync().Result; + PNResult grantResult = pubnub + .GrantToken() + .TTL(5) + .Resources(new PNTokenResources() + { Spaces = new Dictionary() { { "spc1", new PNTokenAuthValues() { Read = true } } } }) + .ExecuteAsync() + .Result; if (grantResult.Result != null && !string.IsNullOrEmpty(grantResult.Result.Token)) { revokeManualEvent = new ManualResetEvent(false); @@ -1063,29 +1063,20 @@ public static void ThenRevokeTokenShouldReturnSuccess() .Token(grantResult.Result.Token) .Execute(new PNAccessManagerRevokeTokenResultExt((result, status) => { - if (result != null) - { - Console.WriteLine(pubnub.JsonPluggableLibrary.SerializeToJsonString(result)); - } - else - { - Console.WriteLine(pubnub.JsonPluggableLibrary.SerializeToJsonString(status)); - } - receivedRevokeMessage = true; + Assert.IsNotNull(result, "Revoke result was null"); + Assert.True(status.StatusCode == 200, $"Revoke status was {status.StatusCode}, not 200." + + $"\n Status: {pubnub.JsonPluggableLibrary.SerializeToJsonString(status)}"); revokeManualEvent.Set(); })); - revokeManualEvent.WaitOne(); - Thread.Sleep(1000); - } } catch (Exception ex) { - Console.WriteLine(ex.ToString()); - receivedRevokeMessage = true; - revokeManualEvent.Set(); + Assert.Fail($"Exception when trying to grant token: {ex}"); } + receivedRevokeMessage = grantManualEvent.WaitOne(7000); + Assert.True(receivedRevokeMessage, "Did not receive grant callback"); } [Test] diff --git a/src/UnitTests/PubnubApi.Tests/WhenMessageAction.cs b/src/UnitTests/PubnubApi.Tests/WhenMessageAction.cs index 847d11145..40f457000 100644 --- a/src/UnitTests/PubnubApi.Tests/WhenMessageAction.cs +++ b/src/UnitTests/PubnubApi.Tests/WhenMessageAction.cs @@ -4,6 +4,7 @@ using PubnubApi; using MockServer; using System.Threading.Tasks; +using System.Collections.Generic; namespace PubNubMessaging.Tests { @@ -875,5 +876,115 @@ await pubnub.RemoveMessageAction() Assert.IsTrue(receivedAddEvent && receivedRemoveEvent, "Message Action events Failed"); } + [Test] + public static async Task ThenAddMessageActionWithCustomDataAndVerifyShouldReturnSuccess() + { + string channel = $"foo.tc_{Guid.NewGuid()}"; + long messageTimetoken = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); + string customType = "custom_reaction"; + string customValue = "thumbs_up"; + + PNConfiguration config = new PNConfiguration(new UserId("mytestuuid")) + { + PublishKey = PubnubCommon.PublishKey, + SubscribeKey = PubnubCommon.SubscribeKey, + SecretKey = PubnubCommon.SecretKey, + Secure = false + }; + + pubnub = createPubNubInstance(config); + pubnub.SetAuthToken(authToken); + + // First publish a message to get a valid message timetoken + PNResult publishResult = await pubnub.Publish() + .Channel(channel) + .Message("Test message for action") + .ExecuteAsync(); + + Assert.IsNotNull(publishResult, "Publish result should not be null"); + Assert.IsFalse(publishResult.Status.Error, "Publish operation should not have errors"); + Assert.AreEqual(200, publishResult.Status.StatusCode, "Publish operation should return 200 status code"); + Assert.IsNotNull(publishResult.Result, "Publish result data should not be null"); + Assert.IsTrue(publishResult.Result.Timetoken > 0, "Publish result should have a valid timetoken"); + + // Use the actual message timetoken from the publish result + messageTimetoken = publishResult.Result.Timetoken; + + // Add message action + PNMessageAction messageAction = new PNMessageAction + { + Type = customType, + Value = customValue + }; + + PNResult addResult = await pubnub.AddMessageAction() + .Channel(channel) + .MessageTimetoken(messageTimetoken) + .Action(messageAction) + .ExecuteAsync(); + + Assert.IsNotNull(addResult, "Add result should not be null"); + Assert.IsFalse(addResult.Status.Error, "Add operation should not have errors"); + Assert.AreEqual(200, addResult.Status.StatusCode, "Add operation should return 200 status code"); + Assert.IsNotNull(addResult.Result, "Add result data should not be null"); + Assert.AreEqual(messageTimetoken, addResult.Result.MessageTimetoken, "Message timetoken should match"); + Assert.IsTrue(addResult.Result.ActionTimetoken > 0, "Action timetoken should be valid"); + Assert.IsFalse(string.IsNullOrEmpty(addResult.Result.Uuid), "UUID should not be empty"); + + // Verify the message action through GetMessageActions + PNResult getResult = await pubnub.GetMessageActions() + .Channel(channel) + .ExecuteAsync(); + + Assert.IsNotNull(getResult, "Get result should not be null"); + Assert.IsFalse(getResult.Status.Error, "Get operation should not have errors"); + Assert.AreEqual(200, getResult.Status.StatusCode, "Get operation should return 200 status code"); + Assert.IsNotNull(getResult.Result, "Get result data should not be null"); + Assert.IsNotNull(getResult.Result.MessageActions, "Message actions list should not be null"); + Assert.IsTrue(getResult.Result.MessageActions.Count > 0, "Should have at least one message action"); + + // Find our specific message action + PNMessageActionItem foundAction = getResult.Result.MessageActions.Find( + x => x.MessageTimetoken == messageTimetoken && + x.Action.Type == customType && + x.Action.Value == customValue); + + Assert.IsNotNull(foundAction, "Should find the added message action"); + Assert.AreEqual(messageTimetoken, foundAction.MessageTimetoken, "Message timetoken should match"); + Assert.AreEqual(customType, foundAction.Action.Type, "Action type should match"); + Assert.AreEqual(customValue, foundAction.Action.Value, "Action value should match"); + Assert.IsTrue(foundAction.ActionTimetoken > 0, "Action timetoken should be valid"); + Assert.IsFalse(string.IsNullOrEmpty(foundAction.Uuid), "UUID should not be empty"); + + // Test edge case: Try to add the same action again + PNResult duplicateAddResult = await pubnub.AddMessageAction() + .Channel(channel) + .MessageTimetoken(messageTimetoken) + .Action(messageAction) + .ExecuteAsync(); + + Assert.IsNotNull(duplicateAddResult, "Duplicate add result should not be null"); + Assert.IsTrue(duplicateAddResult.Status.Error, "Duplicate add operation should have an error"); + Assert.IsNull(duplicateAddResult.Result, "Duplicate add result data should be null"); + Assert.AreEqual(409, duplicateAddResult.Status.StatusCode, "Duplicate add operation should return 409 status code"); + + // Verify we still have the same number of actions + PNResult verifyResult = await pubnub.GetMessageActions() + .Channel(channel) + .ExecuteAsync(); + + Assert.IsNotNull(verifyResult, "Verify result should not be null"); + Assert.IsFalse(verifyResult.Status.Error, "Verify operation should not have errors"); + Assert.AreEqual(200, verifyResult.Status.StatusCode, "Verify operation should return 200 status code"); + Assert.IsNotNull(verifyResult.Result, "Verify result data should not be null"); + Assert.IsNotNull(verifyResult.Result.MessageActions, "Message actions list should not be null"); + Assert.AreEqual(getResult.Result.MessageActions.Count, verifyResult.Result.MessageActions.Count, + "Number of message actions should not change after duplicate add"); + + pubnub.Destroy(); + pubnub.PubnubUnitTest = null; + pubnub = null; + } + } } diff --git a/src/UnitTests/PubnubApi.Tests/WhenMessageCountIsRequested.cs b/src/UnitTests/PubnubApi.Tests/WhenMessageCountIsRequested.cs index 451cbadb7..519e2e650 100644 --- a/src/UnitTests/PubnubApi.Tests/WhenMessageCountIsRequested.cs +++ b/src/UnitTests/PubnubApi.Tests/WhenMessageCountIsRequested.cs @@ -3,6 +3,7 @@ using PubnubApi; using MockServer; using System.Threading.Tasks; +using System; namespace PubNubMessaging.Tests { @@ -332,5 +333,69 @@ public static async Task ThenWithAsyncChannel2Timetoken2ShouldReturnSuccess() Assert.IsTrue(receivedMessage, "WhenMessageCountIsRequested -> ThenWithAsyncChannel2Timetoken2ShouldReturnSuccess failed."); } + + [Test] + public static async Task ThenMessageCountShouldReflectPublishedMessages() + { + string testChannel = $"foo.tc_{Guid.NewGuid()}"; + string customUuid = "mytestuuid"; + + PNConfiguration config = new PNConfiguration(new UserId(customUuid)) + { + PublishKey = PubnubCommon.PublishKey, + SubscribeKey = PubnubCommon.SubscribeKey, + SecretKey = PubnubCommon.SecretKey, + Secure = false + }; + if (PubnubCommon.PAMServerSideRun) + { + config.SecretKey = PubnubCommon.SecretKey; + } + + pubnub = createPubNubInstance(config); + + // Step 1: Check initial message count (should be 0) + PNResult initialCountResult = await pubnub.MessageCounts() + .Channels(new[] { testChannel }) + .ChannelsTimetoken(new long[] { DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() * 10000 }) + .ExecuteAsync(); + + Assert.IsNotNull(initialCountResult, "Initial count result should not be null"); + Assert.IsNotNull(initialCountResult.Result, "Initial count result data should not be null"); + Assert.IsNotNull(initialCountResult.Result.Channels, "Initial count channels should not be null"); + Assert.IsTrue(initialCountResult.Result.Channels.ContainsKey(testChannel), "Initial count should include test channel"); + Assert.AreEqual(0, initialCountResult.Result.Channels[testChannel], "Initial message count should be 0"); + + // Step 2: Publish a message + long timeToken = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() * 10000; + string testMessage = "Test message for counting"; + PNResult publishResult = await pubnub.Publish() + .Channel(testChannel) + .Message(testMessage) + .ExecuteAsync(); + + Assert.IsNotNull(publishResult, "Publish result should not be null"); + Assert.IsNotNull(publishResult.Result, "Publish result data should not be null"); + Assert.IsFalse(publishResult.Status.Error, "Publish should not have errors"); + Assert.IsTrue(publishResult.Result.Timetoken > 0, "Publish should return valid timetoken"); + + await Task.Delay(5000); + + // Step 3: Check message count after publishing (should be 1) + PNResult finalCountResult = await pubnub.MessageCounts() + .Channels(new[] { testChannel }) + .ChannelsTimetoken(new long[] { timeToken }) + .ExecuteAsync(); + + Assert.IsNotNull(finalCountResult, "Final count result should not be null"); + Assert.IsNotNull(finalCountResult.Result, "Final count result data should not be null"); + Assert.IsNotNull(finalCountResult.Result.Channels, "Final count channels should not be null"); + Assert.IsTrue(finalCountResult.Result.Channels.ContainsKey(testChannel), "Final count should include test channel"); + Assert.AreEqual(1, finalCountResult.Result.Channels[testChannel], "Final message count should be 1"); + + pubnub.Destroy(); + pubnub.PubnubUnitTest = null; + pubnub = null; + } } } diff --git a/src/UnitTests/PubnubApi.Tests/WhenMessageDeletedFromChannel.cs b/src/UnitTests/PubnubApi.Tests/WhenMessageDeletedFromChannel.cs index f6aa2ea3c..79766039c 100644 --- a/src/UnitTests/PubnubApi.Tests/WhenMessageDeletedFromChannel.cs +++ b/src/UnitTests/PubnubApi.Tests/WhenMessageDeletedFromChannel.cs @@ -1,8 +1,10 @@ -using NUnit.Framework; +using System; +using NUnit.Framework; using System.Threading; using PubnubApi; using MockServer; using System.Diagnostics; +using System.Linq; using System.Threading.Tasks; namespace PubNubMessaging.Tests @@ -98,7 +100,7 @@ public static void Exit() } [Test] - public static void ThenDeleteMessageShouldReturnSuccessMessage() + public static async Task ThenDeleteMessageShouldDeleteAndReturnSuccessMessage() { server.ClearRequests(); @@ -117,8 +119,20 @@ public static void ThenDeleteMessageShouldReturnSuccessMessage() } pubnub = createPubNubInstance(config); pubnub.SetAuthToken(authToken); + + pubnub.Subscribe().Channels(new []{channel}).WithPresence().Execute(); + await Task.Delay(2000); - string expected = "{\"status\": 200, \"error\": false, \"error_message\": \"\"}"; + var randomisedMessage = Guid.NewGuid().ToString(); + await pubnub.Publish().Message(randomisedMessage).Channel(channel).ExecuteAsync(); + await Task.Delay(4000); + + var history = await pubnub.FetchHistory().Channels(new[] { channel }).ExecuteAsync(); + + Assert.True(history.Result.Messages.Any(x => x.Value.Any(y => y.Entry.ToString() == randomisedMessage)) + , "Message was not present in history after sending"); + + var expected = "{\"status\": 200, \"error\": false, \"error_message\": \"\"}"; server.AddRequest(new Request() .WithMethod("DELETE") @@ -145,11 +159,20 @@ public static void ThenDeleteMessageShouldReturnSuccessMessage() })); deleteMessageManualEvent.WaitOne(manualResetEventWaitTimeout); + Assert.IsTrue(receivedMessage, "ThenDeleteMessageShouldReturnSuccessMessage - DeleteMessages Result not expected"); + + await Task.Delay(3000); + history = await pubnub.FetchHistory().Channels(new[] { channel }).ExecuteAsync(); + + Assert.True(history.Result?.Messages == null + || !history.Result.Messages.Any(x => x.Value.Any(y => y.Entry.ToString() == randomisedMessage)) + , "Message was present in history even after deletion"); + pubnub.Destroy(); pubnub.PubnubUnitTest = null; pubnub = null; - Assert.IsTrue(receivedMessage, "ThenDeleteMessageShouldReturnSuccessMessage - DeleteMessages Result not expected"); + } [Test] diff --git a/src/UnitTests/PubnubApi.Tests/WhenObjectChannelMetadata.cs b/src/UnitTests/PubnubApi.Tests/WhenObjectChannelMetadata.cs index 257b6f706..3ef8567c0 100644 --- a/src/UnitTests/PubnubApi.Tests/WhenObjectChannelMetadata.cs +++ b/src/UnitTests/PubnubApi.Tests/WhenObjectChannelMetadata.cs @@ -1,10 +1,12 @@ -using NUnit.Framework; +using System; +using NUnit.Framework; using System.Threading; using PubnubApi; using System.Collections.Generic; using MockServer; using System.Diagnostics; using System.Threading.Tasks; +using System.Linq; namespace PubNubMessaging.Tests { @@ -15,7 +17,7 @@ public class WhenObjectChannelMetadata : TestHarness private static Pubnub pubnub; private static Server server; private static string authToken; - private static string channelMetadataId = "pandu-ut-sid"; + private static string channelMetadataId = "foo.pandu-ut-sid"; [SetUp] public static async Task Init() @@ -45,38 +47,20 @@ public static async Task Init() pubnub = createPubNubInstance(config); - var grantResult = await pubnub.GrantToken().TTL(20).AuthorizedUuid(config.UserId).Resources( - new PNTokenResources() - { - Channels = new Dictionary() - { - { - channelMetadataId, new PNTokenAuthValues() - { - Read = true, - Write = true, - Create = true, - Get = true, - Delete = true, - Join = true, - Update = true, - Manage = true - } - } - } - }).ExecuteAsync(); + if (string.IsNullOrEmpty(PubnubCommon.GrantToken)) + { + await GenerateTestGrantToken(pubnub); + } + authToken = PubnubCommon.GrantToken; + if (!PubnubCommon.EnableStubTest) { await Task.Delay(3000); } - - authToken = grantResult.Result?.Token; - + pubnub.Destroy(); pubnub.PubnubUnitTest = null; pubnub = null; - Assert.IsTrue(grantResult.Result != null && grantResult.Status.Error == false, - "WhenObjectChannelMetadata Grant access failed."); } [TearDown] @@ -93,7 +77,7 @@ public static void Exit() } [Test] - public static void ThenChannelMetadataCRUDShouldReturnSuccessCodeAndInfo() + public static async Task ThenChannelMetadataCRUDShouldReturnSuccessCodeAndInfo() { server.ClearRequests(); @@ -132,16 +116,32 @@ public static void ThenChannelMetadataCRUDShouldReturnSuccessCodeAndInfo() #region "CreateSpace" System.Diagnostics.Debug.WriteLine("pubnub.CreateSpace() STARTED"); - pubnub.SetChannelMetadata().Channel(channelMetadataId).Name("pandu-ut-spname") + string initialName = "pandu-ut-spname"; + string initialDescription = "Initial description"; + Dictionary initialCustomData = new Dictionary() { { "type", "test" } }; + + pubnub.SetChannelMetadata() + .Channel(channelMetadataId) + .Name(initialName) + .Description(initialDescription) + .Custom(initialCustomData) + .IncludeCustom(true) .Execute(new PNSetChannelMetadataResultExt((r, s) => { if (r != null && s.StatusCode == 200 && !s.Error) { - pubnub.JsonPluggableLibrary.SerializeToJsonString(r); - if (channelMetadataId == r.Channel) - { - receivedMessage = true; - } + string jsonString = pubnub.JsonPluggableLibrary.SerializeToJsonString(r); + Debug.WriteLine($"CreateSpace Response: {jsonString}"); + + // Validate all fields in the response + Assert.AreEqual(channelMetadataId, r.Channel, "Channel ID mismatch"); + Assert.AreEqual(initialName, r.Name, "Channel name mismatch"); + Assert.AreEqual(initialDescription, r.Description, "Description mismatch"); + Assert.IsNotNull(r.Custom, "Custom data should not be null"); + Assert.AreEqual("test", r.Custom["type"], "Custom data type value mismatch"); + Assert.IsNotNull(r.Updated, "Updated timestamp should not be null"); + + receivedMessage = true; } manualEvent.Set(); @@ -150,6 +150,7 @@ public static void ThenChannelMetadataCRUDShouldReturnSuccessCodeAndInfo() #endregion manualEvent.WaitOne(manualResetEventWaitTimeout); + await Task.Delay(2000); if (receivedMessage) { @@ -159,18 +160,32 @@ public static void ThenChannelMetadataCRUDShouldReturnSuccessCodeAndInfo() #region "SetChannelMetadata" System.Diagnostics.Debug.WriteLine("pubnub.SetChannelMetadata() STARTED"); - pubnub.SetChannelMetadata().Channel(channelMetadataId).Name("pandu-ut-spname-upd") - .Description("pandu-ut-spdesc") - .Custom(new Dictionary() { { "color", "red" } }) + Dictionary customData = new Dictionary() { { "color", "red" } }; + string expectedName = "pandu-ut-spname-upd"; + string expectedDescription = "pandu-ut-spdesc"; + + pubnub.SetChannelMetadata() + .Channel(channelMetadataId) + .Name(expectedName) + .Description(expectedDescription) + .Custom(customData) + .IncludeCustom(true) .Execute(new PNSetChannelMetadataResultExt((r, s) => { if (r != null && s.StatusCode == 200 && !s.Error) { - pubnub.JsonPluggableLibrary.SerializeToJsonString(r); - if (channelMetadataId == r.Channel) - { - receivedMessage = true; - } + string jsonString = pubnub.JsonPluggableLibrary.SerializeToJsonString(r); + Debug.WriteLine($"SetChannelMetadata Response: {jsonString}"); + + // Validate all fields in the response + Assert.AreEqual(channelMetadataId, r.Channel, "Channel ID mismatch"); + Assert.AreEqual(expectedName, r.Name, "Channel name mismatch"); + Assert.AreEqual(expectedDescription, r.Description, "Description mismatch"); + Assert.IsNotNull(r.Custom, "Custom data should not be null"); + Assert.AreEqual("red", r.Custom["color"], "Custom data color value mismatch"); + Assert.IsNotNull(r.Updated, "Updated timestamp should not be null"); + + receivedMessage = true; } manualEvent.Set(); @@ -180,6 +195,8 @@ public static void ThenChannelMetadataCRUDShouldReturnSuccessCodeAndInfo() manualEvent.WaitOne(manualResetEventWaitTimeout); } + + await Task.Delay(2000); if (receivedMessage) { @@ -194,11 +211,18 @@ public static void ThenChannelMetadataCRUDShouldReturnSuccessCodeAndInfo() { if (r != null && s.StatusCode == 200 && !s.Error) { - pubnub.JsonPluggableLibrary.SerializeToJsonString(r); - if (channelMetadataId == r.Channel) - { - receivedMessage = true; - } + string jsonString = pubnub.JsonPluggableLibrary.SerializeToJsonString(r); + Debug.WriteLine($"GetChannelMetadata Response: {jsonString}"); + + // Validate all fields in the response + Assert.AreEqual(channelMetadataId, r.Channel, "Channel ID mismatch"); + Assert.AreEqual("pandu-ut-spname-upd", r.Name, "Channel name mismatch"); + Assert.AreEqual("pandu-ut-spdesc", r.Description, "Description mismatch"); + Assert.IsNotNull(r.Custom, "Custom data should not be null"); + Assert.AreEqual("red", r.Custom["color"], "Custom data color value mismatch"); + Assert.IsNotNull(r.Updated, "Updated timestamp should not be null"); + + receivedMessage = true; } manualEvent.Set(); @@ -217,18 +241,61 @@ public static void ThenChannelMetadataCRUDShouldReturnSuccessCodeAndInfo() #region "GetAllChannelMetadata" System.Diagnostics.Debug.WriteLine("pubnub.GetAllChannelMetadata() STARTED"); - pubnub.GetAllChannelMetadata().IncludeCount(true) + pubnub.GetAllChannelMetadata().IncludeCount(true).IncludeCustom(true) .Execute(new PNGetAllChannelMetadataResultExt((r, s) => { if (r != null && s.StatusCode == 200 && !s.Error) { - pubnub.JsonPluggableLibrary.SerializeToJsonString(r); - List spaceList = r.Channels; - if (spaceList != null && spaceList.Count > 0 && - spaceList.Find(x => x.Channel == channelMetadataId) != null) - { - receivedMessage = true; - } + string jsonString = pubnub.JsonPluggableLibrary.SerializeToJsonString(r); + Debug.WriteLine($"GetAllChannelMetadata Response: {jsonString}"); + + // Validate all fields in the response + Assert.IsNotNull(r.Channels, "Channels list should not be null"); + Assert.Greater(r.Channels.Count, 0, "Channels list should not be empty"); + + PNChannelMetadataResult channelMetadata = r.Channels.Find(x => x.Channel == channelMetadataId); + Assert.IsNotNull(channelMetadata, "Channel metadata not found in list"); + + // Validate the found channel metadata + Assert.AreEqual(channelMetadataId, channelMetadata.Channel, "Channel ID mismatch"); + Assert.AreEqual("pandu-ut-spname-upd", channelMetadata.Name, "Channel name mismatch"); + Assert.AreEqual("pandu-ut-spdesc", channelMetadata.Description, "Description mismatch"); + Assert.IsNotNull(channelMetadata.Custom, "Custom data should not be null"); + Assert.AreEqual("red", channelMetadata.Custom["color"], "Custom data color value mismatch"); + Assert.IsNotNull(channelMetadata.Updated, "Updated timestamp should not be null"); + + // Validate pagination data + Assert.Greater(r.TotalCount, 0, "Total count should be greater than 0"); + + receivedMessage = true; + } + + manualEvent.Set(); + })); + + #endregion + + manualEvent.WaitOne(manualResetEventWaitTimeout); + } + + if (receivedMessage) + { + receivedMessage = false; + manualEvent = new ManualResetEvent(false); + + #region "RemoveChannelMetadata" + + System.Diagnostics.Debug.WriteLine("pubnub.RemoveChannelMetadata() STARTED"); + pubnub.RemoveChannelMetadata().Channel(channelMetadataId) + .Execute(new PNRemoveChannelMetadataResultExt((r, s) => + { + if (r != null && s.StatusCode == 200 && !s.Error) + { + string jsonString = pubnub.JsonPluggableLibrary.SerializeToJsonString(r); + Debug.WriteLine($"RemoveChannelMetadata Response: {jsonString}"); + + // No properties to validate as the response is empty + receivedMessage = true; } manualEvent.Set(); @@ -241,7 +308,7 @@ public static void ThenChannelMetadataCRUDShouldReturnSuccessCodeAndInfo() if (!receivedMessage) { - Assert.IsTrue(receivedMessage, $"SetChannelMetadata/DeleteChannelMetadataId Failed."); + Assert.IsTrue(receivedMessage, $"ChannelMetadata CRUD operations failed."); } pubnub.Destroy(); @@ -693,5 +760,271 @@ public static async Task ThenWithAsyncChannelMetadataUpdateDeleteShouldReturnEve pubnub.PubnubUnitTest = null; pubnub = null; } + + [Test] + public static async Task ThenSetChannelMembersShouldReturnSuccess() + { + string testChannel = $"foo.tc_{Guid.NewGuid()}"; + string testUuid = "fuu.test-uuid-1"; + Dictionary customData = new Dictionary { { "role", "admin" } }; + + PNConfiguration config = new PNConfiguration(new UserId("mytestuuid")) + { + PublishKey = PubnubCommon.PublishKey, + SubscribeKey = PubnubCommon.SubscribeKey, + Secure = false + }; + if (PubnubCommon.PAMServerSideRun) + { + config.SecretKey = PubnubCommon.SecretKey; + } + + pubnub = createPubNubInstance(config); + pubnub.SetAuthToken(authToken); + + // Create channel member + var member = new PNChannelMember + { + Uuid = testUuid, + Custom = customData + }; + + // Set channel member + PNResult setResult = await pubnub.SetChannelMembers() + .Channel(testChannel) + .Uuids(new List { member }) + .Include(new[] { PNChannelMemberField.CUSTOM, PNChannelMemberField.UUID }) + .ExecuteAsync(); + + Assert.IsNotNull(setResult, "Set result should not be null"); + Assert.IsNotNull(setResult.Result, "Set result data should not be null"); + Assert.IsFalse(setResult.Status.Error, "Set operation should not have errors"); + Assert.AreEqual(200, setResult.Status.StatusCode, "Set operation should return 200 status code"); + Assert.IsNotNull(setResult.Result.ChannelMembers, "Members list should not be null"); + Assert.AreEqual(1, setResult.Result.ChannelMembers.Count, "Should have one member"); + Assert.AreEqual(testUuid, setResult.Result.ChannelMembers[0].UuidMetadata.Uuid, "Member UUID should match"); + Assert.IsNotNull(setResult.Result.ChannelMembers[0].Custom, "Member custom data should not be null"); + Assert.AreEqual("admin", setResult.Result.ChannelMembers[0].Custom["role"], "Member role should be admin"); + + pubnub.Destroy(); + pubnub.PubnubUnitTest = null; + pubnub = null; + } + + [Test] + public static async Task ThenSetAndGetChannelMembersShouldReturnSuccess() + { + string testChannel = $"foo.tc_{Guid.NewGuid()}"; + string testUuid = "fuu.test-uuid-2"; + Dictionary customData = new Dictionary { { "role", "user" } }; + + PNConfiguration config = new PNConfiguration(new UserId("mytestuuid")) + { + PublishKey = PubnubCommon.PublishKey, + SubscribeKey = PubnubCommon.SubscribeKey, + Secure = false + }; + if (PubnubCommon.PAMServerSideRun) + { + config.SecretKey = PubnubCommon.SecretKey; + } + + pubnub = createPubNubInstance(config); + pubnub.SetAuthToken(authToken); + + // Create and set channel member + var member = new PNChannelMember + { + Uuid = testUuid, + Custom = customData + }; + + PNResult setResult = await pubnub.SetChannelMembers() + .Channel(testChannel) + .Uuids(new List { member }) + .Include(new[] { PNChannelMemberField.CUSTOM, PNChannelMemberField.UUID }) + .ExecuteAsync(); + + Assert.IsNotNull(setResult, "Set result should not be null"); + Assert.IsFalse(setResult.Status.Error, "Set operation should not have errors"); + + // Get channel members + PNResult getResult = await pubnub.GetChannelMembers() + .Channel(testChannel) + .Include(new[] { PNChannelMemberField.CUSTOM, PNChannelMemberField.UUID }) + .ExecuteAsync(); + + Assert.IsNotNull(getResult, "Get result should not be null"); + Assert.IsNotNull(getResult.Result, "Get result data should not be null"); + Assert.IsFalse(getResult.Status.Error, "Get operation should not have errors"); + Assert.AreEqual(200, getResult.Status.StatusCode, "Get operation should return 200 status code"); + Assert.IsNotNull(getResult.Result.ChannelMembers, "Members list should not be null"); + Assert.AreEqual(1, getResult.Result.ChannelMembers.Count, "Should have one member"); + Assert.AreEqual(testUuid, getResult.Result.ChannelMembers[0].UuidMetadata.Uuid, "Member UUID should match"); + Assert.IsNotNull(getResult.Result.ChannelMembers[0].Custom, "Member custom data should not be null"); + Assert.AreEqual("user", getResult.Result.ChannelMembers[0].Custom["role"], "Member role should be user"); + + pubnub.Destroy(); + pubnub.PubnubUnitTest = null; + pubnub = null; + } + + [Test] + public static async Task ThenSetGetAndRemoveChannelMembersShouldReturnSuccess() + { + string testChannel = $"foo.tc_{Guid.NewGuid()}"; + string testUuid = "fuu.test-uuid-3"; + Dictionary customData = new Dictionary { { "role", "moderator" } }; + + PNConfiguration config = new PNConfiguration(new UserId("mytestuuid")) + { + PublishKey = PubnubCommon.PublishKey, + SubscribeKey = PubnubCommon.SubscribeKey, + Secure = false + }; + if (PubnubCommon.PAMServerSideRun) + { + config.SecretKey = PubnubCommon.SecretKey; + } + + pubnub = createPubNubInstance(config); + pubnub.SetAuthToken(authToken); + + // Create and set channel member + var member = new PNChannelMember + { + Uuid = testUuid, + Custom = customData + }; + + PNResult setResult = await pubnub.SetChannelMembers() + .Channel(testChannel) + .Uuids(new List { member }) + .Include(new[] { PNChannelMemberField.CUSTOM, PNChannelMemberField.UUID }) + .ExecuteAsync(); + + Assert.IsNotNull(setResult, "Set result should not be null"); + Assert.IsFalse(setResult.Status.Error, "Set operation should not have errors"); + + await Task.Delay(6000); + + // Get channel members before removal + PNResult getResult = await pubnub.GetChannelMembers() + .Channel(testChannel) + .Include(new[] { PNChannelMemberField.CUSTOM, PNChannelMemberField.UUID }) + .ExecuteAsync(); + + Assert.IsNotNull(getResult, "Get result should not be null"); + Assert.IsFalse(getResult.Status.Error, "Get operation should not have errors"); + Assert.AreEqual(1, getResult.Result.ChannelMembers.Count, "Should have one member before removal"); + + // Remove channel member + PNResult removeResult = await pubnub.RemoveChannelMembers() + .Channel(testChannel) + .Uuids(new List { testUuid }) + .Include(new[] { PNChannelMemberField.CUSTOM, PNChannelMemberField.UUID }) + .ExecuteAsync(); + + Assert.IsNotNull(removeResult, "Remove result should not be null"); + Assert.IsNotNull(removeResult.Result, "Remove result data should not be null"); + Assert.IsFalse(removeResult.Status.Error, "Remove operation should not have errors"); + Assert.AreEqual(200, removeResult.Status.StatusCode, "Remove operation should return 200 status code"); + + // Get channel members after removal + PNResult getAfterRemoveResult = await pubnub.GetChannelMembers() + .Channel(testChannel) + .Include(new[] { PNChannelMemberField.CUSTOM, PNChannelMemberField.UUID }) + .ExecuteAsync(); + + Assert.IsNotNull(getAfterRemoveResult, "Get after remove result should not be null"); + Assert.IsFalse(getAfterRemoveResult.Status.Error, "Get after remove operation should not have errors"); + Assert.AreEqual(0, getAfterRemoveResult.Result.ChannelMembers.Count, "Should have no members after removal"); + + pubnub.Destroy(); + pubnub.PubnubUnitTest = null; + pubnub = null; + } + + [Test] + public static async Task ThenSetAndManageChannelMembersShouldReturnSuccess() + { + string testChannel = $"foo.tc_{Guid.NewGuid()}"; + string testUuid1 = $"fuu.tu_{Guid.NewGuid()}"; + string testUuid2 = $"fuu.tu_{Guid.NewGuid()}"; + Dictionary customData1 = new Dictionary { { "role", "admin" } }; + Dictionary customData2 = new Dictionary { { "role", "user" } }; + + PNConfiguration config = new PNConfiguration(new UserId("mytestuuid")) + { + PublishKey = PubnubCommon.PublishKey, + SubscribeKey = PubnubCommon.SubscribeKey, + Secure = false + }; + if (PubnubCommon.PAMServerSideRun) + { + config.SecretKey = PubnubCommon.SecretKey; + } + + pubnub = createPubNubInstance(config); + pubnub.SetAuthToken(authToken); + + // Create and set initial channel member + var member1 = new PNChannelMember + { + Uuid = testUuid1, + Custom = customData1 + }; + + PNResult setResult = await pubnub.SetChannelMembers() + .Channel(testChannel) + .Uuids(new List { member1 }) + .Include(new[] { PNChannelMemberField.CUSTOM, PNChannelMemberField.UUID }) + .ExecuteAsync(); + + Assert.IsNotNull(setResult, "Set result should not be null"); + Assert.IsFalse(setResult.Status.Error, "Set operation should not have errors"); + + await Task.Delay(6000); + + // Manage channel members (add new, update existing) + var member2 = new PNChannelMember + { + Uuid = testUuid2, + Custom = customData2 + }; + + PNResult manageResult = await pubnub.ManageChannelMembers() + .Channel(testChannel) + .Set(new List { member1, member2 }) + .Include(new[] { PNChannelMemberField.CUSTOM, PNChannelMemberField.UUID }) + .ExecuteAsync(); + + Assert.IsNotNull(manageResult, "Manage result should not be null"); + Assert.IsNotNull(manageResult.Result, "Manage result data should not be null"); + Assert.IsFalse(manageResult.Status.Error, "Manage operation should not have errors"); + Assert.AreEqual(200, manageResult.Status.StatusCode, "Manage operation should return 200 status code"); + Assert.IsNotNull(manageResult.Result.ChannelMembers, "Members list should not be null"); + Assert.AreEqual(2, manageResult.Result.ChannelMembers.Count, "Should have two members"); + + await Task.Delay(5000); + + // Get channel members to verify + PNResult getResult = await pubnub.GetChannelMembers() + .Channel(testChannel) + .Include(new[] { PNChannelMemberField.CUSTOM, PNChannelMemberField.UUID }) + .ExecuteAsync(); + + Assert.IsNotNull(getResult, "Get result should not be null"); + Assert.IsFalse(getResult.Status.Error, "Get operation should not have errors"); + Assert.AreEqual(2, getResult.Result.ChannelMembers.Count, "Should have two members after manage operation"); + + var members = getResult.Result.ChannelMembers; + Assert.IsTrue(members.Any(m => m.UuidMetadata.Uuid == testUuid1 && m.Custom["role"].ToString() == "admin"), "First member should exist with admin role"); + Assert.IsTrue(members.Any(m => m.UuidMetadata.Uuid == testUuid2 && m.Custom["role"].ToString() == "user"), "Second member should exist with user role"); + + pubnub.Destroy(); + pubnub.PubnubUnitTest = null; + pubnub = null; + } } } \ No newline at end of file diff --git a/src/UnitTests/PubnubApi.Tests/WhenObjectMembership.cs b/src/UnitTests/PubnubApi.Tests/WhenObjectMembership.cs index e3f1913b3..37507a58e 100644 --- a/src/UnitTests/PubnubApi.Tests/WhenObjectMembership.cs +++ b/src/UnitTests/PubnubApi.Tests/WhenObjectMembership.cs @@ -1,9 +1,11 @@ -using NUnit.Framework; +using System; +using NUnit.Framework; using System.Threading; using PubnubApi; using System.Collections.Generic; using MockServer; using System.Diagnostics; +using System.Linq; using System.Threading.Tasks; namespace PubNubMessaging.Tests @@ -643,13 +645,15 @@ public static void ThenSetRemoveChannelMetadataWithSetRemoveMembershipShouldRetu pubnub.SetUuidMetadata().Uuid(uuidMetadataId).Name("pandu-ut-un") .Execute(new PNSetUuidMetadataResultExt((r, s) => { - if (r != null && s.StatusCode == 200 && !s.Error) + Assert.IsNotNull(r, $"Set UUID metadata result was null. Error info: {s.ErrorData?.Information}"); + Assert.AreEqual(200, s.StatusCode, $"Set UUID metadata status was not 200, status: " + + $"\n{pubnub.JsonPluggableLibrary.SerializeToJsonString(s)}"); + Assert.False(s.Error, "Set UUID metadata status reported an error, status: " + + $"\n{pubnub.JsonPluggableLibrary.SerializeToJsonString(s)}"); + Assert.AreEqual(uuidMetadataId, r.Uuid, "UUID metadata ID did not match"); + if (uuidMetadataId == r.Uuid) { - pubnub.JsonPluggableLibrary.SerializeToJsonString(r); - if (uuidMetadataId == r.Uuid) - { - receivedMessage = true; - } + receivedMessage = true; } manualEvent.Set(); })); @@ -665,13 +669,15 @@ public static void ThenSetRemoveChannelMetadataWithSetRemoveMembershipShouldRetu pubnub.SetChannelMetadata().Channel(channelMetadataId1).Name("pandu-ut-spname") .Execute(new PNSetChannelMetadataResultExt((r, s) => { - if (r != null && s.StatusCode == 200 && !s.Error) + Assert.IsNotNull(r, $"Set channel 1 metadata result was null. Error info: {s.ErrorData?.Information}"); + Assert.AreEqual(200, s.StatusCode, $"Set channel 1 metadata status was not 200, status: " + + $"\n{pubnub.JsonPluggableLibrary.SerializeToJsonString(s)}"); + Assert.False(s.Error, "Set channel 1 metadata status reported an error, status: " + + $"\n{pubnub.JsonPluggableLibrary.SerializeToJsonString(s)}"); + Assert.AreEqual(channelMetadataId1, r.Channel, "Channel 1 metadata ID did not match"); + if (channelMetadataId1 == r.Channel) { - pubnub.JsonPluggableLibrary.SerializeToJsonString(r); - if (channelMetadataId1 == r.Channel) - { - receivedMessage = true; - } + receivedMessage = true; } manualEvent.Set(); })); @@ -687,13 +693,15 @@ public static void ThenSetRemoveChannelMetadataWithSetRemoveMembershipShouldRetu pubnub.SetChannelMetadata().Channel(channelMetadataId2).Name("pandu-ut-spname") .Execute(new PNSetChannelMetadataResultExt((r, s) => { - if (r != null && s.StatusCode == 200 && !s.Error) + Assert.IsNotNull(r, $"Set channel 2 metadata result was null. Error info: {s.ErrorData?.Information}"); + Assert.AreEqual(200, s.StatusCode, $"Set channel 2 metadata status was not 200, status: " + + $"\n{pubnub.JsonPluggableLibrary.SerializeToJsonString(s)}"); + Assert.False(s.Error, "Set channel 2 metadata status reported an error, status: " + + $"\n{pubnub.JsonPluggableLibrary.SerializeToJsonString(s)}"); + Assert.AreEqual(channelMetadataId2, r.Channel, "Channel 2 metadata ID did not match"); + if (channelMetadataId2 == r.Channel) { - pubnub.JsonPluggableLibrary.SerializeToJsonString(r); - if (channelMetadataId2 == r.Channel) - { - receivedMessage = true; - } + receivedMessage = true; } manualEvent.Set(); })); @@ -705,25 +713,31 @@ public static void ThenSetRemoveChannelMetadataWithSetRemoveMembershipShouldRetu { receivedMessage = false; manualEvent = new ManualResetEvent(false); - #region "SetMemberships ADD" - System.Diagnostics.Debug.WriteLine("pubnub.SetMemberships() ADD STARTED"); + #region "SetMemberships Add" + System.Diagnostics.Debug.WriteLine("pubnub.SetMemberships() SET STARTED"); pubnub.SetMemberships().Uuid(uuidMetadataId) .Channels(new List() { - new PNMembership() { Channel = channelMetadataId1 }, - new PNMembership() { Channel = channelMetadataId2 } + new PNMembership() { Channel = channelMetadataId1 }, + new PNMembership() { Channel = channelMetadataId2 } }) .Execute(new PNMembershipsResultExt((r, s) => { - if (r != null && s.StatusCode == 200 && !s.Error) + Assert.IsNotNull(r, $"Set memberships result was null. Error info: {s.ErrorData?.Information}"); + Assert.AreEqual(200, s.StatusCode, $"Set memberships status was not 200, status: " + + $"\n{pubnub.JsonPluggableLibrary.SerializeToJsonString(s)}"); + Assert.False(s.Error, "Set memberships status reported an error, status: " + + $"\n{pubnub.JsonPluggableLibrary.SerializeToJsonString(s)}"); + Assert.IsNotNull(r.Memberships, "Memberships list was null"); + Assert.IsTrue(r.Memberships.Find(x => x.ChannelMetadata.Channel == channelMetadataId1) != null, + "Channel 1 membership not found"); + Assert.IsTrue(r.Memberships.Find(x => x.ChannelMetadata.Channel == channelMetadataId2) != null, + "Channel 2 membership not found"); + if (r.Memberships != null + && r.Memberships.Find(x => x.ChannelMetadata.Channel == channelMetadataId1) != null + && r.Memberships.Find(x => x.ChannelMetadata.Channel == channelMetadataId2) != null) { - pubnub.JsonPluggableLibrary.SerializeToJsonString(r); - if (r.Memberships != null - && r.Memberships.Find(x => x.ChannelMetadata.Channel == channelMetadataId1) != null - && r.Memberships.Find(x => x.ChannelMetadata.Channel == channelMetadataId2) != null) - { - receivedMessage = true; - } + receivedMessage = true; } manualEvent.Set(); })); @@ -740,25 +754,28 @@ public static void ThenSetRemoveChannelMetadataWithSetRemoveMembershipShouldRetu pubnub.SetMemberships().Uuid(uuidMetadataId) .Channels(new List() { - new PNMembership() { Channel = channelMetadataId1 , Custom = new Dictionary(){ { "color", "green1" } } }, - new PNMembership() { Channel = channelMetadataId2 , Custom = new Dictionary(){ { "color", "green2" } } } + new PNMembership() { Channel = channelMetadataId1, Custom = new Dictionary(){ { "color", "green1" } } } }) .Execute(new PNMembershipsResultExt((r, s) => { - if (r != null && s.StatusCode == 200 && !s.Error) + Assert.IsNotNull(r, $"Update memberships result was null. Error info: {s.ErrorData?.Information}"); + Assert.AreEqual(200, s.StatusCode, $"Update memberships status was not 200, status: " + + $"\n{pubnub.JsonPluggableLibrary.SerializeToJsonString(s)}"); + Assert.False(s.Error, "Update memberships status reported an error, status: " + + $"\n{pubnub.JsonPluggableLibrary.SerializeToJsonString(s)}"); + Assert.IsNotNull(r.Memberships, "Memberships list was null"); + Assert.IsTrue(r.Memberships.Find(x => x.ChannelMetadata.Channel == channelMetadataId1) != null, + "Channel 1 membership not found after update"); + if (r.Memberships != null + && r.Memberships.Find(x => x.ChannelMetadata.Channel == channelMetadataId1) != null) { - pubnub.JsonPluggableLibrary.SerializeToJsonString(r); - if (r.Memberships != null - && r.Memberships.Find(x => x.ChannelMetadata.Channel == channelMetadataId1) != null - && r.Memberships.Find(x => x.ChannelMetadata.Channel == channelMetadataId2) != null) - { - receivedMessage = true; - } + receivedMessage = true; } manualEvent.Set(); })); - #endregion manualEvent.WaitOne(manualResetEventWaitTimeout); + + #endregion } if (receivedMessage) @@ -771,13 +788,15 @@ public static void ThenSetRemoveChannelMetadataWithSetRemoveMembershipShouldRetu .Channels(new List() { channelMetadataId2 }) .Execute(new PNMembershipsResultExt((r, s) => { - if (r != null && s.StatusCode == 200 && !s.Error) + Assert.IsNotNull(r, $"Remove memberships result was null. Error info: {s.ErrorData?.Information}"); + Assert.AreEqual(200, s.StatusCode, $"Remove memberships status was not 200, status: " + + $"\n{pubnub.JsonPluggableLibrary.SerializeToJsonString(s)}"); + Assert.False(s.Error, "Remove memberships status reported an error, status: " + + $"\n{pubnub.JsonPluggableLibrary.SerializeToJsonString(s)}"); + Assert.IsNotNull(r.Memberships, "Memberships list was null after removal"); + if (r.Memberships != null) { - pubnub.JsonPluggableLibrary.SerializeToJsonString(r); - if (r.Memberships != null) - { - receivedMessage = true; - } + receivedMessage = true; } manualEvent.Set(); })); @@ -794,14 +813,17 @@ public static void ThenSetRemoveChannelMetadataWithSetRemoveMembershipShouldRetu pubnub.GetMemberships().Uuid(uuidMetadataId) .Execute(new PNMembershipsResultExt((r, s) => { - if (r != null && s.StatusCode == 200 && !s.Error) + Assert.IsNotNull(r, $"Get memberships result was null. Error info: {s.ErrorData?.Information}"); + Assert.AreEqual(200, s.StatusCode, $"Get memberships status was not 200, status: " + + $"\n{pubnub.JsonPluggableLibrary.SerializeToJsonString(s)}"); + Assert.False(s.Error, "Get memberships status reported an error, status: " + + $"\n{pubnub.JsonPluggableLibrary.SerializeToJsonString(s)}"); + Assert.IsNotNull(r.Memberships, "Memberships list was null after get"); + Assert.IsTrue(r.Memberships.Find(x => x.ChannelMetadata.Channel == channelMetadataId1) != null, + "Channel 1 membership not found in get result"); + if (r.Memberships?.Find(x => x.ChannelMetadata.Channel == channelMetadataId1) != null) { - pubnub.JsonPluggableLibrary.SerializeToJsonString(r); - if (r.Memberships != null - && r.Memberships.Find(x => x.ChannelMetadata.Channel == channelMetadataId1) != null) - { - receivedMessage = true; - } + receivedMessage = true; } manualEvent.Set(); })); @@ -947,8 +969,8 @@ public static async Task ThenWithAsyncSetRemoveChannelMetadataWithSetRemoveMembe PNResult manageMbrshipAddResult = Task.Factory.StartNew(async () => await pubnub.SetMemberships().Uuid(uuidMetadataId) .Channels(new List() { - new PNMembership() { Channel = channelMetadataId1 }, - new PNMembership() { Channel = channelMetadataId2 } + new PNMembership() { Channel = channelMetadataId1 }, + new PNMembership() { Channel = channelMetadataId2 } }) .ExecuteAsync()).Result.Result; #else @@ -1718,15 +1740,21 @@ public static void ThenSetRemoveMembershipsShouldReturnEventInfo() }) .Execute(new PNMembershipsResultExt((r, s) => { - if (r != null && s.StatusCode == 200 && !s.Error) + Assert.IsNotNull(r, "Set memberships result was null"); + Assert.AreEqual(200, s.StatusCode, $"Set memberships status was not 200, status: " + + $"\n{pubnub.JsonPluggableLibrary.SerializeToJsonString(s)}"); + Assert.False(s.Error, "Set memberships status reported an error, status: " + + $"\n{pubnub.JsonPluggableLibrary.SerializeToJsonString(s)}"); + Assert.IsNotNull(r.Memberships, "Memberships list was null"); + Assert.IsTrue(r.Memberships.Find(x => x.ChannelMetadata.Channel == channelMetadataId1) != null, + "Channel 1 membership not found"); + Assert.IsTrue(r.Memberships.Find(x => x.ChannelMetadata.Channel == channelMetadataId2) != null, + "Channel 2 membership not found"); + if (r.Memberships != null + && r.Memberships.Find(x => x.ChannelMetadata.Channel == channelMetadataId1) != null + && r.Memberships.Find(x => x.ChannelMetadata.Channel == channelMetadataId2) != null) { - pubnub.JsonPluggableLibrary.SerializeToJsonString(r); - if (r.Memberships != null - && r.Memberships.Find(x => x.ChannelMetadata.Channel == channelMetadataId1) != null - && r.Memberships.Find(x => x.ChannelMetadata.Channel == channelMetadataId2) != null) - { - receivedMessage = true; - } + receivedMessage = true; } manualEvent.Set(); })); @@ -1749,14 +1777,18 @@ public static void ThenSetRemoveMembershipsShouldReturnEventInfo() }) .Execute(new PNMembershipsResultExt((r, s) => { - if (r != null && s.StatusCode == 200 && !s.Error) + Assert.IsNotNull(r, "Update memberships result was null"); + Assert.AreEqual(200, s.StatusCode, $"Update memberships status was not 200, status: " + + $"\n{pubnub.JsonPluggableLibrary.SerializeToJsonString(s)}"); + Assert.False(s.Error, "Update memberships status reported an error, status: " + + $"\n{pubnub.JsonPluggableLibrary.SerializeToJsonString(s)}"); + Assert.IsNotNull(r.Memberships, "Memberships list was null"); + Assert.IsTrue(r.Memberships.Find(x => x.ChannelMetadata.Channel == channelMetadataId1) != null, + "Channel 1 membership not found after update"); + if (r.Memberships != null + && r.Memberships.Find(x => x.ChannelMetadata.Channel == channelMetadataId1) != null) { - pubnub.JsonPluggableLibrary.SerializeToJsonString(r); - if (r.Memberships != null - && r.Memberships.Find(x => x.ChannelMetadata.Channel == channelMetadataId1) != null) - { - receivedMessage = true; - } + receivedMessage = true; } manualEvent.Set(); })); @@ -1985,9 +2017,6 @@ public static async Task ThenWithAsyncSetRemoveMembershipsShouldReturnEventInfo( receivedMessage = true; } } - else - { - } #endregion } @@ -2003,5 +2032,317 @@ public static async Task ThenWithAsyncSetRemoveMembershipsShouldReturnEventInfo( pubnub = null; } + [Test] + public static async Task ThenSetMembershipsShouldHandleAllFeatures() + { + server.ClearRequests(); + + if (PubnubCommon.EnableStubTest) + { + Assert.Ignore("Ignored ThenSetMembershipsShouldHandleAllFeatures"); + return; + } + + string uuidMetadataId = "pandu-ut-uid"; + string channelMetadataId1 = "pandu-ut-sid1"; + string channelMetadataId2 = "pandu-ut-sid2"; + string channelMetadataId3 = "pandu-ut-sid2"; // Using existing channel ID since we only have two available + + PNConfiguration config = new PNConfiguration(new UserId("mytestuuid")) + { + PublishKey = PubnubCommon.PublishKey, + SubscribeKey = PubnubCommon.SubscribeKey, + Secure = false + }; + if (PubnubCommon.PAMServerSideRun) + { + config.SecretKey = PubnubCommon.SecretKey; + } + + pubnub = createPubNubInstance(config); + if (!string.IsNullOrEmpty(authToken)) + { + pubnub.SetAuthToken(authToken); + } + + // First create the UUID metadata + PNResult createUserResult = await pubnub.SetUuidMetadata() + .Uuid(uuidMetadataId) + .Name("pandu-ut-un") + .ExecuteAsync(); + + Assert.IsNotNull(createUserResult.Result, "UUID metadata creation failed"); + Assert.AreEqual(200, createUserResult.Status.StatusCode, "UUID metadata creation status code should be 200"); + Assert.IsFalse(createUserResult.Status.Error, "UUID metadata creation should not have errors"); + + // Create channel metadata for all channels + PNResult createChannel1Result = await pubnub.SetChannelMetadata() + .Channel(channelMetadataId1) + .Name("pandu-ut-spname") + .ExecuteAsync(); + + Assert.IsNotNull(createChannel1Result.Result, "Channel 1 metadata creation failed"); + Assert.AreEqual(200, createChannel1Result.Status.StatusCode, "Channel 1 metadata creation status code should be 200"); + + PNResult createChannel2Result = await pubnub.SetChannelMetadata() + .Channel(channelMetadataId2) + .Name("pandu-ut-spname") + .ExecuteAsync(); + + Assert.IsNotNull(createChannel2Result.Result, "Channel 2 metadata creation failed"); + Assert.AreEqual(200, createChannel2Result.Status.StatusCode, "Channel 2 metadata creation status code should be 200"); + + // Test 1: Basic membership setting with custom data + var memberships = new List + { + new PNMembership + { + Channel = channelMetadataId1, + Custom = new Dictionary { { "role", "admin" } } + }, + new PNMembership + { + Channel = channelMetadataId2, + Custom = new Dictionary { { "role", "member" } } + } + }; + + PNResult setMembershipsResult = await pubnub.SetMemberships() + .Uuid(uuidMetadataId) + .Channels(memberships) + .Include(new[] { PNMembershipField.CUSTOM, PNMembershipField.CHANNEL }) + .Limit(10) + .IncludeCount(true) + .ExecuteAsync(); + + Assert.IsNotNull(setMembershipsResult.Result, "Set memberships result should not be null"); + Assert.AreEqual(200, setMembershipsResult.Status.StatusCode, "Set memberships status code should be 200"); + Assert.IsFalse(setMembershipsResult.Status.Error, "Set memberships should not have errors"); + Assert.IsNotNull(setMembershipsResult.Result.Memberships, "Memberships list should not be null"); + Assert.AreEqual(2, setMembershipsResult.Result.Memberships.Count, "Should have 2 memberships"); + Assert.IsTrue(setMembershipsResult.Result.Memberships.Any(m => m.ChannelMetadata.Channel == channelMetadataId1), "Should contain channel 1"); + Assert.IsTrue(setMembershipsResult.Result.Memberships.Any(m => m.ChannelMetadata.Channel == channelMetadataId2), "Should contain channel 2"); + Assert.IsTrue(setMembershipsResult.Result.Memberships.Any(m => m.Custom != null && m.Custom.ContainsKey("role")), "Should have custom data"); + + // Test 2: Update existing membership and add new one + var updatedMemberships = new List + { + new PNMembership + { + Channel = channelMetadataId1, + Custom = new Dictionary { { "role", "superadmin" } } + }, + new PNMembership + { + Channel = channelMetadataId2, + Custom = new Dictionary { { "role", "viewer" } } + } + }; + + PNResult updateMembershipsResult = await pubnub.SetMemberships() + .Uuid(uuidMetadataId) + .Channels(updatedMemberships) + .Include(new[] { PNMembershipField.CUSTOM, PNMembershipField.CHANNEL }) + .Limit(10) + .IncludeCount(true) + .ExecuteAsync(); + + Assert.IsNotNull(updateMembershipsResult.Result, "Update memberships result should not be null"); + Assert.AreEqual(200, updateMembershipsResult.Status.StatusCode, "Update memberships status code should be 200"); + Assert.IsFalse(updateMembershipsResult.Status.Error, "Update memberships should not have errors"); + Assert.IsNotNull(updateMembershipsResult.Result.Memberships, "Memberships list should not be null"); + Assert.IsTrue(updateMembershipsResult.Result.Memberships.Any(m => m.ChannelMetadata.Channel == channelMetadataId1 && + m.Custom != null && m.Custom["role"].ToString() == "superadmin"), "Should have updated channel 1"); + Assert.IsTrue(updateMembershipsResult.Result.Memberships.Any(m => m.ChannelMetadata.Channel == channelMetadataId2), "Should contain channel 2"); + + // Cleanup + await pubnub.RemoveUuidMetadata().Uuid(uuidMetadataId).ExecuteAsync(); + await pubnub.RemoveChannelMetadata().Channel(channelMetadataId1).ExecuteAsync(); + await pubnub.RemoveChannelMetadata().Channel(channelMetadataId2).ExecuteAsync(); + + pubnub.Destroy(); + pubnub.PubnubUnitTest = null; + pubnub = null; + } + + [Test] + public static async Task ThenMembershipOperationsShouldWorkCorrectly() + { + server.ClearRequests(); + + if (PubnubCommon.EnableStubTest) + { + Assert.Ignore("Ignored ThenMembershipOperationsShouldWorkCorrectly"); + return; + } + + string uuidMetadataId = "pandu-ut-uid"; + string channelMetadataId1 = "pandu-ut-sid1"; + string channelMetadataId2 = "pandu-ut-sid2"; + + PNConfiguration config = new PNConfiguration(new UserId("mytestuuid")) + { + PublishKey = PubnubCommon.PublishKey, + SubscribeKey = PubnubCommon.SubscribeKey, + Secure = false + }; + if (PubnubCommon.PAMServerSideRun) + { + config.SecretKey = PubnubCommon.SecretKey; + } + + pubnub = createPubNubInstance(config); + if (!string.IsNullOrEmpty(authToken)) + { + pubnub.SetAuthToken(authToken); + } + + // First create the UUID metadata + PNResult createUserResult = await pubnub.SetUuidMetadata() + .Uuid(uuidMetadataId) + .Name("pandu-ut-un") + .ExecuteAsync(); + + Assert.IsNotNull(createUserResult.Result, "UUID metadata creation failed"); + Assert.AreEqual(200, createUserResult.Status.StatusCode, "UUID metadata creation status code should be 200"); + Assert.IsFalse(createUserResult.Status.Error, "UUID metadata creation should not have errors"); + + // Create channel metadata for all channels + PNResult createChannel1Result = await pubnub.SetChannelMetadata() + .Channel(channelMetadataId1) + .Name("pandu-ut-spname") + .ExecuteAsync(); + + Assert.IsNotNull(createChannel1Result.Result, "Channel 1 metadata creation failed"); + Assert.AreEqual(200, createChannel1Result.Status.StatusCode, "Channel 1 metadata creation status code should be 200"); + + PNResult createChannel2Result = await pubnub.SetChannelMetadata() + .Channel(channelMetadataId2) + .Name("pandu-ut-spname") + .ExecuteAsync(); + + Assert.IsNotNull(createChannel2Result.Result, "Channel 2 metadata creation failed"); + Assert.AreEqual(200, createChannel2Result.Status.StatusCode, "Channel 2 metadata creation status code should be 200"); + + await Task.Delay(5000); + + // Test 1: Set Memberships + var memberships = new List + { + new PNMembership + { + Channel = channelMetadataId1, + Custom = new Dictionary { { "role", "admin" } } + }, + new PNMembership + { + Channel = channelMetadataId2, + Custom = new Dictionary { { "role", "member" } } + } + }; + + PNResult setMembershipsResult = await pubnub.SetMemberships() + .Uuid(uuidMetadataId) + .Channels(memberships) + .Include(new[] { PNMembershipField.CUSTOM, PNMembershipField.CHANNEL }) + .Limit(10) + .IncludeCount(true) + .ExecuteAsync(); + + Assert.IsNotNull(setMembershipsResult.Result, "Set memberships result should not be null"); + Assert.AreEqual(200, setMembershipsResult.Status.StatusCode, "Set memberships status code should be 200"); + Assert.IsFalse(setMembershipsResult.Status.Error, "Set memberships should not have errors"); + Assert.IsNotNull(setMembershipsResult.Result.Memberships, "Memberships list should not be null"); + Assert.AreEqual(2, setMembershipsResult.Result.Memberships.Count, "Should have 2 memberships"); + Assert.IsTrue(setMembershipsResult.Result.Memberships.Any(m => m.ChannelMetadata.Channel == channelMetadataId1), "Should contain channel 1"); + Assert.IsTrue(setMembershipsResult.Result.Memberships.Any(m => m.ChannelMetadata.Channel == channelMetadataId2), "Should contain channel 2"); + Assert.IsTrue(setMembershipsResult.Result.Memberships.Any(m => m.Custom != null && m.Custom.ContainsKey("role")), "Should have custom data"); + + await Task.Delay(4000); + + // Test 2: Get Memberships + PNResult getMembershipsResult = await pubnub.GetMemberships() + .Uuid(uuidMetadataId) + .Include(new[] { PNMembershipField.CUSTOM, PNMembershipField.CHANNEL }) + .Limit(10) + .IncludeCount(true) + .ExecuteAsync(); + + Assert.IsNotNull(getMembershipsResult.Result, "Get memberships result should not be null"); + Assert.AreEqual(200, getMembershipsResult.Status.StatusCode, "Get memberships status code should be 200"); + Assert.IsFalse(getMembershipsResult.Status.Error, "Get memberships should not have errors"); + Assert.IsNotNull(getMembershipsResult.Result.Memberships, "Memberships list should not be null"); + Assert.AreEqual(2, getMembershipsResult.Result.Memberships.Count, "Should have 2 memberships"); + Assert.IsTrue(getMembershipsResult.Result.Memberships.Any(m => m.ChannelMetadata.Channel == channelMetadataId1), "Should contain channel 1"); + Assert.IsTrue(getMembershipsResult.Result.Memberships.Any(m => m.ChannelMetadata.Channel == channelMetadataId2), "Should contain channel 2"); + + // Test 3: Manage Memberships (Update and Remove) + var updatedMemberships = new List + { + new PNMembership + { + Channel = channelMetadataId1, + Custom = new Dictionary { { "role", "superadmin" } } + } + }; + + PNResult manageMembershipsResult = await pubnub.ManageMemberships() + .Uuid(uuidMetadataId) + .Set(updatedMemberships) + .Remove(new List { channelMetadataId2 }) + .Include(new[] { PNMembershipField.CUSTOM, PNMembershipField.CHANNEL }) + .Limit(10) + .IncludeCount(true) + .ExecuteAsync(); + + Assert.IsNotNull(manageMembershipsResult.Result, "Manage memberships result should not be null"); + Assert.AreEqual(200, manageMembershipsResult.Status.StatusCode, "Manage memberships status code should be 200"); + Assert.IsFalse(manageMembershipsResult.Status.Error, "Manage memberships should not have errors"); + Assert.IsNotNull(manageMembershipsResult.Result.Memberships, "Memberships list should not be null"); + Assert.IsTrue(manageMembershipsResult.Result.Memberships.Any(m => m.ChannelMetadata.Channel == channelMetadataId1 && + m.Custom != null && m.Custom["role"].ToString() == "superadmin"), "Should have updated channel 1"); + Assert.IsFalse(manageMembershipsResult.Result.Memberships.Any(m => m.ChannelMetadata.Channel == channelMetadataId2), "Should not contain channel 2"); + + // Test 4: Remove Memberships + PNResult removeMembershipsResult = await pubnub.RemoveMemberships() + .Uuid(uuidMetadataId) + .Channels(new List { channelMetadataId1 }) + .Include(new[] { PNMembershipField.CUSTOM, PNMembershipField.CHANNEL }) + .Limit(10) + .IncludeCount(true) + .ExecuteAsync(); + + Assert.IsNotNull(removeMembershipsResult.Result, "Remove memberships result should not be null"); + Assert.AreEqual(200, removeMembershipsResult.Status.StatusCode, "Remove memberships status code should be 200"); + Assert.IsFalse(removeMembershipsResult.Status.Error, "Remove memberships should not have errors"); + Assert.IsNotNull(removeMembershipsResult.Result.Memberships, "Memberships list should not be null"); + Assert.IsFalse(removeMembershipsResult.Result.Memberships.Any(m => m.ChannelMetadata.Channel == channelMetadataId1), "Should not contain channel 1"); + + await Task.Delay(4000); + + // Verify final state with Get Memberships + PNResult finalGetResult = await pubnub.GetMemberships() + .Uuid(uuidMetadataId) + .Include(new[] { PNMembershipField.CUSTOM, PNMembershipField.CHANNEL }) + .Limit(10) + .IncludeCount(true) + .ExecuteAsync(); + + Assert.IsNotNull(finalGetResult.Result, "Final get memberships result should not be null"); + Assert.AreEqual(200, finalGetResult.Status.StatusCode, "Final get memberships status code should be 200"); + Assert.IsFalse(finalGetResult.Status.Error, "Final get memberships should not have errors"); + Assert.IsNotNull(finalGetResult.Result.Memberships, "Memberships list should not be null"); + Assert.AreEqual(0, finalGetResult.Result.Memberships.Count, "Should have no memberships"); + + // Cleanup + await pubnub.RemoveUuidMetadata().Uuid(uuidMetadataId).ExecuteAsync(); + await pubnub.RemoveChannelMetadata().Channel(channelMetadataId1).ExecuteAsync(); + await pubnub.RemoveChannelMetadata().Channel(channelMetadataId2).ExecuteAsync(); + + pubnub.Destroy(); + pubnub.PubnubUnitTest = null; + pubnub = null; + } + } } diff --git a/src/UnitTests/PubnubApi.Tests/WhenObjectUuidMetadata.cs b/src/UnitTests/PubnubApi.Tests/WhenObjectUuidMetadata.cs index 100195df1..ec04b3003 100644 --- a/src/UnitTests/PubnubApi.Tests/WhenObjectUuidMetadata.cs +++ b/src/UnitTests/PubnubApi.Tests/WhenObjectUuidMetadata.cs @@ -664,5 +664,311 @@ public static async Task ThenWithAsyncUuidMetadataUpdateDeleteShouldReturnEventI pubnub = null; } + + [Test] + public static async Task ThenUuidMetadataShouldSupportAllFields() + { + server.ClearRequests(); + + if (PubnubCommon.EnableStubTest) + { + Assert.Ignore("Ignored ThenUuidMetadataShouldSupportAllFields"); + return; + } + + bool receivedMessage = false; + string uuidMetadataId = "pandu-ut-uid"; + + PNConfiguration config = new PNConfiguration(new UserId("mytestuuid")) + { + PublishKey = PubnubCommon.PublishKey, + SubscribeKey = PubnubCommon.SubscribeKey, + Secure = false, + }; + if (PubnubCommon.PAMServerSideRun) + { + config.SecretKey = PubnubCommon.SecretKey; + } + + pubnub = createPubNubInstance(config); + if (!string.IsNullOrEmpty(authToken)) + { + pubnub.SetAuthToken(authToken); + } + + ManualResetEvent manualEvent = new ManualResetEvent(false); + + // First create with all fields + pubnub.SetUuidMetadata() + .Uuid(uuidMetadataId) + .Name("pandu-ut-un-all-fields") + .ProfileUrl("pandu-sample-profile-url") + .ExternalId("pandu-sample-ext-id") + .Email("test@test.com") + .Custom(new Dictionary() { { "color", "red" } }) + .IncludeCustom(true) + .QueryParam(new Dictionary() { { "test_param", "test_value" } }) + .Execute(new PNSetUuidMetadataResultExt((r, s) => + { + if (r != null && s.StatusCode == 200 && !s.Error) + { + pubnub.JsonPluggableLibrary.SerializeToJsonString(r); + if (uuidMetadataId == r.Uuid) + { + receivedMessage = true; + } + } + manualEvent.Set(); + })); + + manualEvent.WaitOne(manualResetEventWaitTimeout); + + await Task.Delay(4000); + + if (receivedMessage) + { + receivedMessage = false; + manualEvent = new ManualResetEvent(false); + + // Then get to verify all fields + pubnub.GetUuidMetadata() + .Uuid(uuidMetadataId) + .IncludeCustom(true) + .Execute(new PNGetUuidMetadataResultExt((r, s) => + { + if (r != null && s.StatusCode == 200 && !s.Error) + { + pubnub.JsonPluggableLibrary.SerializeToJsonString(r); + Assert.That(r.Uuid, Is.EqualTo(uuidMetadataId), "UUID should match"); + Assert.That(r.Name, Is.EqualTo("pandu-ut-un-all-fields"), "Name should match"); + Assert.That(r.ProfileUrl, Is.EqualTo("pandu-sample-profile-url"), "ProfileUrl should match"); + Assert.That(r.ExternalId, Is.EqualTo("pandu-sample-ext-id"), "ExternalId should match"); + Assert.That(r.Email, Is.EqualTo("test@test.com"), "Email should match"); + Assert.That(r.Custom, Is.Not.Null, "Custom should not be null"); + Assert.That(r.Custom.ContainsKey("color"), Is.True, "Custom should contain 'color' key"); + Assert.That(r.Custom["color"].ToString(), Is.EqualTo("red"), "Custom color value should match"); + receivedMessage = true; + } + manualEvent.Set(); + })); + } + + manualEvent.WaitOne(manualResetEventWaitTimeout); + + Assert.IsTrue(receivedMessage, "UuidMetadata with all fields test failed"); + + pubnub.Destroy(); + pubnub.PubnubUnitTest = null; + pubnub = null; + } + + [Test] + public static async Task ThenGetAllUuidMetadataShouldSupportAllFields() + { + server.ClearRequests(); + + if (PubnubCommon.EnableStubTest) + { + Assert.Ignore("Ignored ThenGetAllUuidMetadataShouldSupportAllFields"); + return; + } + + bool receivedMessage = false; + string uuidMetadataId = "pandu-ut-uid"; + + PNConfiguration config = new PNConfiguration(new UserId("mytestuuid")) + { + PublishKey = PubnubCommon.PublishKey, + SubscribeKey = PubnubCommon.SubscribeKey, + Secure = false + }; + if (PubnubCommon.PAMServerSideRun) + { + config.SecretKey = PubnubCommon.SecretKey; + } + + pubnub = createPubNubInstance(config); + if (!string.IsNullOrEmpty(authToken)) + { + pubnub.SetAuthToken(authToken); + } + + ManualResetEvent manualEvent = new ManualResetEvent(false); + + // First create with all fields + pubnub.SetUuidMetadata() + .Uuid(uuidMetadataId) + .Name("pandu-ut-un-all-fields") + .ProfileUrl("pandu-sample-profile-url") + .ExternalId("pandu-sample-ext-id") + .Email("test@test.com") + .Custom(new Dictionary() { { "color", "red" } }) + .IncludeCustom(true) + .QueryParam(new Dictionary() { { "test_param", "test_value" } }) + .Execute(new PNSetUuidMetadataResultExt((r, s) => + { + if (r != null && s.StatusCode == 200 && !s.Error) + { + pubnub.JsonPluggableLibrary.SerializeToJsonString(r); + if (uuidMetadataId == r.Uuid) + { + receivedMessage = true; + } + } + manualEvent.Set(); + })); + + manualEvent.WaitOne(manualResetEventWaitTimeout); + await Task.Delay(2000); + + if (receivedMessage) + { + receivedMessage = false; + manualEvent = new ManualResetEvent(false); + + // Then get all to verify fields + pubnub.GetAllUuidMetadata() + .IncludeCount(true) + .IncludeCustom(true) + .Filter($"id == '{uuidMetadataId}'") + .Execute(new PNGetAllUuidMetadataResultExt((r, s) => + { + if (r != null && s.StatusCode == 200 && !s.Error) + { + pubnub.JsonPluggableLibrary.SerializeToJsonString(r); + Assert.That(r.Uuids, Is.Not.Null, "Uuids list should not be null"); + Assert.That(r.Uuids.Count, Is.EqualTo(1), "Should find exactly one UUID"); + + var uuid = r.Uuids[0]; + Assert.That(uuid.Uuid, Is.EqualTo(uuidMetadataId), "UUID should match"); + Assert.That(uuid.Name, Is.EqualTo("pandu-ut-un-all-fields"), "Name should match"); + Assert.That(uuid.ProfileUrl, Is.EqualTo("pandu-sample-profile-url"), "ProfileUrl should match"); + Assert.That(uuid.ExternalId, Is.EqualTo("pandu-sample-ext-id"), "ExternalId should match"); + Assert.That(uuid.Email, Is.EqualTo("test@test.com"), "Email should match"); + Assert.That(uuid.Custom, Is.Not.Null, "Custom should not be null"); + Assert.That(uuid.Custom.ContainsKey("color"), Is.True, "Custom should contain 'color' key"); + Assert.That(uuid.Custom["color"].ToString(), Is.EqualTo("red"), "Custom color value should match"); + + Assert.That(r.TotalCount, Is.EqualTo(1), "Total count should be 1"); + receivedMessage = true; + } + manualEvent.Set(); + })); + } + + manualEvent.WaitOne(manualResetEventWaitTimeout); + + Assert.IsTrue(receivedMessage, "GetAllUuidMetadata with all fields test failed"); + + pubnub.Destroy(); + pubnub.PubnubUnitTest = null; + pubnub = null; + } + + [Test] + public static async Task ThenRemoveUuidMetadataShouldRemoveAllFields() + { + server.ClearRequests(); + + if (PubnubCommon.EnableStubTest) + { + Assert.Ignore("Ignored ThenRemoveUuidMetadataShouldRemoveAllFields"); + return; + } + + bool receivedMessage = false; + string uuidMetadataId = "pandu-ut-uid"; + + PNConfiguration config = new PNConfiguration(new UserId("mytestuuid")) + { + PublishKey = PubnubCommon.PublishKey, + SubscribeKey = PubnubCommon.SubscribeKey, + Secure = false + }; + if (PubnubCommon.PAMServerSideRun) + { + config.SecretKey = PubnubCommon.SecretKey; + } + + pubnub = createPubNubInstance(config); + if (!string.IsNullOrEmpty(authToken)) + { + pubnub.SetAuthToken(authToken); + } + + ManualResetEvent manualEvent = new ManualResetEvent(false); + + // First create with all fields + pubnub.SetUuidMetadata() + .Uuid(uuidMetadataId) + .Name("pandu-ut-un-remove") + .ProfileUrl("pandu-sample-profile-url") + .ExternalId("pandu-sample-ext-id") + .Email("test@test.com") + .Custom(new Dictionary() { { "color", "red" } }) + .IncludeCustom(true) + .Execute(new PNSetUuidMetadataResultExt((r, s) => + { + if (r != null && s.StatusCode == 200 && !s.Error) + { + pubnub.JsonPluggableLibrary.SerializeToJsonString(r); + if (uuidMetadataId == r.Uuid) + { + receivedMessage = true; + } + } + manualEvent.Set(); + })); + + manualEvent.WaitOne(manualResetEventWaitTimeout); + await Task.Delay(2000); + + if (receivedMessage) + { + receivedMessage = false; + manualEvent = new ManualResetEvent(false); + + // Then remove the metadata + pubnub.RemoveUuidMetadata() + .Uuid(uuidMetadataId) + .Execute(new PNRemoveUuidMetadataResultExt((r, s) => + { + if (r != null && s.StatusCode == 200 && !s.Error) + { + receivedMessage = true; + } + manualEvent.Set(); + })); + } + + manualEvent.WaitOne(manualResetEventWaitTimeout); + await Task.Delay(2000); + + if (receivedMessage) + { + receivedMessage = false; + manualEvent = new ManualResetEvent(false); + + // Finally verify it's gone by trying to get it + pubnub.GetUuidMetadata() + .Uuid(uuidMetadataId) + .Execute(new PNGetUuidMetadataResultExt((r, s) => + { + Assert.That(r, Is.Null, "Result should be null for removed UUID"); + Assert.That(s.StatusCode, Is.EqualTo(404), "Status code should be 404"); + Assert.That(s.Error, Is.True, "Should not indicate error"); + receivedMessage = true; + manualEvent.Set(); + })); + } + + manualEvent.WaitOne(manualResetEventWaitTimeout); + + Assert.IsTrue(receivedMessage, "UuidMetadata removal test failed"); + + pubnub.Destroy(); + pubnub.PubnubUnitTest = null; + pubnub = null; + } } } diff --git a/src/UnitTests/PubnubApi.Tests/WhenSubscribedToAChannel.cs b/src/UnitTests/PubnubApi.Tests/WhenSubscribedToAChannel.cs index 9d6c57b46..4167f888c 100644 --- a/src/UnitTests/PubnubApi.Tests/WhenSubscribedToAChannel.cs +++ b/src/UnitTests/PubnubApi.Tests/WhenSubscribedToAChannel.cs @@ -133,6 +133,62 @@ public static void ThenMissingSubscribeKeyShouldReturnException() pubnub.PubnubUnitTest = null; pubnub = null; } + + [Test] + public static async Task ThenUnsubsribeShouldSucceed() + { + var channel = "test_unsubscribe_channel"; + PNConfiguration pubnubConfiguration = new PNConfiguration(new UserId("mytestuuid")) + { + PublishKey = PubnubCommon.PublishKey, + SubscribeKey = PubnubCommon.SubscribeKey, + SecretKey = PubnubCommon.SecretKey, + EnableEventEngine = false + }; + pubnub = createPubNubInstance(pubnubConfiguration, authToken); + + var receiveReset = new ManualResetEvent(false); + var subscribedListener = new SubscribeCallbackExt( + (_, messageEvent) => + { + receiveReset.Set(); + } + , (_, _) => {} + , (_, status) => {} + ); + pubnub.AddListener(subscribedListener); + + pubnub.Subscribe().Channels(new []{channel}).WithPresence().Execute(); + await Task.Delay(3000); + + await pubnub.Publish().Channel(channel).Message("some_message").ExecuteAsync(); + + manualResetEventWaitTimeout = 310 * 1000; + var received = receiveReset.WaitOne(manualResetEventWaitTimeout); + + Assert.True(received, "Didn't receive message when subscribed"); + + var shouldNotReceiveReset = new ManualResetEvent(false); + var unsubscribedListener = new SubscribeCallbackExt( + (_, messageEvent) => + { + shouldNotReceiveReset.Set(); + } + , (_, _) => {} + , (_, status) => {} + ); + pubnub.AddListener(unsubscribedListener); + pubnub.Unsubscribe().Channels(new []{channel}).Execute(); + await Task.Delay(3000); + + await pubnub.Publish().Channel(channel).Message("some_message").ExecuteAsync(); + received = shouldNotReceiveReset.WaitOne(5000); + + Assert.False(received, "Received message even after unsubscribing"); + + pubnub.UnsubscribeAll(); + pubnub.Destroy(); + } [Test] public static void ThenComplexMessageSubscribeShouldReturnReceivedMessage() @@ -871,5 +927,95 @@ public static void ThenSubscriberShouldBeAbleToReceiveManyMessages() Assert.IsTrue(receivedMessage, "WhenSubscribedToAChannel --> ThenSubscriberShouldBeAbleToReceiveManyMessages Failed"); } + + [Test] + public static async Task ThenMultipleListenersShouldReceiveCallbacks() + { + if (PubnubCommon.EnableStubTest) + { + Assert.Ignore("Ignored ThenMultipleListenersShouldReceiveCallbacks"); + return; + } + + var firstListenerMessageEvent = new ManualResetEvent(false); + var secondListenerMessageEvent = new ManualResetEvent(false); + var firstListenerStatusEvent = new ManualResetEvent(false); + var secondListenerStatusEvent = new ManualResetEvent(false); + + PNConfiguration config = new PNConfiguration(new UserId("mytestuuid")) + { + PublishKey = PubnubCommon.PublishKey, + SubscribeKey = PubnubCommon.SubscribeKey, + SecretKey = PubnubCommon.SecretKey, + EnableEventEngine = true + }; + + pubnub = createPubNubInstance(config, authToken); + + var firstListener = new SubscribeCallbackExt( + (_, messageEvent) => + { + if (messageEvent.Message != null) + { + firstListenerMessageEvent.Set(); + } + }, + (_, _) => { }, + (_, status) => + { + if (status.StatusCode == 200 && status.Category == PNStatusCategory.PNConnectedCategory) + { + firstListenerStatusEvent.Set(); + } + } + ); + + var secondListener = new SubscribeCallbackExt( + (_, messageEvent) => + { + if (messageEvent.Message != null) + { + secondListenerMessageEvent.Set(); + } + }, + (_, _) => { }, + (_, status) => + { + if (status.StatusCode == 200 && status.Category == PNStatusCategory.PNConnectedCategory) + { + secondListenerStatusEvent.Set(); + } + } + ); + + pubnub.AddListener(firstListener); + pubnub.AddListener(secondListener); + + string channel = "test_multiple_listeners_channel"; + string message = "test message for multiple listeners"; + + pubnub.Subscribe().Channels(new[] { channel }).Execute(); + + // Wait for both listeners to receive connection status + bool firstStatusReceived = firstListenerStatusEvent.WaitOne(manualResetEventWaitTimeout); + bool secondStatusReceived = secondListenerStatusEvent.WaitOne(manualResetEventWaitTimeout); + + Assert.IsTrue(firstStatusReceived, "First listener should receive connection status"); + Assert.IsTrue(secondStatusReceived, "Second listener should receive connection status"); + + await pubnub.Publish().Channel(channel).Message(message).ExecuteAsync(); + + // Wait for both listeners to receive the message + bool firstMessageReceived = firstListenerMessageEvent.WaitOne(manualResetEventWaitTimeout); + bool secondMessageReceived = secondListenerMessageEvent.WaitOne(manualResetEventWaitTimeout); + + Assert.IsTrue(firstMessageReceived, "First listener should receive message"); + Assert.IsTrue(secondMessageReceived, "Second listener should receive message"); + + pubnub.Unsubscribe().Channels(new[] { channel }).Execute(); + pubnub.Destroy(); + pubnub.PubnubUnitTest = null; + pubnub = null; + } } } diff --git a/src/UnitTests/PubnubApi.Tests/WhenSubscribedToAChannel3.cs b/src/UnitTests/PubnubApi.Tests/WhenSubscribedToAChannel3.cs index abcf22b93..24a03b19c 100644 --- a/src/UnitTests/PubnubApi.Tests/WhenSubscribedToAChannel3.cs +++ b/src/UnitTests/PubnubApi.Tests/WhenSubscribedToAChannel3.cs @@ -24,9 +24,6 @@ public class WhenSubscribedToAChannel3 : TestHarness, IDisposable private static bool receivedGrantMessage = false; private static int manualResetEventWaitTimeout = 310 * 1000; - private static string channel = "hello_my_channel"; - private static string channel2 = "hello_my_channel_2"; - private static string[] channelsGrant = { "hello_my_channel", "hello_my_channel1", "hello_my_channel2" }; private static string authKey = "myauth"; private static string currentTestCase = ""; private static string authToken; @@ -119,6 +116,9 @@ public static async Task ThenStatusCallbackShouldKeepFiring() config.AuthKey = authKey; } + var channel = $"foo.{Guid.NewGuid()}"; + var channel2 = $"foo.{Guid.NewGuid()}"; + var firstChannel = new ManualResetEvent(false); var secondChannel = new ManualResetEvent(false); SubscribeCallbackExt eventListener = new SubscribeCallbackExt( @@ -196,6 +196,7 @@ private static void CommonSubscribeShouldReturnUnicodeMessageBasedOnParams(strin subscribeManualEvent = new ManualResetEvent(false); string expected = "{\"t\":{\"t\":\"14839022442039237\",\"r\":7},\"m\":[]}"; + var channel = $"foo.{Guid.NewGuid()}"; server.AddRequest(new Request() .WithMethod("GET") @@ -311,6 +312,7 @@ private static void CommonSubscribeReturnForwardSlashMessageBasedOnParams(string subscribeManualEvent = new ManualResetEvent(false); string expected = "{\"t\":{\"t\":\"14839022442039237\",\"r\":7},\"m\":[]}"; + var channel = $"foo.{Guid.NewGuid()}"; server.AddRequest(new Request() .WithMethod("GET") @@ -500,6 +502,7 @@ private static void CommonSubscribeShouldReturnSpecialCharMessageBasedOnParams(s subscribeManualEvent = new ManualResetEvent(false); string expected = "{\"t\":{\"t\":\"14839022442039237\",\"r\":7},\"m\":[]}"; + var channel = $"foo.{Guid.NewGuid()}"; server.AddRequest(new Request() .WithMethod("GET") @@ -644,52 +647,6 @@ public static void ThenSubscribeShouldReturnSpecialCharMessageSecretSSL() Assert.IsTrue(receivedMessage, "WhenSubscribedToAChannel --> ThenSubscribeShouldReturnSpecialCharMessageSecretSSL Failed"); } - private class UTGrantResult : PNCallback - { - public override void OnResponse(PNAccessManagerGrantResult result, PNStatus status) - { - try - { - Debug.WriteLine("PNStatus={0}", pubnub.JsonPluggableLibrary.SerializeToJsonString(status)); - - if (result != null) - { - Debug.WriteLine("PNAccessManagerGrantResult={0}", pubnub.JsonPluggableLibrary.SerializeToJsonString(result)); - if (result.Channels != null && result.Channels.Count > 0) - { - foreach (KeyValuePair> channelKP in result.Channels) - { - string channel = channelKP.Key; - if (Array.IndexOf(channelsGrant, channel) > -1) - { - var read = result.Channels[channel][authKey].ReadEnabled; - var write = result.Channels[channel][authKey].WriteEnabled; - if (read && write) - { - receivedGrantMessage = true; - } - else - { - receivedGrantMessage = false; - } - } - else - { - receivedGrantMessage = false; - break; - } - } - } - } - } - catch { /* ignore */ } - finally - { - grantManualEvent.Set(); - } - } - } - public class UTSubscribeCallback : SubscribeCallback { public override void Message(Pubnub pubnub, PNMessageResult message) diff --git a/src/UnitTests/PubnubApi.Tests/file_large.png b/src/UnitTests/PubnubApi.Tests/file_large.png new file mode 100644 index 000000000..9dd4414ff Binary files /dev/null and b/src/UnitTests/PubnubApi.Tests/file_large.png differ diff --git a/src/UnitTests/PubnubApiPCL.Tests/PubnubApiPCL.Tests.csproj b/src/UnitTests/PubnubApiPCL.Tests/PubnubApiPCL.Tests.csproj index 5becbf2b4..907211488 100644 --- a/src/UnitTests/PubnubApiPCL.Tests/PubnubApiPCL.Tests.csproj +++ b/src/UnitTests/PubnubApiPCL.Tests/PubnubApiPCL.Tests.csproj @@ -107,7 +107,7 @@ - + @@ -129,6 +129,11 @@ + + + PreserveNewest + + diff --git a/src/UnitTests/PubnubApiPCL.Tests/file_large.png b/src/UnitTests/PubnubApiPCL.Tests/file_large.png new file mode 100644 index 000000000..9dd4414ff Binary files /dev/null and b/src/UnitTests/PubnubApiPCL.Tests/file_large.png differ