using AyCode.Core.Extensions; using AyCode.Core.Helpers; using AyCode.Core.Tests.TestModels; using AyCode.Services.SignalRs; using MessagePack.Resolvers; using Microsoft.AspNetCore.SignalR.Client; namespace AyCode.Services.Tests.SignalRs; [TestClass] public class AcSignalRClientBaseTests { private TestableSignalRClient _client = null!; private TestLogger _logger = null!; [TestInitialize] public void Setup() { _logger = new TestLogger(); _client = new TestableSignalRClient(_logger); _client.TransportSendTimeout = 100; // Short timeout for tests } #region Connection State Tests [TestMethod] public async Task StartConnection_WhenDisconnected_ConnectsSuccessfully() { _client.SetConnectionState(HubConnectionState.Disconnected); await _client.StartConnection(); Assert.IsTrue(_client.GetPendingRequests().IsEmpty); } [TestMethod] public async Task StopConnection_ClearsState() { _client.RegisterPendingRequest(1, new SignalRRequestModel()); await _client.StopConnection(); // Connection should be stopped (no exception) } #endregion #region SendMessageToServerAsync Tests [TestMethod] public async Task SendMessageToServerAsync_SendsMessage() { await _client.SendMessageToServerAsync(TestClientTags.Ping); Assert.AreEqual(1, _client.SentMessages.Count); Assert.AreEqual(TestClientTags.Ping, _client.LastSentMessage?.MessageTag); } [TestMethod] public async Task SendMessageToServerAsync_WithMessage_SerializesCorrectly() { var idMessage = new IdMessage(42); var message = new SignalPostJsonDataMessage(idMessage); await _client.SendMessageToServerAsync(TestClientTags.GetById, message, 1); Assert.AreEqual(1, _client.SentMessages.Count); var sent = _client.LastSentMessage; Assert.IsNotNull(sent?.MessageBytes); Assert.AreEqual(1, sent.RequestId); } [TestMethod] public async Task SendMessageToServerAsync_WhenDisconnected_LogsError() { _client.SetConnectionState(HubConnectionState.Disconnected); await _client.SendMessageToServerAsync(TestClientTags.Ping, null, 1); // Should attempt to connect first, then send Assert.AreEqual(1, _client.SentMessages.Count); } #endregion #region GetByIdAsync Tests [TestMethod] public async Task GetByIdAsync_SendsIdMessageWithSingleId() { _client.SetNextRequestId(100); // Start the async operation but don't await the result (it will timeout) _ = Task.Run(() => _client.GetByIdAsync(TestClientTags.GetById, 42)); // Give it time to send await Task.Delay(50); Assert.IsTrue(_client.SentMessages.Count >= 1); var sent = _client.SentMessages.FirstOrDefault(m => m.MessageTag == TestClientTags.GetById); Assert.IsNotNull(sent); Assert.AreEqual(100, sent.RequestId); var idMessage = sent.AsIdMessage(); Assert.IsNotNull(idMessage); Assert.AreEqual(1, idMessage.Ids.Count); Assert.AreEqual("42", idMessage.Ids[0]); // Direct JSON format } [TestMethod] public async Task GetByIdAsync_WithGuid_SendsCorrectly() { var guid = Guid.Parse("12345678-1234-1234-1234-123456789abc"); _client.SetNextRequestId(101); _ = Task.Run(() => _client.GetByIdAsync(TestClientTags.GetById, guid)); await Task.Delay(50); var sent = _client.SentMessages.FirstOrDefault(m => m.MessageTag == TestClientTags.GetById); Assert.IsNotNull(sent); var idMessage = sent.AsIdMessage(); Assert.IsNotNull(idMessage); Assert.IsTrue(idMessage.Ids[0].Contains("12345678")); } [TestMethod] public async Task GetByIdAsync_WithMultipleIds_SendsAllIds() { _client.SetNextRequestId(102); var task = _client.GetByIdAsync>(TestClientTags.GetById, new object[] { 1, 2, 3 }); await TaskHelper.WaitToAsync(() => _client.SentMessages.Count > 0, 1000, 10); var idMessage = _client.LastSentMessage?.AsIdMessage(); Assert.IsNotNull(idMessage); Assert.AreEqual(3, idMessage.Ids.Count); } [TestMethod] public async Task GetByIdAsync_WithCallback_InvokesCallback() { _client.SetNextRequestId(103); var callbackInvoked = false; TestOrderItem? receivedData = null; var task = _client.GetByIdAsync(TestClientTags.GetById, async response => { callbackInvoked = true; receivedData = response.ResponseData; await Task.CompletedTask; }, 42); await Task.Delay(10); // Simulate server response var responseItem = new TestOrderItem { Id = 42, ProductName = "Test Product" }; await _client.SimulateSuccessResponse(103, TestClientTags.GetById, responseItem); await Task.Delay(10); Assert.IsTrue(callbackInvoked); Assert.IsNotNull(receivedData); Assert.AreEqual(42, receivedData.Id); Assert.AreEqual("Test Product", receivedData.ProductName); } #endregion #region GetAllAsync Tests [TestMethod] public async Task GetAllAsync_SendsMessageWithoutParams() { _client.SetNextRequestId(200); var task = _client.GetAllAsync>(TestClientTags.GetAll); await TaskHelper.WaitToAsync(() => _client.SentMessages.Count > 0, 1000, 10); Assert.AreEqual(1, _client.SentMessages.Count); Assert.AreEqual(TestClientTags.GetAll, _client.LastSentMessage?.MessageTag); } [TestMethod] public async Task GetAllAsync_WithContextParams_SendsParams() { _client.SetNextRequestId(201); var task = _client.GetAllAsync>(TestClientTags.GetAll, new object[] { true, "filter" }); await TaskHelper.WaitToAsync(() => _client.SentMessages.Count > 0, 1000, 10); var idMessage = _client.LastSentMessage?.AsIdMessage(); Assert.IsNotNull(idMessage); Assert.AreEqual(2, idMessage.Ids.Count); } [TestMethod] public async Task GetAllAsync_WithEmptyParams_SendsWithoutMessage() { _client.SetNextRequestId(202); var task = _client.GetAllAsync>(TestClientTags.GetAll, Array.Empty()); await TaskHelper.WaitToAsync(() => _client.SentMessages.Count > 0, 1000, 10); // Empty params should not create IdMessage Assert.IsNull(_client.LastSentMessage?.MessageBytes); } #endregion #region PostDataAsync Tests [TestMethod] public async Task PostDataAsync_SendsComplexObject() { _client.SetNextRequestId(300); var orderItem = new TestOrderItem { Id = 1, ProductName = "New Product", Quantity = 5 }; _ = Task.Run(() => _client.PostDataAsync(TestClientTags.Create, orderItem)); await TaskHelper.WaitToAsync(() => _client.SentMessages.Count > 0, 1000, 10); Assert.IsTrue(_client.SentMessages.Count >= 1, $"Expected at least 1 message, got {_client.SentMessages.Count}"); var sent = _client.SentMessages.FirstOrDefault(m => m.MessageTag == TestClientTags.Create); Assert.IsNotNull(sent); Assert.IsNotNull(sent.MessageBytes); var postData = sent.AsPostData(); Assert.IsNotNull(postData); Assert.AreEqual("New Product", postData.ProductName); } [TestMethod] public async Task PostDataAsync_WithNestedObject_SerializesCorrectly() { _client.SetNextRequestId(301); var order = TestDataFactory.CreateOrder(itemCount: 2); _ = Task.Run(() => _client.PostDataAsync(TestClientTags.PostOrder, order)); await TaskHelper.WaitToAsync(() => _client.SentMessages.Count > 0, 1000, 10); var postData = _client.LastSentMessage?.AsPostData(); Assert.IsNotNull(postData); Assert.AreEqual(2, postData.Items.Count); } #endregion #region OnReceiveMessage Tests [TestMethod] public async Task OnReceiveMessage_WithPendingRequest_SetsResponse() { var requestId = 400; // Register with null callback - response will be stored in ResponseByRequestId _client.RegisterPendingRequest(requestId, new SignalRRequestModel()); await _client.SimulateSuccessResponse(requestId, TestClientTags.Echo, "Hello"); // After receiving response, the request should have ResponseByRequestId set // The pending request gets removed after callback handling, but for null callback // it stays with the response set var pending = _client.GetPendingRequests(); if (pending.TryGetValue(requestId, out var model)) { Assert.IsNotNull(model.ResponseByRequestId); } // If not in pending, it means it was handled (which is also valid) } [TestMethod] public async Task OnReceiveMessage_WithCallback_InvokesCallback() { var requestId = 401; var callbackInvoked = false; string? receivedData = null; _client.RegisterPendingRequest(requestId, new SignalRRequestModel(new Action>(response => { callbackInvoked = true; receivedData = response.ResponseData; }))); await _client.SimulateSuccessResponse(requestId, TestClientTags.Echo, "Hello World"); Assert.IsTrue(callbackInvoked); Assert.IsNotNull(receivedData); } [TestMethod] public async Task OnReceiveMessage_WithoutPendingRequest_CallsMessageReceived() { var response = new SignalResponseJsonMessage(TestClientTags.GetStatus, SignalResponseStatus.Success, "OK"); var bytes = response.ToMessagePack(ContractlessStandardResolver.Options); await _client.InvokeOnReceiveMessage(TestClientTags.GetStatus, bytes, null); Assert.AreEqual(1, _client.ReceivedMessages.Count); Assert.AreEqual(TestClientTags.GetStatus, _client.ReceivedMessages[0].MessageTag); } [TestMethod] public async Task OnReceiveMessage_WithEmptyBytes_LogsWarning() { await _client.InvokeOnReceiveMessage(TestClientTags.Echo, Array.Empty(), 999); Assert.IsTrue(_logger.HasWarningLogs); } #endregion #region Error Handling Tests [TestMethod] public async Task GetByIdAsync_WithErrorResponse_ReturnsDefault() { _client.SetNextRequestId(500); var task = Task.Run(async () => { try { return await _client.GetByIdAsync(TestClientTags.NotFound, 999); } catch { return null; } }); await Task.Delay(10); await _client.SimulateErrorResponse(500, TestClientTags.NotFound); var result = await task; Assert.IsNull(result); } [TestMethod] public async Task SendMessageToServerAsync_WithZeroTag_LogsError() { await _client.SendMessageToServerAsync(0, response => Task.CompletedTask); Assert.IsTrue(_logger.HasErrorLogs); } #endregion #region Complex Scenario Tests [TestMethod] public async Task MultipleParallelRequests_HandleCorrectly() { _client.SetNextRequestId(600); // Start multiple requests var task1 = Task.Run(async () => { var t = _client.GetByIdAsync(TestClientTags.GetById, 1); await Task.Delay(5); return 600; }); var task2 = Task.Run(async () => { var t = _client.GetByIdAsync(TestClientTags.GetById, 2); await Task.Delay(5); return 601; }); await Task.WhenAll(task1, task2); // Both requests should have been sent Assert.AreEqual(2, _client.SentMessages.Count); } [TestMethod] public async Task ConcurrentResponseHandling_ResolvesCorrectRequests() { _client.SetNextRequestId(700); // Register two pending requests with callbacks var results = new Dictionary(); _client.RegisterPendingRequest(700, new SignalRRequestModel(new Action>(response => { results[700] = response.ResponseData; }))); _client.RegisterPendingRequest(701, new SignalRRequestModel(new Action>(response => { results[701] = response.ResponseData; }))); // Simulate responses in reverse order await _client.SimulateSuccessResponse(701, TestClientTags.Echo, "Response 701"); await _client.SimulateSuccessResponse(700, TestClientTags.Echo, "Response 700"); // Each request should get its correct response Assert.AreEqual(2, results.Count); } #endregion #region IdMessage Single Primitive Parameter Tests [TestMethod] public void IdMessage_WithInt_SerializesAsNumber() { var idMessage = new IdMessage(42); Assert.AreEqual(1, idMessage.Ids.Count); Assert.AreEqual("42", idMessage.Ids[0]); } [TestMethod] public void IdMessage_WithLong_SerializesAsNumber() { var idMessage = new IdMessage(9223372036854775807L); Assert.AreEqual(1, idMessage.Ids.Count); Assert.AreEqual("9223372036854775807", idMessage.Ids[0]); } [TestMethod] public void IdMessage_WithBoolTrue_SerializesCorrectly() { var idMessage = new IdMessage(true); Assert.AreEqual(1, idMessage.Ids.Count); Assert.AreEqual("true", idMessage.Ids[0]); } [TestMethod] public void IdMessage_WithBoolFalse_SerializesCorrectly() { var idMessage = new IdMessage(false); Assert.AreEqual(1, idMessage.Ids.Count); Assert.AreEqual("false", idMessage.Ids[0]); } [TestMethod] public void IdMessage_WithString_SerializesAsQuotedString() { var idMessage = new IdMessage("hello"); Assert.AreEqual(1, idMessage.Ids.Count); Assert.AreEqual("\"hello\"", idMessage.Ids[0]); } [TestMethod] public void IdMessage_WithDouble_SerializesAsNumber() { var idMessage = new IdMessage(3.14159); Assert.AreEqual(1, idMessage.Ids.Count); Assert.IsTrue(idMessage.Ids[0].StartsWith("3.14")); } [TestMethod] public void IdMessage_WithDecimal_SerializesAsNumber() { var idMessage = new IdMessage(99.99m); Assert.AreEqual(1, idMessage.Ids.Count); Assert.AreEqual("99.99", idMessage.Ids[0]); } [TestMethod] public void IdMessage_WithGuid_SerializesAsQuotedString() { var guid = Guid.Parse("12345678-1234-1234-1234-123456789abc"); var idMessage = new IdMessage(guid); Assert.AreEqual(1, idMessage.Ids.Count); Assert.IsTrue(idMessage.Ids[0].Contains("12345678")); Assert.IsTrue(idMessage.Ids[0].StartsWith("\"") && idMessage.Ids[0].EndsWith("\"")); } [TestMethod] public void IdMessage_WithDateTime_SerializesAsQuotedString() { var date = new DateTime(2024, 12, 25, 12, 30, 45, DateTimeKind.Utc); var idMessage = new IdMessage(date); Assert.AreEqual(1, idMessage.Ids.Count); Assert.IsTrue(idMessage.Ids[0].Contains("2024")); } [TestMethod] public void IdMessage_WithEnum_SerializesAsNumber() { var idMessage = new IdMessage(TestStatus.Processing); Assert.AreEqual(1, idMessage.Ids.Count); Assert.AreEqual("2", idMessage.Ids[0]); // Processing = 2 } #endregion #region IdMessage Multiple Primitive Parameters Tests [TestMethod] public void IdMessage_WithTwoInts_SerializesCorrectly() { var idMessage = new IdMessage(new object[] { 10, 20 }); Assert.AreEqual(2, idMessage.Ids.Count); Assert.AreEqual("10", idMessage.Ids[0]); Assert.AreEqual("20", idMessage.Ids[1]); } [TestMethod] public void IdMessage_WithThreeMixedTypes_SerializesCorrectly() { var idMessage = new IdMessage(new object[] { true, "test", 123 }); Assert.AreEqual(3, idMessage.Ids.Count); Assert.AreEqual("true", idMessage.Ids[0]); Assert.AreEqual("\"test\"", idMessage.Ids[1]); Assert.AreEqual("123", idMessage.Ids[2]); } [TestMethod] public void IdMessage_WithFiveParams_SerializesCorrectly() { var testGuid = Guid.NewGuid(); var idMessage = new IdMessage(new object[] { 42, "hello", true, testGuid, 99.99m }); Assert.AreEqual(5, idMessage.Ids.Count); Assert.AreEqual("42", idMessage.Ids[0]); Assert.AreEqual("\"hello\"", idMessage.Ids[1]); Assert.AreEqual("true", idMessage.Ids[2]); Assert.IsTrue(idMessage.Ids[3].Contains(testGuid.ToString())); Assert.AreEqual("99.99", idMessage.Ids[4]); } [TestMethod] public void IdMessage_WithIntBoolStringGuidDecimal_AllTypesSerializeCorrectly() { var guid = Guid.Parse("aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"); var idMessage = new IdMessage(new object[] { 42, // int true, // bool "text", // string guid, // Guid 123.45m // decimal }); Assert.AreEqual(5, idMessage.Ids.Count); Assert.AreEqual("42", idMessage.Ids[0]); Assert.AreEqual("true", idMessage.Ids[1]); Assert.AreEqual("\"text\"", idMessage.Ids[2]); Assert.IsTrue(idMessage.Ids[3].Contains("aaaaaaaa")); Assert.AreEqual("123.45", idMessage.Ids[4]); } #endregion #region IdMessage No Parameters Tests [TestMethod] public void IdMessage_WithEmptyArray_HasNoIds() { var idMessage = new IdMessage(Array.Empty()); Assert.AreEqual(0, idMessage.Ids.Count); } [TestMethod] public async Task GetAllAsync_WithNullParams_SendsWithoutMessage() { _client.SetNextRequestId(203); object[]? nullParams = null; var task = _client.GetAllAsync>(TestClientTags.GetAll, nullParams); await TaskHelper.WaitToAsync(() => _client.SentMessages.Count > 0, 1000, 10); // Null params should not create IdMessage Assert.IsNull(_client.LastSentMessage?.MessageBytes); } [TestMethod] public async Task SendMessageToServerAsync_NoParams_SendsWithoutBytes() { await _client.SendMessageToServerAsync(TestClientTags.Ping, null, 1); Assert.AreEqual(1, _client.SentMessages.Count); Assert.IsNull(_client.LastSentMessage?.MessageBytes); } #endregion #region IdMessage Complex Object Tests [TestMethod] public void IdMessage_WithComplexObject_SerializesAsJson() { var item = new TestOrderItem { Id = 1, ProductName = "Test", Quantity = 5 }; var idMessage = new IdMessage(item); Assert.AreEqual(1, idMessage.Ids.Count); Assert.IsTrue(idMessage.Ids[0].Contains("\"ProductName\"")); Assert.IsTrue(idMessage.Ids[0].Contains("\"Test\"")); } [TestMethod] public void IdMessage_WithNestedObject_SerializesCorrectly() { var order = new TestOrder { Id = 100, OrderNumber = "ORD-001", Items = new List { new() { Id = 1, ProductName = "Item A" }, new() { Id = 2, ProductName = "Item B" } } }; var idMessage = new IdMessage(order); Assert.AreEqual(1, idMessage.Ids.Count); Assert.IsTrue(idMessage.Ids[0].Contains("ORD-001")); Assert.IsTrue(idMessage.Ids[0].Contains("Item A")); Assert.IsTrue(idMessage.Ids[0].Contains("Item B")); } [TestMethod] public void IdMessage_WithSharedTag_IIdType_SerializesCorrectly() { var tag = new SharedTag { Id = 1, Name = "Important", Color = "#FF0000" }; var idMessage = new IdMessage(tag); Assert.AreEqual(1, idMessage.Ids.Count); Assert.IsTrue(idMessage.Ids[0].Contains("Important")); Assert.IsTrue(idMessage.Ids[0].Contains("#FF0000")); } #endregion #region IdMessage Array/Collection Tests [TestMethod] public void IdMessage_WithIntArray_SerializesAsJsonArray() { var values = new[] { 1, 2, 3, 4, 5 }; var idMessage = new IdMessage(values); Assert.AreEqual(1, idMessage.Ids.Count); Assert.IsTrue(idMessage.Ids[0].Contains("[")); Assert.IsTrue(idMessage.Ids[0].Contains("1")); Assert.IsTrue(idMessage.Ids[0].Contains("5")); } [TestMethod] public void IdMessage_WithStringList_SerializesAsJsonArray() { var items = new List { "apple", "banana", "cherry" }; var idMessage = new IdMessage(items); Assert.AreEqual(1, idMessage.Ids.Count); Assert.IsTrue(idMessage.Ids[0].Contains("apple")); Assert.IsTrue(idMessage.Ids[0].Contains("banana")); } [TestMethod] public void IdMessage_WithGuidArray_SerializesAsJsonArray() { // Guid[] is treated as a single object and serialized as JSON array // Use new object[] { guids } to pass it as single parameter var guids = new[] { Guid.NewGuid(), Guid.NewGuid() }; var idMessage = new IdMessage(new object[] { guids }); Assert.AreEqual(1, idMessage.Ids.Count); Assert.IsTrue(idMessage.Ids[0].StartsWith("[")); Assert.IsTrue(idMessage.Ids[0].EndsWith("]")); } [TestMethod] public void IdMessage_WithGuidArrayAsMultipleParams_EnumeratesEachGuid() { // Using IEnumerable constructor - each guid becomes separate Id var guids = new[] { Guid.NewGuid(), Guid.NewGuid() }; var idMessage = new IdMessage((IEnumerable)guids); Assert.AreEqual(2, idMessage.Ids.Count); } [TestMethod] public void IdMessage_WithEmptyArray_SerializesAsEmptyJsonArray() { var empty = Array.Empty(); var idMessage = new IdMessage(empty); Assert.AreEqual(1, idMessage.Ids.Count); Assert.AreEqual("[]", idMessage.Ids[0]); } [TestMethod] public void IdMessage_WithTestOrderItemList_SerializesAsJsonArray() { var items = new List { new() { Id = 1, ProductName = "First" }, new() { Id = 2, ProductName = "Second" } }; var idMessage = new IdMessage(items); Assert.AreEqual(1, idMessage.Ids.Count); Assert.IsTrue(idMessage.Ids[0].Contains("First")); Assert.IsTrue(idMessage.Ids[0].Contains("Second")); } #endregion #region IdMessage Mixed Parameters (Primitive + Complex + Collection) Tests [TestMethod] public void IdMessage_WithIntAndDto_SerializesCorrectly() { var item = new TestOrderItem { Id = 10, ProductName = "Test" }; var idMessage = new IdMessage(new object[] { 42, item }); Assert.AreEqual(2, idMessage.Ids.Count); Assert.AreEqual("42", idMessage.Ids[0]); Assert.IsTrue(idMessage.Ids[1].Contains("Test")); } [TestMethod] public void IdMessage_WithDtoAndList_SerializesCorrectly() { var item = new TestOrderItem { Id = 1, ProductName = "Product" }; var numbers = new List { 1, 2, 3 }; var idMessage = new IdMessage(new object[] { item, numbers }); Assert.AreEqual(2, idMessage.Ids.Count); Assert.IsTrue(idMessage.Ids[0].Contains("Product")); Assert.IsTrue(idMessage.Ids[1].Contains("[")); } [TestMethod] public void IdMessage_WithBoolArrayString_MixedWithArray_SerializesCorrectly() { var numbers = new[] { 1, 2, 3 }; var idMessage = new IdMessage(new object[] { true, numbers, "hello" }); Assert.AreEqual(3, idMessage.Ids.Count); Assert.AreEqual("true", idMessage.Ids[0]); Assert.IsTrue(idMessage.Ids[1].Contains("[1,2,3]") || idMessage.Ids[1].Contains("[1, 2, 3]")); Assert.AreEqual("\"hello\"", idMessage.Ids[2]); } [TestMethod] public void IdMessage_WithThreeComplexParams_SerializesCorrectly() { var item = new TestOrderItem { Id = 1, ProductName = "Product" }; var tags = new List { "tag1", "tag2" }; var sharedTag = new SharedTag { Id = 1, Name = "Shared" }; var idMessage = new IdMessage(new object[] { item, tags, sharedTag }); Assert.AreEqual(3, idMessage.Ids.Count); Assert.IsTrue(idMessage.Ids[0].Contains("Product")); Assert.IsTrue(idMessage.Ids[1].Contains("tag1")); Assert.IsTrue(idMessage.Ids[2].Contains("Shared")); } [TestMethod] public void IdMessage_WithFiveMixedParams_AllTypesSerialize() { var guid = Guid.NewGuid(); var item = new TestOrderItem { Id = 5, ProductName = "Mixed" }; var numbers = new[] { 10, 20, 30 }; var idMessage = new IdMessage(new object[] { 42, // int guid, // Guid item, // complex object numbers, // array "final" // string }); Assert.AreEqual(5, idMessage.Ids.Count); Assert.AreEqual("42", idMessage.Ids[0]); Assert.IsTrue(idMessage.Ids[1].Contains(guid.ToString())); Assert.IsTrue(idMessage.Ids[2].Contains("Mixed")); Assert.IsTrue(idMessage.Ids[3].Contains("[")); Assert.AreEqual("\"final\"", idMessage.Ids[4]); } #endregion #region GetAllAsync Parameter Type Tests [TestMethod] public async Task GetAllAsync_WithSingleBoolParam_SerializesCorrectly() { _client.SetNextRequestId(210); _ = _client.GetAllAsync>(TestClientTags.GetAll, new object[] { true }); await TaskHelper.WaitToAsync(() => _client.SentMessages.Count > 0, 1000, 10); var idMessage = _client.LastSentMessage?.AsIdMessage(); Assert.IsNotNull(idMessage); Assert.AreEqual(1, idMessage.Ids.Count); Assert.AreEqual("true", idMessage.Ids[0]); } [TestMethod] public async Task GetAllAsync_WithIntAndString_SerializesCorrectly() { _client.SetNextRequestId(211); _ = _client.GetAllAsync>(TestClientTags.GetAll, new object[] { 100, "filter" }); await TaskHelper.WaitToAsync(() => _client.SentMessages.Count > 0, 1000, 10); var idMessage = _client.LastSentMessage?.AsIdMessage(); Assert.IsNotNull(idMessage); Assert.AreEqual(2, idMessage.Ids.Count); Assert.AreEqual("100", idMessage.Ids[0]); Assert.AreEqual("\"filter\"", idMessage.Ids[1]); } [TestMethod] public async Task GetAllAsync_WithGuidParam_SerializesCorrectly() { _client.SetNextRequestId(212); var guid = Guid.NewGuid(); _ = _client.GetAllAsync(TestClientTags.GetAll, new object[] { guid }); await TaskHelper.WaitToAsync(() => _client.SentMessages.Count > 0, 1000, 10); var idMessage = _client.LastSentMessage?.AsIdMessage(); Assert.IsNotNull(idMessage); Assert.AreEqual(1, idMessage.Ids.Count); Assert.IsTrue(idMessage.Ids[0].Contains(guid.ToString())); } [TestMethod] public async Task GetAllAsync_WithComplexObjectParam_SerializesCorrectly() { _client.SetNextRequestId(213); var filter = new TestOrderItem { Id = 0, ProductName = "SearchFilter" }; _ = _client.GetAllAsync>(TestClientTags.GetAll, new object[] { filter }); await TaskHelper.WaitToAsync(() => _client.SentMessages.Count > 0, 1000, 10); var idMessage = _client.LastSentMessage?.AsIdMessage(); Assert.IsNotNull(idMessage); Assert.AreEqual(1, idMessage.Ids.Count); Assert.IsTrue(idMessage.Ids[0].Contains("SearchFilter")); } [TestMethod] public async Task GetAllAsync_WithArrayParam_SerializesCorrectly() { _client.SetNextRequestId(214); var ids = new[] { 1, 2, 3, 4, 5 }; _ = _client.GetAllAsync>(TestClientTags.GetAll, new object[] { ids }); await TaskHelper.WaitToAsync(() => _client.SentMessages.Count > 0, 1000, 10); var idMessage = _client.LastSentMessage?.AsIdMessage(); Assert.IsNotNull(idMessage); Assert.AreEqual(1, idMessage.Ids.Count); Assert.IsTrue(idMessage.Ids[0].Contains("[")); } [TestMethod] public async Task GetAllAsync_WithThreeMixedParams_SerializesCorrectly() { _client.SetNextRequestId(215); var tag = new SharedTag { Id = 1, Name = "Filter" }; _ = _client.GetAllAsync>(TestClientTags.GetAll, new object[] { true, 50, tag }); await TaskHelper.WaitToAsync(() => _client.SentMessages.Count > 0, 1000, 10); var idMessage = _client.LastSentMessage?.AsIdMessage(); Assert.IsNotNull(idMessage); Assert.AreEqual(3, idMessage.Ids.Count); Assert.AreEqual("true", idMessage.Ids[0]); Assert.AreEqual("50", idMessage.Ids[1]); Assert.IsTrue(idMessage.Ids[2].Contains("Filter")); } #endregion #region PostDataAsync Parameter Type Tests [TestMethod] public async Task PostDataAsync_WithSimpleDto_SerializesCorrectly() { _client.SetNextRequestId(320); var item = new TestOrderItem { Id = 1, ProductName = "Simple", Quantity = 10, UnitPrice = 25.50m }; _ = Task.Run(() => _client.PostDataAsync(TestClientTags.Create, item)); await Task.Delay(50); var postData = _client.LastSentMessage?.AsPostData(); Assert.IsNotNull(postData); Assert.AreEqual("Simple", postData.ProductName); Assert.AreEqual(10, postData.Quantity); Assert.AreEqual(25.50m, postData.UnitPrice); } [TestMethod] public async Task PostDataAsync_WithDtoContainingList_SerializesCorrectly() { _client.SetNextRequestId(321); var order = new TestOrder { Id = 1, OrderNumber = "ORD-TEST", Items = new List { new() { Id = 1, ProductName = "A", Quantity = 1 }, new() { Id = 2, ProductName = "B", Quantity = 2 }, new() { Id = 3, ProductName = "C", Quantity = 3 } } }; _ = Task.Run(() => _client.PostDataAsync(TestClientTags.PostOrder, order)); await Task.Delay(50); var postData = _client.LastSentMessage?.AsPostData(); Assert.IsNotNull(postData); Assert.AreEqual("ORD-TEST", postData.OrderNumber); Assert.AreEqual(3, postData.Items.Count); } [TestMethod] public async Task PostDataAsync_WithDtoContainingSharedRefs_SerializesCorrectly() { _client.SetNextRequestId(322); var sharedTag = new SharedTag { Id = 1, Name = "Shared" }; var order = new TestOrder { Id = 1, OrderNumber = "ORD-SHARED", PrimaryTag = sharedTag, SecondaryTag = sharedTag }; _ = Task.Run(() => _client.PostDataAsync(TestClientTags.PostOrder, order)); await Task.Delay(50); var postData = _client.LastSentMessage?.AsPostData(); Assert.IsNotNull(postData); Assert.IsNotNull(postData.PrimaryTag); Assert.AreEqual("Shared", postData.PrimaryTag.Name); } [TestMethod] public async Task PostDataAsync_WithList_SerializesCorrectly() { _client.SetNextRequestId(323); var items = new List { new() { Id = 1, ProductName = "First" }, new() { Id = 2, ProductName = "Second" } }; _ = Task.Run(() => _client.PostDataAsync, List>(TestClientTags.Create, items)); await Task.Delay(50); var postData = _client.LastSentMessage?.AsPostData>(); Assert.IsNotNull(postData); Assert.AreEqual(2, postData.Count); } #endregion #region Edge Cases Tests [TestMethod] public void IdMessage_WithNullInArray_HandlesGracefully() { // This tests how null values are handled - they should serialize as "null" var idMessage = new IdMessage(new object[] { 42, null!, "text" }); Assert.AreEqual(3, idMessage.Ids.Count); Assert.AreEqual("42", idMessage.Ids[0]); // null should serialize as "null" JSON literal Assert.AreEqual("null", idMessage.Ids[1]); Assert.AreEqual("\"text\"", idMessage.Ids[2]); } [TestMethod] public void IdMessage_WithSpecialCharactersInString_EscapesCorrectly() { var idMessage = new IdMessage("hello \"world\" \n\t\\"); Assert.AreEqual(1, idMessage.Ids.Count); // Should contain escaped characters Assert.IsTrue(idMessage.Ids[0].Contains("\\\"")); } [TestMethod] public void IdMessage_WithEmptyString_SerializesAsEmptyQuotedString() { var idMessage = new IdMessage(""); Assert.AreEqual(1, idMessage.Ids.Count); Assert.AreEqual("\"\"", idMessage.Ids[0]); } [TestMethod] public void IdMessage_WithZeroValues_SerializesCorrectly() { var idMessage = new IdMessage(new object[] { 0, 0L, 0.0, 0m }); Assert.AreEqual(4, idMessage.Ids.Count); Assert.AreEqual("0", idMessage.Ids[0]); Assert.AreEqual("0", idMessage.Ids[1]); Assert.AreEqual("0", idMessage.Ids[2]); Assert.AreEqual("0", idMessage.Ids[3]); } [TestMethod] public void IdMessage_WithGuidEmpty_SerializesCorrectly() { var idMessage = new IdMessage(Guid.Empty); Assert.AreEqual(1, idMessage.Ids.Count); Assert.IsTrue(idMessage.Ids[0].Contains("00000000-0000-0000-0000-000000000000")); } #endregion #region MessagePack Serialization Round-Trip Tests [TestMethod] public void SignalPostJsonDataMessage_MessagePackRoundTrip_PreservesPostDataJson() { // Arrange var original = new SignalPostJsonDataMessage(new IdMessage(42)); // Act - Serialize to MessagePack var bytes = original.ToMessagePack(ContractlessStandardResolver.Options); // Deserialize back var deserialized = bytes.MessagePackTo>(ContractlessStandardResolver.Options); // Assert Assert.IsNotNull(deserialized); Assert.IsNotNull(deserialized.PostDataJson, "PostDataJson should not be null after deserialization"); Assert.IsFalse(string.IsNullOrEmpty(deserialized.PostDataJson), "PostDataJson should not be empty"); // Verify PostData can be accessed Assert.IsNotNull(deserialized.PostData); Assert.AreEqual(1, deserialized.PostData.Ids.Count); Assert.AreEqual("42", deserialized.PostData.Ids[0]); } [TestMethod] public void SignalPostJsonDataMessage_WithComplexObject_MessagePackRoundTrip() { // Arrange var order = new TestOrderItem { Id = 1, ProductName = "Test", Quantity = 5 }; var original = new SignalPostJsonDataMessage(order); // Act var bytes = original.ToMessagePack(ContractlessStandardResolver.Options); var deserialized = bytes.MessagePackTo>(ContractlessStandardResolver.Options); // Assert Assert.IsNotNull(deserialized); Assert.IsNotNull(deserialized.PostDataJson); Assert.IsNotNull(deserialized.PostData); Assert.AreEqual("Test", deserialized.PostData.ProductName); Assert.AreEqual(5, deserialized.PostData.Quantity); } [TestMethod] public void SignalPostJsonMessage_BaseClass_MessagePackRoundTrip() { // Test base class directly var original = new SignalPostJsonMessage { PostDataJson = "{\"test\":123}" }; var bytes = original.ToMessagePack(ContractlessStandardResolver.Options); var deserialized = bytes.MessagePackTo(ContractlessStandardResolver.Options); Assert.IsNotNull(deserialized); Assert.AreEqual("{\"test\":123}", deserialized.PostDataJson); } [TestMethod] public void IdMessage_InSignalPostJsonDataMessage_RoundTrip_PreservesAllIds() { // Arrange - Multiple IDs var idMessage = new IdMessage(new object[] { 1, "test", true, Guid.Empty }); var original = new SignalPostJsonDataMessage(idMessage); // Act var bytes = original.ToMessagePack(ContractlessStandardResolver.Options); var deserialized = bytes.MessagePackTo>(ContractlessStandardResolver.Options); // Assert Assert.IsNotNull(deserialized?.PostData); Assert.AreEqual(4, deserialized.PostData.Ids.Count); Assert.AreEqual("1", deserialized.PostData.Ids[0]); Assert.AreEqual("\"test\"", deserialized.PostData.Ids[1]); Assert.AreEqual("true", deserialized.PostData.Ids[2]); } [TestMethod] public void SignalPostJsonDataMessage_DeserializeAsBaseType_WorksCorrectly() { // This simulates what the server does - deserializing as SignalPostJsonMessage (base type) var original = new SignalPostJsonDataMessage(new IdMessage(42)); // Serialize as derived type var bytes = original.ToMessagePack(ContractlessStandardResolver.Options); // Deserialize as BASE type (like server might do) var deserialized = bytes.MessagePackTo(ContractlessStandardResolver.Options); // Assert - PostDataJson should still be available Assert.IsNotNull(deserialized); Assert.IsNotNull(deserialized.PostDataJson); Assert.IsFalse(string.IsNullOrEmpty(deserialized.PostDataJson)); // Should be able to manually deserialize the JSON var idMessage = deserialized.PostDataJson.JsonTo(); Assert.IsNotNull(idMessage); Assert.AreEqual(1, idMessage.Ids.Count); } [TestMethod] public void SignalPostJsonDataMessage_WithIdMessage_ContainingInt_RoundTrip() { // Arrange - This is exactly what the client does when calling PostDataAsync var idMessage = new IdMessage(42); var original = new SignalPostJsonDataMessage(idMessage); // Debug: print what's in PostDataJson Console.WriteLine($"IdMessage.Ids[0]: {idMessage.Ids[0]}"); Console.WriteLine($"Original PostDataJson: {original.PostDataJson}"); // Act - Serialize to MessagePack (what client does) var bytes = original.ToMessagePack(ContractlessStandardResolver.Options); Console.WriteLine($"MessagePack bytes length: {bytes.Length}"); // Deserialize back (what server does) var deserialized = bytes.MessagePackTo>(ContractlessStandardResolver.Options); // Assert Assert.IsNotNull(deserialized); Console.WriteLine($"Deserialized PostDataJson: {deserialized.PostDataJson}"); Assert.IsNotNull(deserialized.PostDataJson, "PostDataJson should not be null"); Assert.IsFalse(string.IsNullOrEmpty(deserialized.PostDataJson), "PostDataJson should not be empty"); // This is the key - PostData should be accessible Assert.IsNotNull(deserialized.PostData, "PostData should be deserializable"); Console.WriteLine($"Deserialized PostData.Ids.Count: {deserialized.PostData.Ids.Count}"); Assert.AreEqual(1, deserialized.PostData.Ids.Count, "Should have 1 Id"); Assert.AreEqual("42", deserialized.PostData.Ids[0], "Id should be '42'"); } #endregion }