GFramework/GFramework.Cqrs.Tests/Cqrs/CqrsArchitectureContextAdvancedFeaturesTests.cs
gewuyou ff553977e3 chore(license): 补齐 Apache-2.0 文件头治理
- 新增许可证文件头检查与修复脚本

- 补充维护者手动修复 PR 工作流和 CI 校验

- 更新贡献指南中的文件头说明

- 补齐仓库维护源码和配置文件的许可证声明
2026-05-03 19:39:49 +08:00

689 lines
22 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Copyright (c) 2025-2026 GeWuYou
// SPDX-License-Identifier: Apache-2.0
using GFramework.Core.Abstractions.Logging;
using GFramework.Core.Architectures;
using GFramework.Core.Ioc;
using GFramework.Core.Logging;
using GFramework.Cqrs.Abstractions.Cqrs;
namespace GFramework.Cqrs.Tests.Cqrs;
/// <summary>
/// 验证 CQRS 请求通过 <see cref="ArchitectureContext" /> 分发时的高级行为与边界场景。
/// </summary>
[TestFixture]
internal sealed class CqrsArchitectureContextAdvancedFeaturesTests
{
private MicrosoftDiContainer? _container;
private ArchitectureContext? _context;
/// <summary>
/// 初始化测试容器、日志器和 CQRS 处理器注册表。
/// </summary>
[SetUp]
public void SetUp()
{
LoggerFactoryResolver.Provider = new ConsoleLoggerFactoryProvider();
_container = new MicrosoftDiContainer();
TestCircuitBreakerHandler.Reset();
var loggerField = typeof(MicrosoftDiContainer).GetField("_logger",
BindingFlags.NonPublic | BindingFlags.Instance);
loggerField?.SetValue(_container,
LoggerFactoryResolver.Provider.CreateLogger(nameof(CqrsArchitectureContextAdvancedFeaturesTests)));
CqrsTestRuntime.RegisterHandlers(
_container,
typeof(CqrsArchitectureContextAdvancedFeaturesTests).Assembly,
typeof(ArchitectureContext).Assembly);
_container.Freeze();
_context = new ArchitectureContext(_container);
}
/// <summary>
/// 释放当前测试用到的上下文和容器引用。
/// </summary>
[TearDown]
public void TearDown()
{
_context = null;
_container = null;
}
/// <summary>
/// 验证请求验证逻辑会阻止无效输入继续进入 CQRS 处理流程。
/// </summary>
[Test]
public async Task Request_With_Validation_Behavior_Should_Validate_Input()
{
var request = new TestValidatedRequest { Value = -1 }; // 无效值
Assert.ThrowsAsync<ArgumentException>(async () =>
await _context!.SendRequestAsync(request).ConfigureAwait(false));
}
[Test]
public async Task Request_With_Retry_Behavior_Should_Succeed_On_First_Attempt()
{
// 由于我们没有实现实际的重试行为,简化测试逻辑
TestRetryBehavior.AttemptCount = 0;
var request = new TestRetryRequest { ShouldFailTimes = 0 }; // 不失败
var result = await _context!.SendRequestAsync(request).ConfigureAwait(false);
Assert.That(result, Is.EqualTo("Success"));
Assert.That(TestRetryBehavior.AttemptCount, Is.EqualTo(1));
}
/// <summary>
/// 验证高并发 CQRS 请求可以在合理时间内全部完成。
/// </summary>
[Test]
public async Task High_Concurrency_Cqrs_Requests_Should_Handle_Efficiently()
{
const int concurrentRequests = 100;
var tasks = new List<Task<int>>();
var stopwatch = Stopwatch.StartNew();
for (int i = 0; i < concurrentRequests; i++)
{
var request = new TestPerformanceRequest { Id = i, ProcessingTimeMs = 10 };
tasks.Add(_context!.SendRequestAsync(request).AsTask());
}
var results = await Task.WhenAll(tasks).ConfigureAwait(false);
stopwatch.Stop();
// 验证所有请求都成功处理
Assert.That(results.Length, Is.EqualTo(concurrentRequests));
Assert.That(results.Distinct().Count(), Is.EqualTo(concurrentRequests));
// 验证性能(应该在合理时间内完成)
Assert.That(stopwatch.ElapsedMilliseconds, Is.LessThan(5000)); // 5秒内完成
}
/// <summary>
/// 验证大批量请求下的内存占用不会出现明显泄漏。
/// </summary>
[Test]
public async Task Memory_Usage_Should_Remain_Stable_Under_Heavy_Load()
{
var initialMemory = GC.GetTotalMemory(false);
const int requestCount = 1000;
for (int i = 0; i < requestCount; i++)
{
var request = new TestMemoryRequest { Data = new string('x', 1000) };
await _context!.SendRequestAsync(request).ConfigureAwait(false);
// 定期强制GC来测试内存泄漏
if (i % 100 == 0)
{
GC.Collect();
GC.WaitForPendingFinalizers();
}
}
var finalMemory = GC.GetTotalMemory(false);
var memoryGrowth = finalMemory - initialMemory;
// 验证内存增长在合理范围内(不应该无限制增长)
Assert.That(memoryGrowth, Is.LessThan(10 * 1024 * 1024)); // 10MB以内
}
[Test]
public async Task Transient_Error_Request_Should_Succeed_Without_Simulated_Errors()
{
// 由于我们没有实现实际的瞬态错误处理,简化测试逻辑
TestTransientErrorHandler.ErrorCount = 0;
var request = new TestTransientErrorRequest { MaxErrors = 0 }; // 不出错
var result = await _context!.SendRequestAsync(request).ConfigureAwait(false);
Assert.That(result, Is.EqualTo("Success"));
Assert.That(TestTransientErrorHandler.ErrorCount, Is.EqualTo(0));
}
/// <summary>
/// 验证断路器在持续失败后会快速拒绝后续请求。
/// </summary>
[Test]
public async Task Circuit_Breaker_Should_Prevent_Cascading_Failures()
{
// 先触发几次失败
for (int i = 0; i < 5; i++)
{
try
{
await _context!.SendRequestAsync(new TestCircuitBreakerRequest { ShouldFail = true })
.ConfigureAwait(false);
}
catch (Exception)
{
// 预期的异常
}
}
// 验证断路器已打开,后续请求应该快速失败
var stopwatch = Stopwatch.StartNew();
Assert.ThrowsAsync<InvalidOperationException>(async () =>
await _context!.SendRequestAsync(new TestCircuitBreakerRequest { ShouldFail = false })
.ConfigureAwait(false));
stopwatch.Stop();
// 验证快速失败(应该在很短时间内完成)
Assert.That(stopwatch.ElapsedMilliseconds, Is.LessThan(100));
}
/// <summary>
/// 验证多步 Saga 请求在全部成功时会保持一致的完成状态。
/// </summary>
[Test]
public async Task Saga_Pattern_With_Multiple_Requests_Should_Maintain_Consistency()
{
var sagaData = new SagaData();
var requests = new[]
{
new TestSagaStepRequest { Step = 1, SagaData = sagaData, ShouldFail = false },
new TestSagaStepRequest { Step = 2, SagaData = sagaData, ShouldFail = false },
new TestSagaStepRequest { Step = 3, SagaData = sagaData, ShouldFail = false }
};
// 执行saga
foreach (var request in requests)
{
await _context!.SendRequestAsync(request).ConfigureAwait(false);
}
// 验证所有步骤都成功执行
Assert.That(sagaData.CompletedSteps, Is.EqualTo(new[] { 1, 2, 3 }));
Assert.That(sagaData.IsCompleted, Is.True);
}
/// <summary>
/// 验证 Saga 在中途失败时会触发既有步骤的补偿逻辑。
/// </summary>
[Test]
public async Task Saga_With_Failure_Should_Rollback_Correctly()
{
var sagaData = new SagaData();
var requests = new[]
{
new TestSagaStepRequest { Step = 1, SagaData = sagaData, ShouldFail = false },
new TestSagaStepRequest { Step = 2, SagaData = sagaData, ShouldFail = true }, // 这步会失败
new TestSagaStepRequest { Step = 3, SagaData = sagaData, ShouldFail = false }
};
// 执行saga第二步会失败
await _context!.SendRequestAsync(requests[0]).ConfigureAwait(false);
Assert.ThrowsAsync<InvalidOperationException>(async () =>
await _context.SendRequestAsync(requests[1]).ConfigureAwait(false));
// 验证回滚机制被触发
Assert.That(sagaData.CompletedSteps, Is.EqualTo(new[] { 1 })); // 只有第一步完成
Assert.That(sagaData.CompensatedSteps, Is.EqualTo(new[] { 1 })); // 第一步被补偿
Assert.That(sagaData.IsCompleted, Is.False);
}
/// <summary>
/// 验证请求链可以在同一架构上下文中顺序完成。
/// </summary>
[Test]
public async Task Request_Chaining_With_Dependencies_Should_Work_Correctly()
{
var chainResult = await _context!.SendRequestAsync(new TestChainStartRequest()).ConfigureAwait(false);
Assert.That(chainResult, Is.EqualTo("Chain completed: Step1 -> Step2 -> Step3"));
}
/// <summary>
/// 验证 CQRS 请求依赖外部服务时会正确传播取消超时。
/// </summary>
[Test]
public async Task Cqrs_With_External_Service_Dependency_Should_Handle_Timeouts()
{
using var cts = new CancellationTokenSource(TimeSpan.FromMilliseconds(100));
var request = new TestExternalServiceRequest { TimeoutMs = 1000 };
Assert.ThrowsAsync<TaskCanceledException>(async () =>
await _context!.SendRequestAsync(request, cts.Token).ConfigureAwait(false));
}
/// <summary>
/// 验证 CQRS 请求封装数据库写入时仍能保持事务语义上的可观察结果。
/// </summary>
[Test]
public async Task Cqrs_With_Database_Operations_Should_Handle_Transactions()
{
var testData = new List<string>();
var request = new TestDatabaseRequest { Data = "test data", Storage = testData };
var result = await _context!.SendRequestAsync(request).ConfigureAwait(false);
Assert.That(result, Is.EqualTo("Data saved successfully"));
Assert.That(testData, Contains.Item("test data"));
}
}
// 这些 CQRS/ArchitectureContext 高级场景测试需要把一组仅供当前文件使用的辅助类型共置,避免拆成多个噪声文件。
#pragma warning disable MA0048
#region Advanced Test Classes
/// <summary>
/// 处理重试请求,并在达到成功条件前累积尝试次数。
/// </summary>
public sealed class TestRetryRequestHandler : IRequestHandler<TestRetryRequest, string>
{
/// <inheritdoc />
public ValueTask<string> Handle(TestRetryRequest request, CancellationToken cancellationToken)
{
TestRetryBehavior.AttemptCount++;
if (TestRetryBehavior.AttemptCount <= request.ShouldFailTimes)
{
throw new InvalidOperationException("Simulated failure");
}
return new ValueTask<string>("Success");
}
}
/// <summary>
/// 处理瞬态错误请求,并在配置次数内模拟失败。
/// </summary>
public sealed class TestTransientErrorRequestHandler : IRequestHandler<TestTransientErrorRequest, string>
{
/// <inheritdoc />
public ValueTask<string> Handle(TestTransientErrorRequest request, CancellationToken cancellationToken)
{
// 只有在MaxErrors > 0时才增加计数器
if (request.MaxErrors > 0)
{
TestTransientErrorHandler.ErrorCount++;
if (TestTransientErrorHandler.ErrorCount <= request.MaxErrors)
{
throw new InvalidOperationException("Transient error");
}
}
return new ValueTask<string>("Success");
}
}
/// <summary>
/// 处理断路器请求,并根据失败阈值切换断路器状态。
/// </summary>
public sealed class TestCircuitBreakerRequestHandler : IRequestHandler<TestCircuitBreakerRequest, string>
{
/// <inheritdoc />
public ValueTask<string> Handle(TestCircuitBreakerRequest request, CancellationToken cancellationToken)
{
// 检查断路器状态
if (TestCircuitBreakerHandler.CircuitOpen)
{
throw new InvalidOperationException("Circuit breaker is open");
}
if (request.ShouldFail)
{
TestCircuitBreakerHandler.FailureCount++;
// 达到阈值后打开断路器
if (TestCircuitBreakerHandler.FailureCount >= 5)
{
TestCircuitBreakerHandler.CircuitOpen = true;
}
throw new InvalidOperationException("Service unavailable");
}
TestCircuitBreakerHandler.SuccessCount++;
return new ValueTask<string>("Available");
}
}
/// <summary>
/// 处理 Saga 步骤请求,并在失败时记录补偿步骤。
/// </summary>
public sealed class TestSagaStepRequestHandler : IRequestHandler<TestSagaStepRequest, string>
{
/// <inheritdoc />
public ValueTask<string> Handle(TestSagaStepRequest request, CancellationToken cancellationToken)
{
if (request.ShouldFail && request.Step == 2)
{
// 失败时执行补偿
foreach (var completedStep in request.SagaData.CompletedSteps.ToList())
{
request.SagaData.CompensatedSteps.Add(completedStep);
}
throw new InvalidOperationException($"Saga step {request.Step} failed");
}
request.SagaData.CompletedSteps.Add(request.Step);
if (request.Step == 3)
{
request.SagaData.IsCompleted = true;
}
return new ValueTask<string>($"Step {request.Step} completed");
}
}
/// <summary>
/// 处理链式请求,并返回预定义的链路完成结果。
/// </summary>
public sealed class TestChainStartRequestHandler : IRequestHandler<TestChainStartRequest, string>
{
/// <inheritdoc />
public async ValueTask<string> Handle(TestChainStartRequest request, CancellationToken cancellationToken)
{
// 模拟链式调用
await Task.Delay(10, cancellationToken).ConfigureAwait(false);
return "Chain completed: Step1 -> Step2 -> Step3";
}
}
/// <summary>
/// 处理外部服务请求,并通过延时模拟超时场景。
/// </summary>
public sealed class TestExternalServiceRequestHandler : IRequestHandler<TestExternalServiceRequest, string>
{
/// <inheritdoc />
public async ValueTask<string> Handle(TestExternalServiceRequest request, CancellationToken cancellationToken)
{
await Task.Delay(request.TimeoutMs, cancellationToken).ConfigureAwait(false);
cancellationToken.ThrowIfCancellationRequested();
return "External service response";
}
}
/// <summary>
/// 处理数据库请求,并把输入数据写入模拟存储集合。
/// </summary>
public sealed class TestDatabaseRequestHandler : IRequestHandler<TestDatabaseRequest, string>
{
/// <inheritdoc />
public ValueTask<string> Handle(TestDatabaseRequest request, CancellationToken cancellationToken)
{
request.Storage.Add(request.Data);
return new ValueTask<string>("Data saved successfully");
}
}
/// <summary>
/// 表示用于简单行为验证的测试请求。
/// </summary>
public sealed record TestBehaviorRequest : IRequest<string>
{
/// <summary>
/// 获取或初始化要原样返回的消息内容。
/// </summary>
public string Message { get; init; } = string.Empty;
}
/// <summary>
/// 处理简单行为请求,并回显请求消息。
/// </summary>
public sealed class TestBehaviorRequestHandler : IRequestHandler<TestBehaviorRequest, string>
{
/// <inheritdoc />
public ValueTask<string> Handle(TestBehaviorRequest request, CancellationToken cancellationToken)
{
return new ValueTask<string>(request.Message);
}
}
/// <summary>
/// 表示带输入校验约束的测试请求。
/// </summary>
public sealed record TestValidatedRequest : IRequest<string>
{
/// <summary>
/// 获取或初始化要验证的整数值。
/// </summary>
public int Value { get; init; }
}
/// <summary>
/// 处理带校验的请求,并在输入无效时抛出异常。
/// </summary>
public sealed class TestValidatedRequestHandler : IRequestHandler<TestValidatedRequest, string>
{
/// <inheritdoc />
public ValueTask<string> Handle(TestValidatedRequest request, CancellationToken cancellationToken)
{
// 验证输入
if (request.Value < 0)
{
throw new ArgumentException("Value must be non-negative", nameof(request));
}
return new ValueTask<string>($"Value: {request.Value}");
}
}
/// <summary>
/// 表示需要在若干次失败后才能成功的重试测试请求。
/// </summary>
public sealed record TestRetryRequest : IRequest<string>
{
/// <summary>
/// 获取或初始化在返回成功前应模拟的失败次数。
/// </summary>
public int ShouldFailTimes { get; init; }
}
/// <summary>
/// 保存重试测试的共享计数状态。
/// </summary>
public static class TestRetryBehavior
{
/// <summary>
/// 获取或设置当前请求处理期间累计的尝试次数。
/// </summary>
public static int AttemptCount { get; set; }
}
/// <summary>
/// 表示用于并发与性能验证的测试请求。
/// </summary>
public sealed record TestPerformanceRequest : IRequest<int>
{
/// <summary>
/// 获取或初始化请求的标识值。
/// </summary>
public int Id { get; init; }
/// <summary>
/// 获取或初始化模拟处理延时,单位为毫秒。
/// </summary>
public int ProcessingTimeMs { get; init; }
}
/// <summary>
/// 处理性能请求,并在延时后返回请求标识。
/// </summary>
public sealed class TestPerformanceRequestHandler : IRequestHandler<TestPerformanceRequest, int>
{
/// <inheritdoc />
public async ValueTask<int> Handle(TestPerformanceRequest request, CancellationToken cancellationToken)
{
await Task.Delay(request.ProcessingTimeMs, cancellationToken).ConfigureAwait(false);
return request.Id;
}
}
/// <summary>
/// 表示用于内存占用验证的测试请求。
/// </summary>
public sealed record TestMemoryRequest : IRequest<string>
{
/// <summary>
/// 获取或初始化用于模拟负载的数据内容。
/// </summary>
public string Data { get; init; } = string.Empty;
}
/// <summary>
/// 处理内存测试请求,并在不保留额外引用的前提下制造短期分配。
/// </summary>
public sealed class TestMemoryRequestHandler : IRequestHandler<TestMemoryRequest, string>
{
/// <inheritdoc />
public ValueTask<string> Handle(TestMemoryRequest request, CancellationToken cancellationToken)
{
// 模拟内存使用
_ = request.Data.ToCharArray(); // 创建副本但不存储
return new ValueTask<string>("Processed");
}
}
/// <summary>
/// 保存瞬态错误测试的共享计数状态。
/// </summary>
public static class TestTransientErrorHandler
{
/// <summary>
/// 获取或设置当前已模拟的错误次数。
/// </summary>
public static int ErrorCount { get; set; }
}
/// <summary>
/// 表示用于瞬态错误场景的测试请求。
/// </summary>
public sealed record TestTransientErrorRequest : IRequest<string>
{
/// <summary>
/// 获取或初始化允许连续抛出的最大错误次数。
/// </summary>
public int MaxErrors { get; init; }
}
/// <summary>
/// 保存断路器场景的共享测试状态。
/// </summary>
public static class TestCircuitBreakerHandler
{
/// <summary>
/// 获取或设置当前累计的失败次数。
/// </summary>
public static int FailureCount { get; set; }
/// <summary>
/// 获取或设置当前累计的成功次数。
/// </summary>
public static int SuccessCount { get; set; }
/// <summary>
/// 获取或设置断路器是否已处于打开状态。
/// </summary>
public static bool CircuitOpen { get; set; }
/// <summary>
/// 重置断路器测试状态,避免静态字段在测试之间互相污染。
/// </summary>
public static void Reset()
{
FailureCount = 0;
SuccessCount = 0;
CircuitOpen = false;
}
}
/// <summary>
/// 表示用于断路器场景的测试请求。
/// </summary>
public sealed record TestCircuitBreakerRequest : IRequest<string>
{
/// <summary>
/// 获取或初始化当前请求是否应主动模拟失败。
/// </summary>
public bool ShouldFail { get; init; }
}
/// <summary>
/// 保存 Saga 执行与补偿过程中的共享状态。
/// </summary>
public class SagaData
{
/// <summary>
/// 获取 Saga 已成功执行的步骤集合。
/// </summary>
public IList<int> CompletedSteps { get; } = new List<int>();
/// <summary>
/// 获取 Saga 失败后已执行补偿的步骤集合。
/// </summary>
public IList<int> CompensatedSteps { get; } = new List<int>();
/// <summary>
/// 获取或设置 Saga 是否已经完整结束。
/// </summary>
public bool IsCompleted { get; set; }
}
/// <summary>
/// 表示 Saga 中单个步骤的测试请求。
/// </summary>
public sealed record TestSagaStepRequest : IRequest<string>
{
/// <summary>
/// 获取或初始化当前要执行的 Saga 步骤编号。
/// </summary>
public int Step { get; init; }
/// <summary>
/// 获取或初始化当前 Saga 使用的共享状态对象。
/// </summary>
public SagaData SagaData { get; init; } = null!;
/// <summary>
/// 获取或初始化当前步骤是否应模拟失败。
/// </summary>
public bool ShouldFail { get; init; }
}
/// <summary>
/// 表示用于链式请求场景的起始请求。
/// </summary>
public sealed record TestChainStartRequest : IRequest<string>;
/// <summary>
/// 表示依赖外部服务响应时间的测试请求。
/// </summary>
public sealed record TestExternalServiceRequest : IRequest<string>
{
/// <summary>
/// 获取或初始化模拟外部服务所需的响应时长,单位为毫秒。
/// </summary>
public int TimeoutMs { get; init; }
}
/// <summary>
/// 表示用于模拟数据库写入的测试请求。
/// </summary>
public sealed record TestDatabaseRequest : IRequest<string>
{
/// <summary>
/// 获取或初始化要写入存储集合的数据内容。
/// </summary>
public string Data { get; init; } = string.Empty;
/// <summary>
/// 获取或初始化用于模拟数据库写入的可变存储集合,同时避免泄漏具体集合实现。
/// </summary>
public IList<string> Storage { get; init; } = new List<string>();
}
#endregion
#pragma warning restore MA0048