using BuildFlow.Shared.Domain.Errors; using FluentAssertions; using Microsoft.EntityFrameworkCore; using Workflow.Application.Features.WorkflowTasks.Commands; using Workflow.Application.Features.WorkflowTasks.Handlers; using Workflow.Domain.Entities; using Workflow.Domain.Enums; using Xunit; using TaskStatus = global::Workflow.Domain.Enums.TaskStatus; namespace BuildFlow.Workflow.Tests.Handlers; public class AddSignerCommandHandlerTests { private static WorkflowDbContext CreateDbContext() { var options = new DbContextOptionsBuilder() .UseInMemoryDatabase(Guid.NewGuid().ToString()) .Options; var db = new WorkflowDbContext(options); db.SavingChanges += AuditStub.OnSave; return db; } private static WorkflowTask SeedTask(WorkflowDbContext db, Guid assigneeId, TaskStatus status) { var task = new WorkflowTask { Id = Guid.NewGuid(), InstanceId = Guid.NewGuid(), TokenId = Guid.NewGuid(), NodeId = Guid.NewGuid(), Title = "Approve", AssigneeId = assigneeId, Status = status }; db.WorkflowTasks.Add(task); return task; } [Fact] public async Task AddSigner_Before_DelegatesOriginalAndCreatesSignerTask() { await using var db = CreateDbContext(); var current = Guid.NewGuid(); var signer = Guid.NewGuid(); var task = SeedTask(db, current, TaskStatus.Pending); await db.SaveChangesAsync(); var handler = new AddSignerCommandHandler(db); var command = new AddSignerCommand(task.Id, current, signer, SignPosition.Before, null); await handler.Handle(command, CancellationToken.None); var stored = await db.WorkflowTasks.FindAsync(task.Id); stored!.Status.Should().Be(TaskStatus.Delegated); var signerTasks = await db.WorkflowTasks .Where(t => t.AssigneeId == signer && t.Status == TaskStatus.Pending) .ToListAsync(); signerTasks.Should().HaveCount(1); } [Fact] public async Task AddSigner_MissingTask_ThrowsNotFound() { await using var db = CreateDbContext(); var handler = new AddSignerCommandHandler(db); var command = new AddSignerCommand(Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid(), SignPosition.Before, null); var act = async () => await handler.Handle(command, CancellationToken.None); var ex = await act.Should().ThrowAsync(); ex.Which.Code.Should().Be(ErrorCode.NotFound); } [Fact] public async Task AddSigner_NonPendingTask_ThrowsInvalidState() { await using var db = CreateDbContext(); var task = SeedTask(db, Guid.NewGuid(), TaskStatus.Approved); await db.SaveChangesAsync(); var handler = new AddSignerCommandHandler(db); var command = new AddSignerCommand(task.Id, task.AssigneeId!.Value, Guid.NewGuid(), SignPosition.Before, null); var act = async () => await handler.Handle(command, CancellationToken.None); var ex = await act.Should().ThrowAsync(); ex.Which.Code.Should().Be(ErrorCode.InvalidState); } [Fact] public async Task AddSigner_NotCurrentHandler_ThrowsForbidden() { await using var db = CreateDbContext(); var task = SeedTask(db, Guid.NewGuid(), TaskStatus.Pending); await db.SaveChangesAsync(); var handler = new AddSignerCommandHandler(db); var command = new AddSignerCommand(task.Id, Guid.NewGuid(), Guid.NewGuid(), SignPosition.Before, null); var act = async () => await handler.Handle(command, CancellationToken.None); var ex = await act.Should().ThrowAsync(); ex.Which.Code.Should().Be(ErrorCode.Forbidden); } [Fact] public async Task AddSigner_SelfSign_ThrowsBusiness() { await using var db = CreateDbContext(); var current = Guid.NewGuid(); var task = SeedTask(db, current, TaskStatus.Pending); await db.SaveChangesAsync(); var handler = new AddSignerCommandHandler(db); var command = new AddSignerCommand(task.Id, current, current, SignPosition.Before, null); var act = async () => await handler.Handle(command, CancellationToken.None); var ex = await act.Should().ThrowAsync(); ex.Which.Code.Should().Be(ErrorCode.Business); } }