using BuildFlow.Shared.Domain.Errors; using BuildFlow.Shared.Domain.Exceptions; using FluentAssertions; using Microsoft.EntityFrameworkCore; using NSubstitute; using Scripting.Application.Commands; using Scripting.Application.DTOs; using Scripting.Application.Handlers; using Scripting.Application.Services; using Scripting.Domain.Entities.ScriptFunction; using Scripting.Domain.Enums; using Scripting.Infrastructure.Persistence; using Xunit; namespace BuildFlow.Scripting.Tests; /// /// scripting 模块命令处理器测试(架构护栏:每个 Handler 至少 1 个测试)。 /// DisableScriptCommandHandler:真实 InMemory 覆盖成功置 Disabled 与不存在→NotFound。 /// TestScriptCommandHandler / InvokeScriptCommandHandler:仅委托 IScriptExecutionService, /// 不触发真实 Roslyn 编译执行(依赖编译/沙箱执行,需集成环境),故用 NSubstitute 桩验证委托 /// 把请求正确转发给 ExecuteTestAsync / ExecuteByNameAsync 并返回其结果。 /// public class ScriptCommandEdgeHandlerTests { // ── DisableScript ────────────────────────────────────────────────────── [Fact] public async Task DisableScript_Published_SetsDisabledAndPersists() { await using var db = ScriptingTestDbFactory.Create(); var script = await SeedScriptAsync(db, "disable-fn", ScriptStatusEnum.Published); var handler = new DisableScriptCommandHandler(db); var dto = await handler.Handle(new DisableScriptCommand(script.Id), CancellationToken.None); dto.Status.Should().Be(ScriptStatusEnum.Disabled); var entity = await db.Scripts.FindAsync(script.Id); entity.Should().NotBeNull(); entity!.Status.Should().Be(ScriptStatusEnum.Disabled); } [Fact] public async Task DisableScript_NotFound_Throws() { await using var db = ScriptingTestDbFactory.Create(); var handler = new DisableScriptCommandHandler(db); var act = async () => await handler.Handle( new DisableScriptCommand(Guid.NewGuid()), CancellationToken.None); var ex = await act.Should().ThrowAsync(); ex.Which.Code.Should().Be(ErrorCode.NotFound); } // ── TestScript(委托执行服务,前置分支无校验,仅验证委托) ─────────────── [Fact] public async Task TestScript_DelegatesToService_ReturnsResult() { var svc = Substitute.For(); var expected = new ScriptExecutionResult(ScriptExecutionStatusEnum.Success, "42", null, 7); var req = new TestScriptCommand(Guid.NewGuid(), "{\"x\":1}"); svc.ExecuteTestAsync(req.Id, req.InputJson, Arg.Any()).Returns(expected); var handler = new TestScriptCommandHandler(svc); var result = await handler.Handle(req, CancellationToken.None); result.Should().Be(expected); await svc.Received(1).ExecuteTestAsync(req.Id, req.InputJson, Arg.Any()); } // ── InvokeScript(委托执行服务,仅 Published 可调用由服务守卫) ────────── [Fact] public async Task InvokeScript_DelegatesToService_ReturnsResult() { var svc = Substitute.For(); var expected = new ScriptExecutionResult(ScriptExecutionStatusEnum.Success, "99", null, 3); var req = new InvokeScriptCommand("my-script", "{}"); svc.ExecuteByNameAsync(req.Name, req.InputJson, Arg.Any()).Returns(expected); var handler = new InvokeScriptCommandHandler(svc); var result = await handler.Handle(req, CancellationToken.None); result.Should().Be(expected); await svc.Received(1).ExecuteByNameAsync(req.Name, req.InputJson, Arg.Any()); } // ── 夹具 ─────────────────────────────────────────────────────────────── private static async Task SeedScriptAsync( ScriptingDbContext db, string name, ScriptStatusEnum status = ScriptStatusEnum.Draft) { var script = new ScriptFunctionEntity { Id = Guid.NewGuid(), Name = name, DisplayName = name, Code = "return 1;", Status = status, Version = 1, TimeoutMs = 5000, CreatedBy = "test", UpdatedBy = "test", OperatorIP = "127.0.0.1", }; db.Scripts.Add(script); await db.SaveChangesAsync(); return script; } }