using AyCode.Core.Extensions;
using AyCode.Core.Serializers.Binaries;
using AyCode.Core.Serializers.Jsons;
using AyCode.Core.Tests.TestModels;
using AyCode.Services.Server.SignalRs;
using AyCode.Services.SignalRs;
using MessagePack.Resolvers;
using AyCode.Core.Tests.Serialization;
namespace AyCode.Services.Server.Tests.SignalRs;
///
/// Base class for SignalR client-to-hub communication tests.
/// Tests the full round-trip: Client -> Server -> Service -> Response -> Client
/// Derived classes specify the serializer type (JSON or Binary).
///
public abstract class SignalRClientToHubTestBase
{
protected abstract AcSerializerOptions SerializerOption { get; }
protected TestLogger _logger = null!;
protected TestableSignalRClient2 _client = null!;
protected TestableSignalRHub2 _hub = null!;
protected TestSignalRService2 _service = null!;
[TestInitialize]
public void Setup()
{
_logger = new TestLogger();
_hub = new TestableSignalRHub2();
_service = new TestSignalRService2();
_client = new TestableSignalRClient2(_hub, _logger);
_hub.SetSerializerType(SerializerOption);
_hub.RegisterService(_service, _client);
}
#region Primitive Parameter Tests
[TestMethod]
[DataRow(42)]
[DataRow(0)]
[DataRow(-100)]
[DataRow(int.MaxValue)]
public async Task Post_SingleInt_ReturnsStringRepresentation(int value)
{
var result = await _client.PostDataAsync(TestSignalRTags.SingleIntParam, value);
Assert.AreEqual(value.ToString(), result);
}
[TestMethod]
[DataRow(10, 20, 30)]
[DataRow(0, 0, 0)]
[DataRow(-5, 10, 5)]
public async Task Post_TwoInts_ReturnsSum(int a, int b, int expectedSum)
{
var result = await _client.PostAsync(TestSignalRTags.TwoIntParams, [a, b]);
Assert.AreEqual(expectedSum, result);
}
[TestMethod]
[DataRow(true)]
[DataRow(false)]
public async Task Post_Bool_ReturnsSameValue(bool value)
{
var result = await _client.PostDataAsync(TestSignalRTags.BoolParam, value);
Assert.AreEqual(value, result);
}
[TestMethod]
[DataRow("Hello")]
[DataRow("")]
[DataRow("Special chars: <>\"'&")]
public async Task Post_String_ReturnsEcho(string text)
{
var result = await _client.PostDataAsync(TestSignalRTags.StringParam, text);
Assert.AreEqual($"Echo: {text}", result);
}
[TestMethod]
public async Task Post_Guid_ReturnsSameValue()
{
var guid = Guid.NewGuid();
var result = await _client.PostDataAsync(TestSignalRTags.GuidParam, guid);
Assert.AreEqual(guid, result);
}
[TestMethod]
public async Task Post_GuidEmpty_ReturnsSameValue()
{
var result = await _client.PostDataAsync(TestSignalRTags.GuidParam, Guid.Empty);
Assert.AreEqual(Guid.Empty, result);
}
[TestMethod]
[DataRow(TestStatus.Pending, TestStatus.Pending)]
[DataRow(TestStatus.Processing, TestStatus.Processing)]
[DataRow(TestStatus.Completed, TestStatus.Completed)]
public async Task Post_Enum_ReturnsSameValue(TestStatus input, TestStatus expected)
{
var result = await _client.PostDataAsync(TestSignalRTags.EnumParam, input);
Assert.AreEqual(expected, result);
}
[TestMethod]
public async Task Get_NoParams_ReturnsOK()
{
var result = await _client.GetAllAsync(TestSignalRTags.NoParams);
Assert.AreEqual("OK", result);
}
[TestMethod]
public async Task Post_MultipleTypes_ReturnsConcatenated()
{
var result = await _client.PostAsync(TestSignalRTags.MultipleTypesParams, [true, "test", 42]);
Assert.AreEqual("True-test-42", result);
}
[TestMethod]
[DataRow(123.45)]
[DataRow(0.0)]
[DataRow(-99.99)]
public async Task Post_Decimal_ReturnsDoubled(double inputDouble)
{
var input = (decimal)inputDouble;
var result = await _client.PostDataAsync(TestSignalRTags.DecimalParam, input);
Assert.AreEqual(input * 2, result);
}
[TestMethod]
public async Task Post_DateTime_ReturnsSameValue()
{
var dateTime = new DateTime(2024, 6, 15, 10, 30, 45, DateTimeKind.Utc);
var result = await _client.PostDataAsync(TestSignalRTags.DateTimeParam, dateTime);
Assert.AreEqual(dateTime, result);
}
[TestMethod]
[DataRow(3.14159)]
[DataRow(0.0)]
[DataRow(-1.5)]
public async Task Post_Double_ReturnsSameValue(double value)
{
var result = await _client.PostDataAsync(TestSignalRTags.DoubleParam, value);
Assert.AreEqual(value, result, 0.0001);
}
[TestMethod]
[DataRow(9223372036854775807L)]
[DataRow(0L)]
[DataRow(-1L)]
public async Task Post_Long_ReturnsSameValue(long value)
{
var result = await _client.PostDataAsync(TestSignalRTags.LongParam, value);
Assert.AreEqual(value, result);
}
#endregion
#region Complex Object Tests
[TestMethod]
public async Task Post_TestOrderItem_ReturnsProcessedItem()
{
var item = new TestOrderItem { Id = 1, ProductName = "Widget", Quantity = 5, UnitPrice = 10.50m };
var result = await _client.PostDataAsync(TestSignalRTags.TestOrderItemParam, item);
Assert.IsNotNull(result);
Assert.AreEqual(1, result.Id);
Assert.AreEqual("Processed: Widget", result.ProductName);
Assert.AreEqual(item.Quantity * 2, result.Quantity);
Assert.AreEqual(item.UnitPrice * 2, result.UnitPrice);
}
[TestMethod]
public async Task Post_TestOrder_ReturnsSameOrder()
{
var order = TestDataFactory.CreateOrder(itemCount: 2);
var result = await _client.PostDataAsync(TestSignalRTags.TestOrderParam, order);
Assert.IsNotNull(result);
Assert.AreEqual(order.Id, result.Id);
Assert.AreEqual(order.OrderNumber, result.OrderNumber);
Assert.AreEqual(2, result.Items.Count);
}
[TestMethod]
public async Task Post_SharedTag_ReturnsSameTag()
{
var tag = new SharedTag { Id = 1, Name = "Important", Color = "#FF0000" };
var result = await _client.PostDataAsync(TestSignalRTags.SharedTagParam, tag);
Assert.IsNotNull(result);
Assert.AreEqual(1, result.Id);
Assert.AreEqual("Important", result.Name);
Assert.AreEqual("#FF0000", result.Color);
}
#endregion
#region Collection Parameter Tests
[TestMethod]
public async Task Post_IntArray_ReturnsDoubledValues()
{
var input = new[] { 1, 2, 3, 4, 5 };
var result = await _client.PostDataAsync(TestSignalRTags.IntArrayParam, input);
Assert.IsNotNull(result);
CollectionAssert.AreEqual(new[] { 2, 4, 6, 8, 10 }, result);
}
[TestMethod]
public async Task Post_GuidArray_ReturnsSameValues()
{
var guids = new[] { Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid() };
var result = await _client.PostDataAsync(TestSignalRTags.GuidArrayParam, guids);
Assert.IsNotNull(result);
CollectionAssert.AreEqual(guids, result);
}
[TestMethod]
public async Task Post_StringList_ReturnsUppercased()
{
var input = new List { "apple", "banana", "cherry" };
var result = await _client.PostDataAsync, List>(TestSignalRTags.StringListParam, input);
Assert.IsNotNull(result);
CollectionAssert.AreEqual(new List { "APPLE", "BANANA", "CHERRY" }, result);
}
[TestMethod]
public async Task Post_TestOrderItemList_ReturnsSameItems()
{
var items = new List
{
new() { Id = 1, ProductName = "Item1" },
new() { Id = 2, ProductName = "Item2" }
};
var result = await _client.PostDataAsync, List>(TestSignalRTags.TestOrderItemListParam, items);
Assert.IsNotNull(result);
Assert.AreEqual(2, result.Count);
Assert.AreEqual("Item1", result[0].ProductName);
Assert.AreEqual("Item2", result[1].ProductName);
}
[TestMethod]
public async Task Post_IntList_ReturnsDoubledValues()
{
var input = new List { 10, 20, 30 };
var result = await _client.PostDataAsync, List>(TestSignalRTags.IntListParam, input);
Assert.IsNotNull(result);
CollectionAssert.AreEqual(new List { 20, 40, 60 }, result);
}
[TestMethod]
public async Task Post_BoolArray_ReturnsSameValues()
{
var input = new[] { true, false, true, false };
var result = await _client.PostDataAsync(TestSignalRTags.BoolArrayParam, input);
Assert.IsNotNull(result);
CollectionAssert.AreEqual(input, result);
}
[TestMethod]
public async Task Post_MixedWithArray_ReturnsFormattedString()
{
var numbers = new[] { 1, 2, 3 };
var result = await _client.PostAsync(TestSignalRTags.MixedWithArrayParam, [true, numbers, "end"]);
Assert.AreEqual("True-[1,2,3]-end", result);
}
[TestMethod]
public async Task Post_NestedList_ReturnsSameStructure()
{
var nested = new List>
{
new() { 1, 2, 3 },
new() { 4, 5 },
new() { 6 }
};
var result = await _client.PostDataAsync>, List>>(TestSignalRTags.NestedListParam, nested);
Assert.IsNotNull(result);
Assert.AreEqual(3, result.Count);
CollectionAssert.AreEqual(new List { 1, 2, 3 }, result[0]);
CollectionAssert.AreEqual(new List { 4, 5 }, result[1]);
CollectionAssert.AreEqual(new List { 6 }, result[2]);
}
#endregion
#region Extended Array Tests
[TestMethod]
public async Task Post_LongArray_ReturnsSameValues()
{
var input = new[] { 1L, 2L, long.MaxValue };
var result = await _client.PostDataAsync(TestSignalRTags.LongArrayParam, input);
Assert.IsNotNull(result);
CollectionAssert.AreEqual(input, result);
}
[TestMethod]
public async Task GetAll_WithEmptyArrayContextParam_ReturnsResult()
{
var result = await _client.GetAllAsync(TestSignalRTags.IntArrayParam, [Array.Empty()]);
Assert.IsNotNull(result, "Result should not be null");
Assert.AreEqual(0, result.Length, "Empty array should return empty array");
}
[TestMethod]
public async Task GetAll_WithArrayContextParam_ReturnsDoubledValues()
{
var input = new[] { 1, 2, 3 };
var result = await _client.GetAllAsync(TestSignalRTags.IntArrayParam, [input]);
Assert.IsNotNull(result, "Result should not be null");
CollectionAssert.AreEqual(new[] { 2, 4, 6 }, result);
}
[TestMethod]
public async Task Post_DecimalArray_ReturnsSameValues()
{
var input = new[] { 1.1m, 2.2m, 3.3m };
var result = await _client.PostDataAsync(TestSignalRTags.DecimalArrayParam, input);
Assert.IsNotNull(result);
CollectionAssert.AreEqual(input, result);
}
[TestMethod]
public async Task Post_DateTimeArray_ReturnsSameValues()
{
var input = new[]
{
new DateTime(2024, 1, 1, 0, 0, 0, DateTimeKind.Utc),
new DateTime(2024, 6, 15, 12, 30, 0, DateTimeKind.Utc)
};
var result = await _client.PostDataAsync(TestSignalRTags.DateTimeArrayParam, input);
Assert.IsNotNull(result);
Assert.AreEqual(2, result.Length);
Assert.AreEqual(input[0], result[0]);
Assert.AreEqual(input[1], result[1]);
}
[TestMethod]
public async Task Post_EnumArray_ReturnsSameValues()
{
var input = new[] { TestStatus.Pending, TestStatus.Completed, TestStatus.Processing };
var result = await _client.PostDataAsync(TestSignalRTags.EnumArrayParam, input);
Assert.IsNotNull(result);
CollectionAssert.AreEqual(input, result);
}
[TestMethod]
public async Task Post_DoubleArray_ReturnsSameValues()
{
var input = new[] { 1.1, 2.2, 3.3 };
var result = await _client.PostDataAsync(TestSignalRTags.DoubleArrayParam, input);
Assert.IsNotNull(result);
Assert.AreEqual(3, result.Length);
Assert.AreEqual(1.1, result[0], 0.001);
Assert.AreEqual(2.2, result[1], 0.001);
Assert.AreEqual(3.3, result[2], 0.001);
}
[TestMethod]
public async Task Post_SharedTagArray_ReturnsSameValues()
{
var input = new[]
{
new SharedTag { Id = 1, Name = "Tag1", Color = "#FF0000" },
new SharedTag { Id = 2, Name = "Tag2", Color = "#00FF00" }
};
var result = await _client.PostDataAsync(TestSignalRTags.SharedTagArrayParam, input);
Assert.IsNotNull(result);
Assert.AreEqual(2, result.Length);
Assert.AreEqual("Tag1", result[0].Name);
Assert.AreEqual("Tag2", result[1].Name);
}
[TestMethod]
public async Task Post_Dictionary_ReturnsSameValues()
{
var input = new Dictionary
{
{ "one", 1 },
{ "two", 2 },
{ "three", 3 }
};
var result = await _client.PostDataAsync, Dictionary>(TestSignalRTags.DictionaryParam, input);
Assert.IsNotNull(result);
Assert.AreEqual(3, result.Count);
Assert.AreEqual(1, result["one"]);
Assert.AreEqual(2, result["two"]);
Assert.AreEqual(3, result["three"]);
}
#endregion
#region Mixed Parameter Tests
[TestMethod]
public async Task Post_IntAndDto_ReturnsFormattedString()
{
var item = new TestOrderItem { Id = 1, ProductName = "Widget" };
var result = await _client.PostAsync(TestSignalRTags.IntAndDtoParam, [42, item]);
Assert.AreEqual("42-Widget", result);
}
[TestMethod]
public async Task Post_DtoAndList_ReturnsFormattedString()
{
var item = new TestOrderItem { Id = 1, ProductName = "Product" };
var numbers = new List { 1, 2, 3 };
var result = await _client.PostAsync(TestSignalRTags.DtoAndListParam, [item, numbers]);
Assert.AreEqual("Product-[1,2,3]", result);
}
[TestMethod]
public async Task Post_ThreeComplexParams_ReturnsFormattedString()
{
var item = new TestOrderItem { Id = 1, ProductName = "Item" };
var tags = new List { "tag1", "tag2", "tag3" };
var sharedTag = new SharedTag { Id = 1, Name = "Shared" };
var result = await _client.PostAsync(TestSignalRTags.ThreeComplexParams, [item, tags, sharedTag]);
Assert.AreEqual("Item-3-Shared", result);
}
[TestMethod]
public async Task Post_FiveParams_ReturnsFormattedString()
{
var guid = Guid.Parse("12345678-1234-1234-1234-123456789abc");
var result = await _client.PostAsync(TestSignalRTags.FiveParams, [42, "hello", true, guid, 99.99m]);
Assert.IsNotNull(result);
Assert.AreEqual($"42-hello-True-{guid}-99.99", result);
}
[TestMethod]
public async Task GetByIdAsync_FiveParams_ReturnsFormattedString()
{
var guid = Guid.NewGuid();
var result = await _client.GetByIdAsync(TestSignalRTags.FiveParams, [1, "text", true, guid, 99.99m]);
Assert.IsNotNull(result);
Assert.AreEqual($"1-text-True-{guid}-99.99", result);
}
#endregion
#region Error Handling Tests
[TestMethod]
public async Task Post_ThrowsException_ReturnsError()
{
var result = await _client.GetAllAsync(TestSignalRTags.ThrowsException);
Assert.IsNull(result);
Assert.IsTrue(_logger.HasErrorLogs);
}
#endregion
#region Async Task Method Tests
[TestMethod]
public async Task Async_TestOrderItem_ReturnsProcessedItem()
{
var item = new TestOrderItem { Id = 1, ProductName = "Widget", Quantity = 5, UnitPrice = 10.50m };
var result = await _client.PostDataAsync(TestSignalRTags.AsyncTestOrderItemParam, item);
Assert.IsNotNull(result);
Assert.AreEqual(1, result.Id);
Assert.AreEqual("Async: Widget", result.ProductName);
Assert.AreEqual(item.Quantity * 3, result.Quantity);
Assert.AreEqual(item.UnitPrice * 3, result.UnitPrice);
}
[TestMethod]
public async Task Async_String_ReturnsProcessedString()
{
var result = await _client.PostDataAsync(TestSignalRTags.AsyncStringParam, "TestInput");
Assert.IsNotNull(result);
Assert.AreEqual("Async: TestInput", result);
}
[TestMethod]
public async Task Async_NoParams_ReturnsAsyncOK()
{
var result = await _client.GetAllAsync(TestSignalRTags.AsyncNoParams);
Assert.IsNotNull(result);
Assert.AreEqual("AsyncOK", result);
}
[TestMethod]
public async Task Async_Int_ReturnsDoubledValue()
{
var result = await _client.PostDataAsync(TestSignalRTags.AsyncIntParam, 42);
Assert.AreEqual(84, result);
}
#endregion
#region Round-Trip Integrity Tests
[TestMethod]
public async Task RoundTrip_ComplexObject_PreservesAllProperties()
{
var item = new TestOrderItem
{
Id = 999,
ProductName = "RoundTrip Test Item",
Quantity = 50,
UnitPrice = 123.45m,
Status = TestStatus.Processing
};
var result = await _client.PostDataAsync(TestSignalRTags.TestOrderItemParam, item);
Assert.IsNotNull(result);
Assert.AreEqual(999, result.Id);
Assert.AreEqual("Processed: RoundTrip Test Item", result.ProductName);
Assert.AreEqual(100, result.Quantity);
Assert.AreEqual(246.90m, result.UnitPrice);
}
[TestMethod]
public async Task RoundTrip_NestedOrder_PreservesHierarchy()
{
var order = TestDataFactory.CreateOrder(itemCount: 3, palletsPerItem: 2, measurementsPerPallet: 1);
var result = await _client.PostDataAsync(TestSignalRTags.TestOrderParam, order);
Assert.IsNotNull(result);
Assert.AreEqual(order.Id, result.Id);
Assert.AreEqual(order.OrderNumber, result.OrderNumber);
Assert.AreEqual(3, result.Items.Count);
for (int i = 0; i < 3; i++)
{
Assert.AreEqual(order.Items[i].Id, result.Items[i].Id);
Assert.AreEqual(2, result.Items[i].Pallets.Count);
}
}
[TestMethod]
public async Task RoundTrip_SpecialCharacters_PreservedCorrectly()
{
var testString = "Special: \"quotes\" 'apostrophes' & ampersand \\ backslash \n newline";
var result = await _client.PostDataAsync(TestSignalRTags.StringParam, testString);
Assert.IsNotNull(result);
Assert.AreEqual($"Echo: {testString}", result);
}
[TestMethod]
public async Task RoundTrip_UnicodeCharacters_PreservedCorrectly()
{
var testString = "Unicode: 中文 日本語 한국어 🎉 émoji";
var result = await _client.PostDataAsync(TestSignalRTags.StringParam, testString);
Assert.IsNotNull(result);
Assert.AreEqual($"Echo: {testString}", result);
}
[TestMethod]
public async Task RoundTrip_EmptyString_PreservedCorrectly()
{
var result = await _client.PostDataAsync(TestSignalRTags.StringParam, "");
Assert.IsNotNull(result);
Assert.AreEqual("Echo: ", result);
}
[TestMethod]
public async Task RoundTrip_LargeDecimal_PreservedCorrectly()
{
var largeDecimal = 999999999999.999999m;
var result = await _client.PostDataAsync(TestSignalRTags.DecimalParam, largeDecimal);
Assert.AreEqual(largeDecimal * 2, result);
}
[TestMethod]
public async Task RoundTrip_ExtremeInt_PreservedCorrectly()
{
var result = await _client.PostDataAsync(TestSignalRTags.SingleIntParam, int.MaxValue);
Assert.AreEqual(int.MaxValue.ToString(), result);
}
#endregion
#region Response Data Integrity Tests
[TestMethod]
public async Task ResponseData_NotDoubleEscaped()
{
var item = new TestOrderItem { Id = 1, ProductName = "Test", Quantity = 10, UnitPrice = 20m };
var result = await _client.PostDataAsync(TestSignalRTags.TestOrderItemParam, item);
Assert.IsNotNull(result);
Assert.AreEqual(1, result.Id);
Assert.AreEqual("Processed: Test", result.ProductName);
Assert.AreEqual(20, result.Quantity);
Assert.AreEqual(40m, result.UnitPrice);
}
[TestMethod]
public async Task CollectionResponse_DeserializesCorrectly()
{
var items = new List
{
new() { Id = 1, ProductName = "Item1", Quantity = 10, UnitPrice = 1.1m },
new() { Id = 2, ProductName = "Item2", Quantity = 20, UnitPrice = 2.2m },
new() { Id = 3, ProductName = "Item3", Quantity = 30, UnitPrice = 3.3m }
};
var result = await _client.PostDataAsync, List>(
TestSignalRTags.TestOrderItemListParam, items);
Assert.IsNotNull(result);
Assert.AreEqual(3, result.Count);
for (int i = 0; i < 3; i++)
{
Assert.AreEqual(items[i].Id, result[i].Id);
Assert.AreEqual(items[i].ProductName, result[i].ProductName);
}
}
[TestMethod]
public async Task Async_Method_ReturnsActualResult_NotTaskWrapper()
{
var item = new TestOrderItem { Id = 42, ProductName = "TestProduct", Quantity = 5, UnitPrice = 10m };
var result = await _client.PostDataAsync(TestSignalRTags.AsyncTestOrderItemParam, item);
Assert.IsNotNull(result, "Result should not be null");
Assert.AreEqual(42, result.Id, "Id should match the original");
Assert.AreEqual("Async: TestProduct", result.ProductName,
"ProductName should be 'Async: TestProduct', not null.");
Assert.AreEqual(15, result.Quantity, "Quantity should be tripled (5*3=15).");
Assert.AreEqual(30m, result.UnitPrice, "UnitPrice should be tripled (10*3=30).");
}
#endregion
#region Task.FromResult Integration Tests
[TestMethod]
public async Task TaskFromResult_String_ReturnsActualResult_NotTaskWrapper()
{
var result = await _client.PostDataAsync(TestSignalRTags.TaskFromResultStringParam, "TestInput");
Assert.IsNotNull(result, "Result should not be null");
Assert.AreEqual("FromResult: TestInput", result,
"Should return the actual string, not Task wrapper JSON");
}
[TestMethod]
public async Task TaskFromResult_ComplexObject_ReturnsActualResult_NotTaskWrapper()
{
var item = new TestOrderItem { Id = 42, ProductName = "TestProduct", Quantity = 5, UnitPrice = 10m };
var result = await _client.PostDataAsync(TestSignalRTags.TaskFromResultTestOrderItemParam, item);
Assert.IsNotNull(result, "Result should not be null");
Assert.AreEqual(42, result.Id, "Id should match the original");
Assert.AreEqual("FromResult: TestProduct", result.ProductName,
"ProductName should be 'FromResult: TestProduct'.");
Assert.AreEqual(10, result.Quantity, "Quantity should be doubled (5*2=10).");
Assert.AreEqual(20m, result.UnitPrice, "UnitPrice should be doubled (10*2=20).");
}
[TestMethod]
public async Task TaskFromResult_Int_ReturnsActualResult_NotTaskWrapper()
{
var result = await _client.PostDataAsync(TestSignalRTags.TaskFromResultIntParam, 42);
Assert.AreEqual(84, result, "Should return doubled value (42*2=84).");
}
[TestMethod]
public async Task TaskFromResult_NoResult_CompletesSuccessfully()
{
var result = await _client.GetAllAsync