consolidate the tests locations
This commit is contained in:
@@ -1,210 +0,0 @@
|
||||
using StellaOps.Router.Common.Enums;
|
||||
using StellaOps.Router.Common.Frames;
|
||||
using StellaOps.Router.Common.Models;
|
||||
|
||||
namespace StellaOps.Router.Testing.Factories;
|
||||
|
||||
/// <summary>
|
||||
/// Factory for creating test frames with sensible defaults.
|
||||
/// </summary>
|
||||
public static class TestFrameFactory
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a request frame with the specified payload.
|
||||
/// </summary>
|
||||
public static Frame CreateRequestFrame(
|
||||
byte[]? payload = null,
|
||||
string? correlationId = null,
|
||||
FrameType frameType = FrameType.Request)
|
||||
{
|
||||
return new Frame
|
||||
{
|
||||
Type = frameType,
|
||||
CorrelationId = correlationId ?? Guid.NewGuid().ToString("N"),
|
||||
Payload = payload ?? Array.Empty<byte>()
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a response frame for the given correlation ID.
|
||||
/// </summary>
|
||||
public static Frame CreateResponseFrame(
|
||||
string correlationId,
|
||||
byte[]? payload = null)
|
||||
{
|
||||
return new Frame
|
||||
{
|
||||
Type = FrameType.Response,
|
||||
CorrelationId = correlationId,
|
||||
Payload = payload ?? Array.Empty<byte>()
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a hello frame for service registration.
|
||||
/// </summary>
|
||||
public static Frame CreateHelloFrame(
|
||||
string serviceName = "test-service",
|
||||
string version = "1.0.0",
|
||||
string region = "test",
|
||||
string instanceId = "test-instance",
|
||||
IReadOnlyList<EndpointDescriptor>? endpoints = null)
|
||||
{
|
||||
var helloPayload = new HelloPayload
|
||||
{
|
||||
Instance = new InstanceDescriptor
|
||||
{
|
||||
InstanceId = instanceId,
|
||||
ServiceName = serviceName,
|
||||
Version = version,
|
||||
Region = region
|
||||
},
|
||||
Endpoints = endpoints ?? []
|
||||
};
|
||||
|
||||
return new Frame
|
||||
{
|
||||
Type = FrameType.Hello,
|
||||
CorrelationId = Guid.NewGuid().ToString("N"),
|
||||
Payload = System.Text.Json.JsonSerializer.SerializeToUtf8Bytes(helloPayload)
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a heartbeat frame.
|
||||
/// </summary>
|
||||
public static Frame CreateHeartbeatFrame(
|
||||
string instanceId = "test-instance",
|
||||
InstanceHealthStatus status = InstanceHealthStatus.Healthy,
|
||||
int inFlightRequestCount = 0,
|
||||
double errorRate = 0.0)
|
||||
{
|
||||
var heartbeatPayload = new HeartbeatPayload
|
||||
{
|
||||
InstanceId = instanceId,
|
||||
Status = status,
|
||||
InFlightRequestCount = inFlightRequestCount,
|
||||
ErrorRate = errorRate,
|
||||
TimestampUtc = DateTime.UtcNow
|
||||
};
|
||||
|
||||
return new Frame
|
||||
{
|
||||
Type = FrameType.Heartbeat,
|
||||
CorrelationId = Guid.NewGuid().ToString("N"),
|
||||
Payload = System.Text.Json.JsonSerializer.SerializeToUtf8Bytes(heartbeatPayload)
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a cancel frame for the given correlation ID.
|
||||
/// </summary>
|
||||
public static Frame CreateCancelFrame(
|
||||
string correlationId,
|
||||
string? reason = null)
|
||||
{
|
||||
var cancelPayload = new CancelPayload
|
||||
{
|
||||
Reason = reason ?? CancelReasons.Timeout
|
||||
};
|
||||
|
||||
return new Frame
|
||||
{
|
||||
Type = FrameType.Cancel,
|
||||
CorrelationId = correlationId,
|
||||
Payload = System.Text.Json.JsonSerializer.SerializeToUtf8Bytes(cancelPayload)
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a frame with a specific payload size for testing limits.
|
||||
/// </summary>
|
||||
public static Frame CreateFrameWithPayloadSize(int payloadSize)
|
||||
{
|
||||
var payload = new byte[payloadSize];
|
||||
Random.Shared.NextBytes(payload);
|
||||
|
||||
return new Frame
|
||||
{
|
||||
Type = FrameType.Request,
|
||||
CorrelationId = Guid.NewGuid().ToString("N"),
|
||||
Payload = payload
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a request frame from JSON content.
|
||||
/// </summary>
|
||||
public static RequestFrame CreateTypedRequestFrame<T>(
|
||||
T request,
|
||||
string method = "POST",
|
||||
string path = "/test",
|
||||
Dictionary<string, string>? headers = null)
|
||||
{
|
||||
return new RequestFrame
|
||||
{
|
||||
RequestId = Guid.NewGuid().ToString("N"),
|
||||
CorrelationId = Guid.NewGuid().ToString("N"),
|
||||
Method = method,
|
||||
Path = path,
|
||||
Headers = headers ?? new Dictionary<string, string>(),
|
||||
Payload = System.Text.Json.JsonSerializer.SerializeToUtf8Bytes(request)
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an endpoint descriptor for testing.
|
||||
/// </summary>
|
||||
public static EndpointDescriptor CreateEndpointDescriptor(
|
||||
string method = "GET",
|
||||
string path = "/test",
|
||||
string serviceName = "test-service",
|
||||
string version = "1.0.0",
|
||||
int timeoutSeconds = 30,
|
||||
bool supportsStreaming = false,
|
||||
IReadOnlyList<ClaimRequirement>? requiringClaims = null)
|
||||
{
|
||||
return new EndpointDescriptor
|
||||
{
|
||||
Method = method,
|
||||
Path = path,
|
||||
ServiceName = serviceName,
|
||||
Version = version,
|
||||
DefaultTimeout = TimeSpan.FromSeconds(timeoutSeconds),
|
||||
SupportsStreaming = supportsStreaming,
|
||||
RequiringClaims = requiringClaims ?? []
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an instance descriptor for testing.
|
||||
/// </summary>
|
||||
public static InstanceDescriptor CreateInstanceDescriptor(
|
||||
string instanceId = "test-instance",
|
||||
string serviceName = "test-service",
|
||||
string version = "1.0.0",
|
||||
string region = "test")
|
||||
{
|
||||
return new InstanceDescriptor
|
||||
{
|
||||
InstanceId = instanceId,
|
||||
ServiceName = serviceName,
|
||||
Version = version,
|
||||
Region = region
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a claim requirement for testing.
|
||||
/// </summary>
|
||||
public static ClaimRequirement CreateClaimRequirement(
|
||||
string type,
|
||||
string? value = null)
|
||||
{
|
||||
return new ClaimRequirement
|
||||
{
|
||||
Type = type,
|
||||
Value = value
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,102 +0,0 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Router.Testing.Fixtures;
|
||||
|
||||
/// <summary>
|
||||
/// Base test fixture for Router tests providing common utilities.
|
||||
/// Implements IAsyncLifetime for async setup/teardown.
|
||||
/// </summary>
|
||||
public abstract class RouterTestFixture : IAsyncLifetime
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a null logger factory for tests that don't need logging.
|
||||
/// </summary>
|
||||
protected ILoggerFactory LoggerFactory { get; } = NullLoggerFactory.Instance;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a null logger for tests that don't need logging.
|
||||
/// </summary>
|
||||
protected ILogger<T> GetLogger<T>() => NullLogger<T>.Instance;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a cancellation token that times out after the specified duration.
|
||||
/// </summary>
|
||||
protected static CancellationToken CreateTimeoutToken(TimeSpan timeout)
|
||||
{
|
||||
var cts = new CancellationTokenSource(timeout);
|
||||
return cts.Token;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a cancellation token that times out after 5 seconds (default for tests).
|
||||
/// </summary>
|
||||
protected static CancellationToken CreateTestTimeoutToken()
|
||||
{
|
||||
return CreateTimeoutToken(TimeSpan.FromSeconds(5));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Waits for a condition to be true with timeout.
|
||||
/// </summary>
|
||||
protected static async Task WaitForConditionAsync(
|
||||
Func<bool> condition,
|
||||
TimeSpan timeout,
|
||||
TimeSpan? pollInterval = null)
|
||||
{
|
||||
var interval = pollInterval ?? TimeSpan.FromMilliseconds(50);
|
||||
var deadline = DateTimeOffset.UtcNow + timeout;
|
||||
|
||||
while (DateTimeOffset.UtcNow < deadline)
|
||||
{
|
||||
if (condition())
|
||||
return;
|
||||
|
||||
await Task.Delay(interval);
|
||||
}
|
||||
|
||||
throw new TimeoutException($"Condition not met within {timeout}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Waits for an async condition to be true with timeout.
|
||||
/// </summary>
|
||||
protected static async Task WaitForConditionAsync(
|
||||
Func<Task<bool>> condition,
|
||||
TimeSpan timeout,
|
||||
TimeSpan? pollInterval = null)
|
||||
{
|
||||
var interval = pollInterval ?? TimeSpan.FromMilliseconds(50);
|
||||
var deadline = DateTimeOffset.UtcNow + timeout;
|
||||
|
||||
while (DateTimeOffset.UtcNow < deadline)
|
||||
{
|
||||
if (await condition())
|
||||
return;
|
||||
|
||||
await Task.Delay(interval);
|
||||
}
|
||||
|
||||
throw new TimeoutException($"Condition not met within {timeout}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Override for async initialization.
|
||||
/// </summary>
|
||||
public virtual Task InitializeAsync() => Task.CompletedTask;
|
||||
|
||||
/// <summary>
|
||||
/// Override for async cleanup.
|
||||
/// </summary>
|
||||
public virtual Task DisposeAsync() => Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Collection fixture for sharing state across tests in the same collection.
|
||||
/// </summary>
|
||||
public abstract class RouterCollectionFixture : IAsyncLifetime
|
||||
{
|
||||
public virtual Task InitializeAsync() => Task.CompletedTask;
|
||||
public virtual Task DisposeAsync() => Task.CompletedTask;
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
using StellaOps.Router.Common.Enums;
|
||||
using StellaOps.Router.Common.Models;
|
||||
|
||||
namespace StellaOps.Router.Testing.Mocks;
|
||||
|
||||
/// <summary>
|
||||
/// A mock connection state for testing routing and connection management.
|
||||
/// </summary>
|
||||
public sealed class MockConnectionState
|
||||
{
|
||||
public string ConnectionId { get; init; } = Guid.NewGuid().ToString("N");
|
||||
public string ServiceName { get; init; } = "test-service";
|
||||
public string Version { get; init; } = "1.0.0";
|
||||
public string Region { get; init; } = "test";
|
||||
public string InstanceId { get; init; } = "test-instance";
|
||||
public InstanceHealthStatus HealthStatus { get; set; } = InstanceHealthStatus.Healthy;
|
||||
public DateTimeOffset ConnectedAtUtc { get; init; } = DateTimeOffset.UtcNow;
|
||||
public DateTimeOffset LastHeartbeatUtc { get; set; } = DateTimeOffset.UtcNow;
|
||||
public int InflightRequests { get; set; }
|
||||
public int Weight { get; set; } = 100;
|
||||
public List<EndpointDescriptor> Endpoints { get; init; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Creates a connection state for testing.
|
||||
/// </summary>
|
||||
public static MockConnectionState Create(
|
||||
string? serviceName = null,
|
||||
string? instanceId = null,
|
||||
InstanceHealthStatus status = InstanceHealthStatus.Healthy)
|
||||
{
|
||||
return new MockConnectionState
|
||||
{
|
||||
ServiceName = serviceName ?? "test-service",
|
||||
InstanceId = instanceId ?? $"instance-{Guid.NewGuid():N}",
|
||||
HealthStatus = status
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates multiple connection states simulating a service cluster.
|
||||
/// </summary>
|
||||
public static List<MockConnectionState> CreateCluster(
|
||||
string serviceName,
|
||||
int instanceCount,
|
||||
InstanceHealthStatus status = InstanceHealthStatus.Healthy)
|
||||
{
|
||||
return Enumerable.Range(0, instanceCount)
|
||||
.Select(i => new MockConnectionState
|
||||
{
|
||||
ServiceName = serviceName,
|
||||
InstanceId = $"{serviceName}-{i}",
|
||||
HealthStatus = status
|
||||
})
|
||||
.ToList();
|
||||
}
|
||||
}
|
||||
@@ -1,104 +0,0 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
namespace StellaOps.Router.Testing.Mocks;
|
||||
|
||||
/// <summary>
|
||||
/// A logger that records all log entries for assertions.
|
||||
/// </summary>
|
||||
public sealed class RecordingLogger<T> : ILogger<T>
|
||||
{
|
||||
private readonly ConcurrentQueue<LogEntry> _entries = new();
|
||||
|
||||
/// <summary>
|
||||
/// Gets all recorded log entries.
|
||||
/// </summary>
|
||||
public IReadOnlyList<LogEntry> Entries => _entries.ToList();
|
||||
|
||||
/// <summary>
|
||||
/// Gets entries filtered by log level.
|
||||
/// </summary>
|
||||
public IEnumerable<LogEntry> GetEntries(LogLevel level) =>
|
||||
_entries.Where(e => e.Level == level);
|
||||
|
||||
/// <summary>
|
||||
/// Gets all error entries.
|
||||
/// </summary>
|
||||
public IEnumerable<LogEntry> Errors => GetEntries(LogLevel.Error);
|
||||
|
||||
/// <summary>
|
||||
/// Gets all warning entries.
|
||||
/// </summary>
|
||||
public IEnumerable<LogEntry> Warnings => GetEntries(LogLevel.Warning);
|
||||
|
||||
/// <summary>
|
||||
/// Clears all recorded entries.
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
while (_entries.TryDequeue(out _)) { }
|
||||
}
|
||||
|
||||
public IDisposable? BeginScope<TState>(TState state) where TState : notnull =>
|
||||
NullScope.Instance;
|
||||
|
||||
public bool IsEnabled(LogLevel logLevel) => true;
|
||||
|
||||
public void Log<TState>(
|
||||
LogLevel logLevel,
|
||||
EventId eventId,
|
||||
TState state,
|
||||
Exception? exception,
|
||||
Func<TState, Exception?, string> formatter)
|
||||
{
|
||||
_entries.Enqueue(new LogEntry
|
||||
{
|
||||
Level = logLevel,
|
||||
EventId = eventId,
|
||||
Message = formatter(state, exception),
|
||||
Exception = exception,
|
||||
Timestamp = DateTimeOffset.UtcNow
|
||||
});
|
||||
}
|
||||
|
||||
private sealed class NullScope : IDisposable
|
||||
{
|
||||
public static readonly NullScope Instance = new();
|
||||
public void Dispose() { }
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a recorded log entry.
|
||||
/// </summary>
|
||||
public sealed record LogEntry
|
||||
{
|
||||
public required LogLevel Level { get; init; }
|
||||
public required EventId EventId { get; init; }
|
||||
public required string Message { get; init; }
|
||||
public Exception? Exception { get; init; }
|
||||
public DateTimeOffset Timestamp { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A logger factory that creates recording loggers.
|
||||
/// </summary>
|
||||
public sealed class RecordingLoggerFactory : ILoggerFactory
|
||||
{
|
||||
private readonly ConcurrentDictionary<string, object> _loggers = new();
|
||||
|
||||
public ILogger CreateLogger(string categoryName) =>
|
||||
(ILogger)_loggers.GetOrAdd(categoryName, _ => new RecordingLogger<object>());
|
||||
|
||||
public ILogger<T> CreateLogger<T>() =>
|
||||
(ILogger<T>)_loggers.GetOrAdd(typeof(T).FullName!, _ => new RecordingLogger<T>());
|
||||
|
||||
public RecordingLogger<T>? GetLogger<T>() =>
|
||||
_loggers.TryGetValue(typeof(T).FullName!, out var logger)
|
||||
? logger as RecordingLogger<T>
|
||||
: null;
|
||||
|
||||
public void AddProvider(ILoggerProvider provider) { }
|
||||
|
||||
public void Dispose() { }
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<LangVersion>preview</LangVersion>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
|
||||
<IsPackable>false</IsPackable>
|
||||
<RootNamespace>StellaOps.Router.Testing</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.0" />
|
||||
<PackageReference Include="xunit" Version="2.9.2" />
|
||||
<PackageReference Include="FluentAssertions" Version="6.12.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\StellaOps.Router.Common\StellaOps.Router.Common.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
Reference in New Issue
Block a user