using Store.Users.Behaviors; using Store.Users.Entities; using YourNamespace; using System.Reflection; using MAX.Framework.Mediation; using MAX.Core.Mediation; using System.ComponentModel.DataAnnotations; using MAX.Core.DependencyInjection; using MAX.Framework.DependencyInjection; using MediatR; using Microsoft.Extensions.DependencyInjection; using Store.Users.Commands; using Store.Users.Contracts.Dtos; // 使用实际 Startup 注册路径,替换数据库处理器以单独验证 MediatR 管道。 var services = new ServiceCollection(); var registrarType = typeof(ServiceCollectionExtensions).Assembly .GetType("MAX.Framework.DependencyInjection.ServiceRegistrarProxy", throwOnError: true)!; var registrar = (IServiceRegistrar)Activator.CreateInstance(registrarType, services)!; new Store.Users.Startup().Register(registrar); services.AddMediatRAttributedBehaviors(typeof(ShopRenewCommand).Assembly); services.AddMediatRAttributedBehaviors(typeof(ShopRenewCommand).Assembly); // 重复扫描不重复注册 var pipelineType = typeof(IPipelineBehavior); var pipelines = services.Where(s => s.ServiceType == pipelineType).ToArray(); if (pipelines.Length != 4 || pipelines[0].ImplementationType?.Name != "ValidationBehavior`2" || pipelines[1].ImplementationType?.Name != "TransactionBehavior`2" || pipelines[2].ImplementationType?.Name != "AccountConsumptionBehavior`2" || pipelines[3].ImplementationType?.Name != "ShopOwnershipBehavior`2") throw new Exception("管道注册顺序错误,应为参数验证、事务、消费检查"); // 本测试只验证参数管道;事务与消费管道需在测试库中进行集成测试。 services.Remove(pipelines[1]); services.Remove(pipelines[2]); services.Remove(pipelines[3]); services.AddSingleton(); services.AddTransient>(sp => sp.GetRequiredService()); using var provider = services.BuildServiceProvider(); var mediator = provider.GetRequiredService(); var probe = provider.GetRequiredService(); var valid = new ShopRenewCommand("user", "shop", 1); ShopRenewCommand[] invalid = [ valid with { UserName = " " }, valid with { UserName = new string('u', 21) }, valid with { ShopId = " " }, valid with { ShopId = new string('s', 51) }, valid with { RenewDuration = 0 }, valid with { RenewDuration = 121 }, valid with { Remark = new string('r', 256) } ]; foreach (var command in invalid) { try { await mediator.Send(command); throw new Exception("无效参数未被管道拦截"); } catch (ValidationException) { } } if (probe.Calls != 0) throw new Exception("无效命令进入了处理器"); await mediator.Send(valid); await mediator.Send(valid with { UserName = " " + new string('u', 20) + " ", ShopId = new string('s', 50), RenewDuration = 120, Remark = " " + new string('r', 255) + " " }); using var cancellation = new CancellationTokenSource(); cancellation.Cancel(); try { await mediator.Send(valid, cancellation.Token); throw new Exception("已取消命令未被拦截"); } catch (OperationCanceledException) { } if (probe.Calls != 2) throw new Exception("有效命令或取消命令的调用次数错误"); var minimumQuote = ((IAccountConsumptionRequest)(valid with { RenewDuration = 3 })).CalculateConsumption(new UserEntity { ShopPointsPerMonth = 10 }); var customQuote = ((IAccountConsumptionRequest)(valid with { RenewDuration = 2 })).CalculateConsumption(new UserEntity { ShopPointsPerMonth = 45 }); if (minimumQuote != new ConsumptionQuote(90m, 30m) || customQuote != new ConsumptionQuote(90m, 45m)) throw new Exception("续费最低价格或账户自定义价格计算错误"); var reuseServices = new ServiceCollection(); reuseServices.AddMediatRAttributedBehaviors(typeof(AccountOnlyRequest).Assembly); var accountBehaviors = reuseServices.Where(s => s.ServiceType == typeof(IPipelineBehavior)).ToArray(); var shopBehaviors = reuseServices.Where(s => s.ServiceType == typeof(IPipelineBehavior)).ToArray(); if (accountBehaviors.Length != 2 || accountBehaviors[0].ImplementationType != typeof(TransactionBehavior) || accountBehaviors[1].ImplementationType != typeof(AccountConsumptionBehavior) || shopBehaviors.Length != 1 || shopBehaviors[0].ImplementationType != typeof(ShopOwnershipBehavior)) throw new Exception("非商铺消费和非消费商铺操作的管道注册未独立"); Console.WriteLine("PASS: 独立消费与商铺操作管道注册、续费计费策略验证通过。"); var orderedServices = new ServiceCollection(); orderedServices.AddSingleton(); orderedServices.AddTransient, OrderHandler>(); orderedServices.AddMediatRAttributedBehaviors(typeof(OrderRequest).Assembly); orderedServices.AddMediatRAttributedBehaviors(typeof(OrderRequest).Assembly); using var orderedProvider = orderedServices.BuildServiceProvider(); await orderedProvider.GetRequiredService().Send(new OrderRequest()); var events = orderedProvider.GetRequiredService().Events; if (!events.SequenceEqual(new[] { "Outer before", "Inner before", "Handler", "Inner after", "Outer after" })) throw new Exception("特性管道执行顺序不正确:" + string.Join(", ", events)); if (pipelines[1].ImplementationType != typeof(TransactionBehavior)) throw new Exception("开放泛型事务管道未正确闭合"); var executionContext = new UnitOfWorkExecutionContext(); var enter = typeof(UnitOfWorkExecutionContext).GetMethod("Enter", BindingFlags.Instance | BindingFlags.NonPublic)!; IFreeSql CreateOrm() => DispatchProxy.Create(); IDisposable Enter(IFreeSql orm) => (IDisposable)enter.Invoke(executionContext, [orm])!; try { _ = executionContext.Orm; throw new Exception("无事务时不应允许访问 ORM"); } catch (InvalidOperationException) { } async Task VerifyIsolation(IFreeSql orm) { using (Enter(orm)) { await Task.Yield(); if (!ReferenceEquals(executionContext.Orm, orm)) throw new Exception("并行事务上下文串用"); try { using var nested = Enter(CreateOrm()); throw new Exception("嵌套事务应被拒绝"); } catch (TargetInvocationException exception) when (exception.InnerException is InvalidOperationException) { } if (!ReferenceEquals(executionContext.Orm, orm)) throw new Exception("嵌套失败破坏了外层上下文"); } if (executionContext.IsActive) throw new Exception("执行结束后上下文未清理"); } await Task.WhenAll(VerifyIsolation(CreateOrm()), VerifyIsolation(CreateOrm())); try { using var scope = Enter(CreateOrm()); throw new ApplicationException("模拟处理器失败"); } catch (ApplicationException) { } if (executionContext.IsActive) throw new Exception("异常后上下文未清理"); Console.WriteLine("PASS: 泛型事务管道闭合、并行上下文隔离、嵌套拒绝和异常清理验证通过。"); Console.WriteLine("PASS: CallerLineNumber 顺序、真实嵌套调用与重复扫描验证通过。"); Console.WriteLine("PASS: 实际 DI 注册、7 项非法输入、有效边界参数和取消请求验证通过。"); sealed class ProbeHandler : IRequestHandler { public int Calls { get; private set; } public Task Handle(ShopRenewCommand request, CancellationToken cancellationToken) { Calls++; return Task.FromResult(new ShopRenewResponse { ShopId = request.ShopId }); } } [MediatRBehavior(typeof(OuterOrderBehavior))] [MediatRBehavior(typeof(InnerOrderBehavior))] sealed record OrderRequest : IRequest; sealed class ExecutionLog { public List Events { get; } = []; } sealed class OrderHandler(ExecutionLog log) : IRequestHandler { public Task Handle(OrderRequest request, CancellationToken cancellationToken) { log.Events.Add("Handler"); return Task.FromResult("ok"); } } sealed class OuterOrderBehavior(ExecutionLog log) : IPipelineBehavior { public async Task Handle(OrderRequest request, CancellationToken cancellationToken, RequestHandlerDelegate next) { log.Events.Add("Outer before"); var result = await next(); log.Events.Add("Outer after"); return result; } } sealed class InnerOrderBehavior(ExecutionLog log) : IPipelineBehavior { public async Task Handle(OrderRequest request, CancellationToken cancellationToken, RequestHandlerDelegate next) { log.Events.Add("Inner before"); var result = await next(); log.Events.Add("Inner after"); return result; } } public class UnusedOrmProxy : DispatchProxy { protected override object? Invoke(MethodInfo? targetMethod, object?[]? args) => throw new NotSupportedException("上下文测试不应访问数据库"); } // 非商铺消费:没有 ShopId,也不启用商铺归属管道。 [MediatRBehavior(typeof(TransactionBehavior<,>))] [MediatRBehavior(typeof(AccountConsumptionBehavior<,>))] sealed record AccountOnlyRequest(string UserName, decimal Amount) : IRequest, IAccountConsumptionRequest { public AccountConsumptionContext? ConsumptionContext { get; set; } public ConsumptionQuote CalculateConsumption(UserEntity user) => new(Amount, Amount); } // 商铺操作:不涉及余额与计费,也可用于不需要事务的读取。 [MediatRBehavior(typeof(ShopOwnershipBehavior<,>))] sealed record ShopOnlyRequest(string UserName, string ShopId) : IRequest, IShopOwnershipRequest { public ShopEntity? OwnedShop { get; set; } }