work-flow/tests/Workflow.Tests/Handlers/NodeCommandHandlerTests.cs
2026-05-21 14:42:51 +08:00

216 lines
6.0 KiB
C#

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<WorkflowDbContext>()
.UseInMemoryDatabase(Guid.NewGuid().ToString())
.Options;
return new WorkflowDbContext(options);
}
private static async Task<WorkflowDefinition> 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<NotFoundException>();
}
#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<NotFoundException>();
}
#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<NotFoundException>();
}
#endregion
}