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;
}
}