using AyCode.Core.Extensions; using MessagePack; using MessagePack.Resolvers; namespace AyCode.Core.Tests.TestModels; /// /// Common SignalR test/benchmark infrastructure. /// Provides message creation and serialization helpers used by both tests and benchmarks. /// public static class SignalRMessageFactory { /// /// Cached MessagePack options for ContractlessStandardResolver /// public static readonly MessagePackSerializerOptions ContractlessOptions = ContractlessStandardResolver.Options; /// /// Creates a MessagePack message for multiple parameters using IdMessage format. /// Each parameter is serialized directly as JSON. /// public static byte[] CreateIdMessage(params object[] values) { var idMessage = new SignalRIdMessageDto(values); var postMessage = new SignalRPostMessageDto { PostDataJson = idMessage.ToJson() }; return MessagePackSerializer.Serialize(postMessage, ContractlessOptions); } /// /// Creates a MessagePack message for a single primitive parameter. /// public static byte[] CreateSingleParamMessage(T value) where T : notnull { return CreateIdMessage(value); } /// /// Creates a MessagePack message for a complex object parameter. /// Uses PostDataJson pattern for single complex objects. /// public static byte[] CreateComplexObjectMessage(T obj) { var json = obj.ToJson(); var postMessage = new SignalRPostMessageDto { PostDataJson = json }; return MessagePackSerializer.Serialize(postMessage, ContractlessOptions); } /// /// Creates an empty MessagePack message for parameterless methods. /// public static byte[] CreateEmptyMessage() { var postMessage = new SignalRPostMessageDto(); return MessagePackSerializer.Serialize(postMessage, ContractlessOptions); } /// /// Creates a response message in MessagePack format. /// public static byte[] CreateResponseMessage(int messageTag, byte status, string? responseDataJson) { var response = new SignalRResponseDto { MessageTag = messageTag, Status = status, ResponseData = responseDataJson }; return MessagePackSerializer.Serialize(response, ContractlessOptions); } /// /// Creates a success response message in MessagePack format. /// public static byte[] CreateSuccessResponse(int messageTag, T data) { return CreateResponseMessage(messageTag, 5, data.ToJson()); // 5 = Success } /// /// Creates an error response message in MessagePack format. /// public static byte[] CreateErrorResponse(int messageTag) { return CreateResponseMessage(messageTag, 0, null); // 0 = Error } /// /// Deserializes a MessagePack message to IdMessage DTO. /// public static SignalRIdMessageDto? DeserializeToIdMessage(byte[] messageBytes) { if (messageBytes == null || messageBytes.Length == 0) return null; try { var postMessage = MessagePackSerializer.Deserialize(messageBytes, ContractlessOptions); return postMessage.PostDataJson?.JsonTo(); } catch { return null; } } /// /// Deserializes a MessagePack response message. /// public static SignalRResponseDto? DeserializeResponse(byte[] messageBytes) { if (messageBytes == null || messageBytes.Length == 0) return null; try { return MessagePackSerializer.Deserialize(messageBytes, ContractlessOptions); } catch { return null; } } } /// /// Lightweight DTO for IdMessage serialization/deserialization in tests and benchmarks. /// Mirrors the structure of IdMessage without dependencies on AyCode.Services. /// public class SignalRIdMessageDto { public List Ids { get; set; } = []; public SignalRIdMessageDto() { } public SignalRIdMessageDto(object[] ids) { Ids.AddRange(ids.Select(x => x.ToJson())); } public SignalRIdMessageDto(object id) { Ids.Add(id.ToJson()); } } /// /// Lightweight DTO for SignalR post message serialization. /// Mirrors SignalPostJsonMessage structure. /// [MessagePackObject] public class SignalRPostMessageDto { [Key(0)] public string? PostDataJson { get; set; } } /// /// Lightweight DTO for SignalR response message serialization. /// Mirrors SignalResponseJsonMessage structure. /// [MessagePackObject] public class SignalRResponseDto { [Key(0)] public int MessageTag { get; set; } [Key(1)] public byte Status { get; set; } [Key(2)] public string? ResponseData { get; set; } [IgnoreMember] public bool IsSuccess => Status == 5; [IgnoreMember] public bool IsError => Status == 0; } /// /// Common SignalR message tags for testing. /// These mirror the production tags but are defined here for test/benchmark independence. /// public static class CommonSignalRTags { // Primitive parameter tags public const int SingleIntParam = 100; public const int TwoIntParams = 101; public const int BoolParam = 102; public const int StringParam = 103; public const int GuidParam = 104; public const int EnumParam = 105; public const int NoParams = 107; public const int MultipleTypesParams = 109; // Extended primitives public const int DecimalParam = 140; public const int DateTimeParam = 141; public const int DoubleParam = 143; public const int LongParam = 144; // Complex object tags public const int TestOrderItemParam = 120; public const int TestOrderParam = 121; public const int SharedTagParam = 122; // Collection tags public const int IntArrayParam = 130; public const int GuidArrayParam = 131; public const int StringListParam = 132; public const int TestOrderItemListParam = 133; public const int IntListParam = 134; public const int BoolArrayParam = 135; public const int MixedWithArrayParam = 136; // Mixed parameter scenarios public const int IntAndDtoParam = 160; public const int DtoAndListParam = 161; public const int ThreeComplexParams = 162; public const int FiveParams = 164; } /// /// Pre-built test messages for benchmarking. /// Caches serialized messages to avoid setup overhead in benchmark iterations. /// public class SignalRBenchmarkData { // Pre-serialized messages public byte[] SingleIntMessage { get; } public byte[] TwoIntMessage { get; } public byte[] FiveParamsMessage { get; } public byte[] ComplexOrderItemMessage { get; } public byte[] ComplexOrderMessage { get; } public byte[] IntArrayMessage { get; } public byte[] MixedParamsMessage { get; } // Test data public TestOrderItem TestOrderItem { get; } public TestOrder TestOrder { get; } public int[] IntArray { get; } public Guid TestGuid { get; } public SignalRBenchmarkData() { // Create test data TestGuid = Guid.NewGuid(); IntArray = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; TestOrderItem = new TestOrderItem { Id = 42, ProductName = "Benchmark Product", Quantity = 100, UnitPrice = 99.99m, Status = TestStatus.Active }; TestOrder = TestDataFactory.CreateOrder(itemCount: 3, palletsPerItem: 2, measurementsPerPallet: 2); // Pre-serialize messages SingleIntMessage = SignalRMessageFactory.CreateSingleParamMessage(42); TwoIntMessage = SignalRMessageFactory.CreateIdMessage(10, 20); FiveParamsMessage = SignalRMessageFactory.CreateIdMessage(42, "hello", true, TestGuid, 99.99m); ComplexOrderItemMessage = SignalRMessageFactory.CreateComplexObjectMessage(TestOrderItem); ComplexOrderMessage = SignalRMessageFactory.CreateComplexObjectMessage(TestOrder); IntArrayMessage = SignalRMessageFactory.CreateComplexObjectMessage(IntArray); MixedParamsMessage = SignalRMessageFactory.CreateIdMessage(true, IntArray, "hello"); } }