using FluentAssertions; using Microsoft.EntityFrameworkCore; using Workflow.Application.Features.WorkflowDefinitions.Commands; using Workflow.Domain.Entities; using Workflow.Domain.Enums; using Workflow.Domain.Exceptions; using Workflow.Infrastructure.Persistence; using Xunit; namespace Workflow.Tests.Handlers; public class NodeCommandHandlerTests { private static WorkflowDbContext CreateDbContext() { var options = new DbContextOptionsBuilder() .UseInMemoryDatabase(Guid.NewGuid().ToString()) .Options; return new WorkflowDbContext(options); } private static async Task SeedDefinition(WorkflowDbContext db) { var definition = new WorkflowDefinition { Id = Guid.NewGuid(), Name = "Test Workflow", Code = $"test-wf-{Guid.NewGuid():N}", Status = DefinitionStatus.Draft, Version = 1 }; db.WorkflowDefinitions.Add(definition); await db.SaveChangesAsync(); return definition; } #region CreateNode [Fact] public async Task CreateNode_ReturnsNodeDto() { // Arrange await using var db = CreateDbContext(); var definition = await SeedDefinition(db); var handler = new CreateNodeCommandHandler(db); var command = new CreateNodeCommand( DefinitionId: definition.Id, NodeType: NodeType.Start, Name: "Start Node", Config: null, PositionX: 100, PositionY: 200 ); // Act var result = await handler.Handle(command, CancellationToken.None); // Assert result.Should().NotBeNull(); result.Name.Should().Be("Start Node"); result.NodeType.Should().Be(NodeType.Start); result.PositionX.Should().Be(100); result.PositionY.Should().Be(200); result.Config.Should().BeNull(); result.Id.Should().NotBeEmpty(); var entity = await db.WorkflowNodes.FindAsync(result.Id); entity.Should().NotBeNull(); entity!.DefinitionId.Should().Be(definition.Id); } [Fact] public async Task CreateNode_WithMissingDefinition_ThrowsNotFoundException() { // Arrange await using var db = CreateDbContext(); var handler = new CreateNodeCommandHandler(db); var command = new CreateNodeCommand( DefinitionId: Guid.NewGuid(), NodeType: NodeType.Approval, Name: "Orphan Node", Config: null, PositionX: 0, PositionY: 0 ); // Act var act = () => handler.Handle(command, CancellationToken.None); // Assert await act.Should().ThrowAsync(); } #endregion #region UpdateNode [Fact] public async Task UpdateNode_UpdatesNameAndPosition() { // Arrange await using var db = CreateDbContext(); var definition = await SeedDefinition(db); var nodeId = Guid.NewGuid(); db.WorkflowNodes.Add(new WorkflowNode { Id = nodeId, DefinitionId = definition.Id, NodeType = NodeType.Approval, Name = "Old Name", PositionX = 50, PositionY = 50 }); await db.SaveChangesAsync(); var handler = new UpdateNodeCommandHandler(db); var command = new UpdateNodeCommand( NodeId: nodeId, Name: "Updated Name", Config: "{\"timeout\": 300}", PositionX: 200, PositionY: 300 ); // Act var result = await handler.Handle(command, CancellationToken.None); // Assert result.Should().NotBeNull(); result.Name.Should().Be("Updated Name"); result.Config.Should().Be("{\"timeout\": 300}"); result.PositionX.Should().Be(200); result.PositionY.Should().Be(300); result.Id.Should().Be(nodeId); var entity = await db.WorkflowNodes.FindAsync(nodeId); entity.Should().NotBeNull(); entity!.Name.Should().Be("Updated Name"); entity.PositionX.Should().Be(200); } [Fact] public async Task UpdateNode_WithMissingNode_ThrowsNotFoundException() { // Arrange await using var db = CreateDbContext(); var handler = new UpdateNodeCommandHandler(db); var command = new UpdateNodeCommand( NodeId: Guid.NewGuid(), Name: "Ghost", Config: null, PositionX: 0, PositionY: 0 ); // Act var act = () => handler.Handle(command, CancellationToken.None); // Assert await act.Should().ThrowAsync(); } #endregion #region DeleteNode [Fact] public async Task DeleteNode_RemovesNode() { // Arrange await using var db = CreateDbContext(); var definition = await SeedDefinition(db); var nodeId = Guid.NewGuid(); db.WorkflowNodes.Add(new WorkflowNode { Id = nodeId, DefinitionId = definition.Id, NodeType = NodeType.End, Name = "To Delete", PositionX = 0, PositionY = 0 }); await db.SaveChangesAsync(); var handler = new DeleteNodeCommandHandler(db); var command = new DeleteNodeCommand(NodeId: nodeId); // Act await handler.Handle(command, CancellationToken.None); // Assert var nodes = await db.WorkflowNodes.ToListAsync(); nodes.Should().BeEmpty(); } [Fact] public async Task DeleteNode_WithMissingNode_ThrowsNotFoundException() { // Arrange await using var db = CreateDbContext(); var handler = new DeleteNodeCommandHandler(db); var command = new DeleteNodeCommand(NodeId: Guid.NewGuid()); // Act var act = () => handler.Handle(command, CancellationToken.None); // Assert await act.Should().ThrowAsync(); } #endregion }