using System.Security.Claims; using AyCode.Core.Extensions; using AyCode.Core.Tests.TestModels; using AyCode.Models.Server.DynamicMethods; using AyCode.Services.Server.SignalRs; using AyCode.Services.SignalRs; using Microsoft.Extensions.Configuration; namespace AyCode.Services.Server.Tests.SignalRs; /// /// Testable SignalR hub that overrides infrastructure dependencies. /// Enables unit testing without SignalR server or mocks. /// public class TestableSignalRHub : AcWebSignalRHubBase { #region Captured Data for Assertions /// /// Messages sent via ResponseToCaller or SendMessageToClient /// public List SentMessages { get; } = []; /// /// Whether notFoundCallback was invoked /// public bool WasNotFoundCallbackInvoked { get; private set; } /// /// The tag name passed to notFoundCallback /// public string? NotFoundTagName { get; private set; } #endregion #region Test Configuration /// /// Simulated connection ID /// public string TestConnectionId { get; set; } = "test-connection-id"; /// /// Simulated user identifier /// public string? TestUserIdentifier { get; set; } = "test-user-id"; /// /// Simulated connection aborted state /// public bool TestIsConnectionAborted { get; set; } = false; /// /// Simulated ClaimsPrincipal (optional) /// public ClaimsPrincipal? TestUser { get; set; } #endregion public TestableSignalRHub() : base(new ConfigurationBuilder().Build(), new TestLogger()) { } public TestableSignalRHub(IConfiguration configuration, TestLogger logger) : base(configuration, logger) { } #region Public Test Entry Points /// /// Sets the serializer type for testing (JSON or Binary). /// public void SetSerializerType(AcSerializerType serializerType) { SerializerOptions = serializerType == AcSerializerType.Binary ? new AcBinarySerializerOptions() : new AcJsonSerializerOptions(); } /// /// Register a service with SignalR-attributed methods /// public void RegisterService(object service) { DynamicMethodCallModels.Add(new AcDynamicMethodCallModel(service)); } /// /// Invoke ProcessOnReceiveMessage for testing /// public Task InvokeProcessOnReceiveMessage(int messageTag, byte[]? message, int? requestId = null) { return ProcessOnReceiveMessage(messageTag, message, requestId, async tagName => { WasNotFoundCallbackInvoked = true; NotFoundTagName = tagName; await Task.CompletedTask; }); } /// /// Get the logger for assertions /// public new TestLogger Logger => base.Logger; /// /// Reset captured state for next test /// public void Reset() { SentMessages.Clear(); WasNotFoundCallbackInvoked = false; NotFoundTagName = null; Logger.Clear(); } #endregion #region Overridden Context Accessors protected override string GetConnectionId() => TestConnectionId; protected override bool IsConnectionAborted() => TestIsConnectionAborted; protected override string? GetUserIdentifier() => TestUserIdentifier; protected override ClaimsPrincipal? GetUser() => TestUser; #endregion #region Overridden Response Methods (capture messages for testing) protected override Task ResponseToCaller(int messageTag, ISignalRMessage message, int? requestId) { SentMessages.Add(new SentMessage( MessageTag: messageTag, Message: message, RequestId: requestId, Target: SendTarget.Caller )); return Task.CompletedTask; } protected override Task SendMessageToClient(IAcSignalRHubItemServer sendTo, int messageTag, ISignalRMessage message, int? requestId = null) { SentMessages.Add(new SentMessage( MessageTag: messageTag, Message: message, RequestId: requestId, Target: SendTarget.Client )); return Task.CompletedTask; } protected override Task SendMessageToOthers(int messageTag, object? content) { SentMessages.Add(new SentMessage( MessageTag: messageTag, Message: new SignalResponseJsonMessage(messageTag, SignalResponseStatus.Success, content), RequestId: null, Target: SendTarget.Others )); return Task.CompletedTask; } protected override Task SendMessageToAll(int messageTag, object? content) { SentMessages.Add(new SentMessage( MessageTag: messageTag, Message: new SignalResponseJsonMessage(messageTag, SignalResponseStatus.Success, content), RequestId: null, Target: SendTarget.All )); return Task.CompletedTask; } protected override Task SendMessageToUserIdInternal(string userId, int messageTag, ISignalRMessage message, int? requestId) { SentMessages.Add(new SentMessage( MessageTag: messageTag, Message: message, RequestId: requestId, Target: SendTarget.User, TargetId: userId )); return Task.CompletedTask; } #endregion } /// /// Captured sent message for assertions /// public record SentMessage( int MessageTag, ISignalRMessage Message, int? RequestId, SendTarget Target, string? TargetId = null) { /// /// Get the response as SignalResponseJsonMessage for inspection /// public SignalResponseJsonMessage? AsJsonResponse => Message as SignalResponseJsonMessage; } /// /// Target of the sent message /// public enum SendTarget { Caller, Client, Others, All, User, Group }