AyCode.Core/AyCode.Services.Tests/SignalRs/AcSignalRClientBaseTests.cs

1159 lines
40 KiB
C#

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>(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<TestOrderItem>(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<TestGuidOrder>(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<List<TestOrderItem>>(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<TestOrderItem>(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<List<TestOrderItem>>(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<List<TestOrderItem>>(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<List<TestOrderItem>>(TestClientTags.GetAll, Array.Empty<object>());
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<TestOrderItem, TestOrderItem>(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<TestOrderItem>();
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<TestOrder, TestOrder>(TestClientTags.PostOrder, order));
await TaskHelper.WaitToAsync(() => _client.SentMessages.Count > 0, 1000, 10);
var postData = _client.LastSentMessage?.AsPostData<TestOrder>();
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<ISignalResponseMessage<string>>(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<byte>(), 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<TestOrderItem>(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<string>(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<TestOrderItem>(TestClientTags.GetById, 1);
await Task.Delay(5);
return 600;
});
var task2 = Task.Run(async () =>
{
var t = _client.GetByIdAsync<TestOrderItem>(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<int, string?>();
_client.RegisterPendingRequest(700, new SignalRRequestModel(new Action<ISignalResponseMessage<string>>(response =>
{
results[700] = response.ResponseData;
})));
_client.RegisterPendingRequest(701, new SignalRRequestModel(new Action<ISignalResponseMessage<string>>(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<object>());
Assert.AreEqual(0, idMessage.Ids.Count);
}
[TestMethod]
public async Task GetAllAsync_WithNullParams_SendsWithoutMessage()
{
_client.SetNextRequestId(203);
object[]? nullParams = null;
var task = _client.GetAllAsync<List<TestOrderItem>>(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<TestOrderItem>
{
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<string> { "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<Guid> constructor - each guid becomes separate Id
var guids = new[] { Guid.NewGuid(), Guid.NewGuid() };
var idMessage = new IdMessage((IEnumerable<Guid>)guids);
Assert.AreEqual(2, idMessage.Ids.Count);
}
[TestMethod]
public void IdMessage_WithEmptyArray_SerializesAsEmptyJsonArray()
{
var empty = Array.Empty<int>();
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<TestOrderItem>
{
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<int> { 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<string> { "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<List<TestOrderItem>>(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<List<TestOrderItem>>(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<TestGuidOrder>(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<List<TestOrderItem>>(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<List<TestOrderItem>>(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<List<TestOrderItem>>(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<TestOrderItem, TestOrderItem>(TestClientTags.Create, item));
await Task.Delay(50);
var postData = _client.LastSentMessage?.AsPostData<TestOrderItem>();
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<TestOrderItem>
{
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<TestOrder, TestOrder>(TestClientTags.PostOrder, order));
await Task.Delay(50);
var postData = _client.LastSentMessage?.AsPostData<TestOrder>();
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<TestOrder, TestOrder>(TestClientTags.PostOrder, order));
await Task.Delay(50);
var postData = _client.LastSentMessage?.AsPostData<TestOrder>();
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<TestOrderItem>
{
new() { Id = 1, ProductName = "First" },
new() { Id = 2, ProductName = "Second" }
};
_ = Task.Run(() => _client.PostDataAsync<List<TestOrderItem>, List<TestOrderItem>>(TestClientTags.Create, items));
await Task.Delay(50);
var postData = _client.LastSentMessage?.AsPostData<List<TestOrderItem>>();
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<IdMessage>(new IdMessage(42));
// Act - Serialize to MessagePack
var bytes = original.ToMessagePack(ContractlessStandardResolver.Options);
// Deserialize back
var deserialized = bytes.MessagePackTo<SignalPostJsonDataMessage<IdMessage>>(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<TestOrderItem>(order);
// Act
var bytes = original.ToMessagePack(ContractlessStandardResolver.Options);
var deserialized = bytes.MessagePackTo<SignalPostJsonDataMessage<TestOrderItem>>(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<SignalPostJsonMessage>(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>(idMessage);
// Act
var bytes = original.ToMessagePack(ContractlessStandardResolver.Options);
var deserialized = bytes.MessagePackTo<SignalPostJsonDataMessage<IdMessage>>(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<IdMessage>(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<SignalPostJsonMessage>(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<IdMessage>();
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<int, string>
var idMessage = new IdMessage(42);
var original = new SignalPostJsonDataMessage<IdMessage>(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<SignalPostJsonDataMessage<IdMessage>>(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
}