222 lines
6.2 KiB
C#
222 lines
6.2 KiB
C#
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;
|
|
|
|
/// <summary>
|
|
/// Testable SignalR hub that overrides infrastructure dependencies.
|
|
/// Enables unit testing without SignalR server or mocks.
|
|
/// </summary>
|
|
public class TestableSignalRHub : AcWebSignalRHubBase<TestSignalRTags, TestLogger>
|
|
{
|
|
#region Captured Data for Assertions
|
|
|
|
/// <summary>
|
|
/// Messages sent via ResponseToCaller or SendMessageToClient
|
|
/// </summary>
|
|
public List<SentMessage> SentMessages { get; } = [];
|
|
|
|
/// <summary>
|
|
/// Whether notFoundCallback was invoked
|
|
/// </summary>
|
|
public bool WasNotFoundCallbackInvoked { get; private set; }
|
|
|
|
/// <summary>
|
|
/// The tag name passed to notFoundCallback
|
|
/// </summary>
|
|
public string? NotFoundTagName { get; private set; }
|
|
|
|
#endregion
|
|
|
|
#region Test Configuration
|
|
|
|
/// <summary>
|
|
/// Simulated connection ID
|
|
/// </summary>
|
|
public string TestConnectionId { get; set; } = "test-connection-id";
|
|
|
|
/// <summary>
|
|
/// Simulated user identifier
|
|
/// </summary>
|
|
public string? TestUserIdentifier { get; set; } = "test-user-id";
|
|
|
|
/// <summary>
|
|
/// Simulated connection aborted state
|
|
/// </summary>
|
|
public bool TestIsConnectionAborted { get; set; } = false;
|
|
|
|
/// <summary>
|
|
/// Simulated ClaimsPrincipal (optional)
|
|
/// </summary>
|
|
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
|
|
|
|
/// <summary>
|
|
/// Sets the serializer type for testing (JSON or Binary).
|
|
/// </summary>
|
|
public void SetSerializerType(AcSerializerType serializerType)
|
|
{
|
|
SerializerOptions = serializerType == AcSerializerType.Binary
|
|
? new AcBinarySerializerOptions()
|
|
: new AcJsonSerializerOptions();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Register a service with SignalR-attributed methods
|
|
/// </summary>
|
|
public void RegisterService(object service)
|
|
{
|
|
DynamicMethodCallModels.Add(new AcDynamicMethodCallModel<SignalRAttribute>(service));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Invoke ProcessOnReceiveMessage for testing
|
|
/// </summary>
|
|
public Task InvokeProcessOnReceiveMessage(int messageTag, byte[]? message, int? requestId = null)
|
|
{
|
|
return ProcessOnReceiveMessage(messageTag, message, requestId, async tagName =>
|
|
{
|
|
WasNotFoundCallbackInvoked = true;
|
|
NotFoundTagName = tagName;
|
|
await Task.CompletedTask;
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the logger for assertions
|
|
/// </summary>
|
|
public new TestLogger Logger => base.Logger;
|
|
|
|
/// <summary>
|
|
/// Reset captured state for next test
|
|
/// </summary>
|
|
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
|
|
}
|
|
|
|
/// <summary>
|
|
/// Captured sent message for assertions
|
|
/// </summary>
|
|
public record SentMessage(
|
|
int MessageTag,
|
|
ISignalRMessage Message,
|
|
int? RequestId,
|
|
SendTarget Target,
|
|
string? TargetId = null)
|
|
{
|
|
/// <summary>
|
|
/// Get the response as SignalResponseJsonMessage for inspection
|
|
/// </summary>
|
|
public SignalResponseJsonMessage? AsJsonResponse => Message as SignalResponseJsonMessage;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Target of the sent message
|
|
/// </summary>
|
|
public enum SendTarget
|
|
{
|
|
Caller,
|
|
Client,
|
|
Others,
|
|
All,
|
|
User,
|
|
Group
|
|
}
|