using System.Security.Claims;
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
///
/// 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
}