216 lines
6.0 KiB
C#
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
|
|
}
|