using AyCode.Core;
using AyCode.Core.Extensions;
using AyCode.Core.Tests.TestModels;
using AyCode.Services.SignalRs;
using MessagePack.Resolvers;
using Microsoft.AspNetCore.SignalR.Client;
namespace AyCode.Services.Tests.SignalRs;
///
/// Testable SignalR client that allows testing without real HubConnection.
///
public class TestableSignalRClient : AcSignalRClientBase
{
private HubConnectionState _connectionState = HubConnectionState.Connected;
private int? _nextRequestIdOverride;
///
/// Messages sent to the server (captured for assertions).
///
public List SentMessages { get; } = [];
///
/// Received messages (captured for assertions).
///
public List ReceivedMessages { get; } = [];
public TestableSignalRClient(TestLogger logger) : base(logger)
{
}
#region Override virtual methods for testing
protected override HubConnectionState GetConnectionState() => _connectionState;
protected override bool IsConnected() => _connectionState == HubConnectionState.Connected;
protected override Task StartConnectionInternal()
{
_connectionState = HubConnectionState.Connected;
return Task.CompletedTask;
}
protected override Task StopConnectionInternal()
{
_connectionState = HubConnectionState.Disconnected;
return Task.CompletedTask;
}
protected override ValueTask DisposeConnectionInternal() => ValueTask.CompletedTask;
protected override Task SendToHubAsync(int messageTag, byte[]? messageBytes, int? requestId)
{
SentMessages.Add(new SentClientMessage(messageTag, messageBytes, requestId));
return Task.CompletedTask;
}
protected override int GetNextRequestId()
{
if (_nextRequestIdOverride.HasValue)
{
var id = _nextRequestIdOverride.Value;
_nextRequestIdOverride = id + 1; // Auto-increment for subsequent calls
return id;
}
return AcDomain.NextUniqueInt32;
}
protected override Task MessageReceived(int messageTag, byte[] messageBytes)
{
ReceivedMessages.Add(new ReceivedClientMessage(messageTag, messageBytes));
return Task.CompletedTask;
}
#endregion
#region Public test helpers (wrappers for protected methods)
///
/// Sets the simulated connection state.
///
public void SetConnectionState(HubConnectionState state) => _connectionState = state;
///
/// Sets the next request ID for deterministic testing.
/// Will auto-increment for subsequent calls.
///
public void SetNextRequestId(int id) => _nextRequestIdOverride = id;
///
/// Gets the pending requests dictionary (public wrapper for testing).
///
public new System.Collections.Concurrent.ConcurrentDictionary GetPendingRequests()
=> base.GetPendingRequests();
///
/// Registers a pending request (public wrapper for testing).
///
public new void RegisterPendingRequest(int requestId, SignalRRequestModel model)
=> base.RegisterPendingRequest(requestId, model);
///
/// Clears pending requests (public wrapper for testing).
///
public new void ClearPendingRequests() => base.ClearPendingRequests();
///
/// Simulates receiving a response from the server.
///
public Task SimulateServerResponse(int requestId, int messageTag, SignalResponseStatus status, object? data = null)
{
var response = new SignalResponseDataMessage(messageTag, status, data, AcJsonSerializerOptions.Default);
var bytes = response.ToBinary();
return OnReceiveMessage(messageTag, bytes, requestId);
}
///
/// Simulates receiving a success response from the server.
///
public Task SimulateSuccessResponse(int requestId, int messageTag, T data)
=> SimulateServerResponse(requestId, messageTag, SignalResponseStatus.Success, data);
///
/// Simulates receiving an error response from the server.
///
public Task SimulateErrorResponse(int requestId, int messageTag)
=> SimulateServerResponse(requestId, messageTag, SignalResponseStatus.Error);
///
/// Gets the last sent message.
///
public SentClientMessage? LastSentMessage => SentMessages.LastOrDefault();
///
/// Clears all captured messages.
///
public void ClearMessages()
{
SentMessages.Clear();
ReceivedMessages.Clear();
}
///
/// Invokes OnReceiveMessage directly for testing.
///
public Task InvokeOnReceiveMessage(int messageTag, byte[] messageBytes, int? requestId)
=> OnReceiveMessage(messageTag, messageBytes, requestId);
#endregion
}
///
/// Represents a message sent from client to server.
///
public record SentClientMessage(int MessageTag, byte[]? MessageBytes, int? RequestId)
{
///
/// Deserializes the message to IdMessage format.
/// Works with both production SignalPostJsonDataMessage and test SignalRPostMessageDto.
///
public IdMessage? AsIdMessage()
{
if (MessageBytes == null) return null;
try
{
// First deserialize to get the PostDataJson string
var msg = MessageBytes.MessagePackTo>(ContractlessStandardResolver.Options);
return msg.PostData;
}
catch
{
// Fallback: try deserializing as raw JSON wrapper
try
{
var rawMsg = MessageBytes.MessagePackTo(ContractlessStandardResolver.Options);
return rawMsg.PostDataJson?.JsonTo();
}
catch
{
return null;
}
}
}
///
/// Deserializes the message to a specific post data type.
///
public T? AsPostData() where T : class
{
if (MessageBytes == null) return null;
try
{
var msg = MessageBytes.MessagePackTo>(ContractlessStandardResolver.Options);
return msg.PostData;
}
catch
{
// Fallback: try deserializing as raw JSON wrapper
try
{
var rawMsg = MessageBytes.MessagePackTo(ContractlessStandardResolver.Options);
return rawMsg.PostDataJson?.JsonTo();
}
catch
{
return null;
}
}
}
}
///
/// Represents a message received by the client.
///
public record ReceivedClientMessage(int MessageTag, byte[] MessageBytes)
{
///
/// Deserializes the message as a response.
///
public SignalResponseDataMessage? AsResponse()
{
try
{
return MessageBytes.BinaryTo();
}
catch
{
return null;
}
}
}