feat: add Edge CRUD commands with tests
This commit is contained in:
parent
f18a48379f
commit
1d5fd7449e
@ -0,0 +1,56 @@
|
||||
using MediatR;
|
||||
using Workflow.Application.Features.WorkflowDefinitions.DTOs;
|
||||
using Workflow.Domain.Entities;
|
||||
using Workflow.Domain.Enums;
|
||||
using Workflow.Domain.Exceptions;
|
||||
using Workflow.Infrastructure.Persistence;
|
||||
|
||||
namespace Workflow.Application.Features.WorkflowDefinitions.Commands;
|
||||
|
||||
public record CreateEdgeCommand(
|
||||
Guid DefinitionId,
|
||||
Guid SourceNodeId,
|
||||
Guid TargetNodeId,
|
||||
EdgeType EdgeType,
|
||||
string? Label,
|
||||
string? Condition,
|
||||
int Order
|
||||
) : IRequest<WorkflowEdgeDto>;
|
||||
|
||||
public class CreateEdgeCommandHandler(WorkflowDbContext db)
|
||||
: IRequestHandler<CreateEdgeCommand, WorkflowEdgeDto>
|
||||
{
|
||||
public async Task<WorkflowEdgeDto> Handle(CreateEdgeCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
var sourceNode = await db.WorkflowNodes.FindAsync([request.SourceNodeId], cancellationToken)
|
||||
?? throw new NotFoundException($"Source node '{request.SourceNodeId}' not found.");
|
||||
|
||||
var targetNode = await db.WorkflowNodes.FindAsync([request.TargetNodeId], cancellationToken)
|
||||
?? throw new NotFoundException($"Target node '{request.TargetNodeId}' not found.");
|
||||
|
||||
var entity = new WorkflowEdge
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
DefinitionId = request.DefinitionId,
|
||||
SourceNodeId = request.SourceNodeId,
|
||||
TargetNodeId = request.TargetNodeId,
|
||||
EdgeType = request.EdgeType,
|
||||
Label = request.Label,
|
||||
Condition = request.Condition,
|
||||
Order = request.Order
|
||||
};
|
||||
|
||||
db.WorkflowEdges.Add(entity);
|
||||
await db.SaveChangesAsync(cancellationToken);
|
||||
|
||||
return new WorkflowEdgeDto(
|
||||
entity.Id,
|
||||
entity.SourceNodeId,
|
||||
entity.TargetNodeId,
|
||||
entity.EdgeType,
|
||||
entity.Label,
|
||||
entity.Condition,
|
||||
entity.Order
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,22 @@
|
||||
using MediatR;
|
||||
using Workflow.Domain.Exceptions;
|
||||
using Workflow.Infrastructure.Persistence;
|
||||
|
||||
namespace Workflow.Application.Features.WorkflowDefinitions.Commands;
|
||||
|
||||
public record DeleteEdgeCommand(Guid EdgeId) : IRequest<Unit>;
|
||||
|
||||
public class DeleteEdgeCommandHandler(WorkflowDbContext db)
|
||||
: IRequestHandler<DeleteEdgeCommand, Unit>
|
||||
{
|
||||
public async Task<Unit> Handle(DeleteEdgeCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
var entity = await db.WorkflowEdges.FindAsync([request.EdgeId], cancellationToken)
|
||||
?? throw new NotFoundException($"Workflow edge '{request.EdgeId}' not found.");
|
||||
|
||||
db.WorkflowEdges.Remove(entity);
|
||||
await db.SaveChangesAsync(cancellationToken);
|
||||
|
||||
return Unit.Value;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,42 @@
|
||||
using MediatR;
|
||||
using Workflow.Application.Features.WorkflowDefinitions.DTOs;
|
||||
using Workflow.Domain.Enums;
|
||||
using Workflow.Domain.Exceptions;
|
||||
using Workflow.Infrastructure.Persistence;
|
||||
|
||||
namespace Workflow.Application.Features.WorkflowDefinitions.Commands;
|
||||
|
||||
public record UpdateEdgeCommand(
|
||||
Guid EdgeId,
|
||||
EdgeType EdgeType,
|
||||
string? Label,
|
||||
string? Condition,
|
||||
int Order
|
||||
) : IRequest<WorkflowEdgeDto>;
|
||||
|
||||
public class UpdateEdgeCommandHandler(WorkflowDbContext db)
|
||||
: IRequestHandler<UpdateEdgeCommand, WorkflowEdgeDto>
|
||||
{
|
||||
public async Task<WorkflowEdgeDto> Handle(UpdateEdgeCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
var entity = await db.WorkflowEdges.FindAsync([request.EdgeId], cancellationToken)
|
||||
?? throw new NotFoundException($"Workflow edge '{request.EdgeId}' not found.");
|
||||
|
||||
entity.EdgeType = request.EdgeType;
|
||||
entity.Label = request.Label;
|
||||
entity.Condition = request.Condition;
|
||||
entity.Order = request.Order;
|
||||
|
||||
await db.SaveChangesAsync(cancellationToken);
|
||||
|
||||
return new WorkflowEdgeDto(
|
||||
entity.Id,
|
||||
entity.SourceNodeId,
|
||||
entity.TargetNodeId,
|
||||
entity.EdgeType,
|
||||
entity.Label,
|
||||
entity.Condition,
|
||||
entity.Order
|
||||
);
|
||||
}
|
||||
}
|
||||
232
tests/Workflow.Tests/Handlers/EdgeCommandHandlerTests.cs
Normal file
232
tests/Workflow.Tests/Handlers/EdgeCommandHandlerTests.cs
Normal file
@ -0,0 +1,232 @@
|
||||
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 EdgeCommandHandlerTests
|
||||
{
|
||||
private static WorkflowDbContext CreateDbContext()
|
||||
{
|
||||
var options = new DbContextOptionsBuilder<WorkflowDbContext>()
|
||||
.UseInMemoryDatabase(Guid.NewGuid().ToString())
|
||||
.Options;
|
||||
return new WorkflowDbContext(options);
|
||||
}
|
||||
|
||||
private static async Task<(WorkflowDefinition Definition, WorkflowNode SourceNode, WorkflowNode TargetNode)> SeedDefinitionWithNodes(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);
|
||||
|
||||
var sourceNode = new WorkflowNode
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
DefinitionId = definition.Id,
|
||||
NodeType = NodeType.Start,
|
||||
Name = "Start",
|
||||
PositionX = 0,
|
||||
PositionY = 0
|
||||
};
|
||||
var targetNode = new WorkflowNode
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
DefinitionId = definition.Id,
|
||||
NodeType = NodeType.End,
|
||||
Name = "End",
|
||||
PositionX = 300,
|
||||
PositionY = 0
|
||||
};
|
||||
db.WorkflowNodes.AddRange(sourceNode, targetNode);
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
return (definition, sourceNode, targetNode);
|
||||
}
|
||||
|
||||
#region CreateEdge
|
||||
|
||||
[Fact]
|
||||
public async Task CreateEdge_ReturnsEdgeDto()
|
||||
{
|
||||
// Arrange
|
||||
await using var db = CreateDbContext();
|
||||
var (definition, sourceNode, targetNode) = await SeedDefinitionWithNodes(db);
|
||||
|
||||
var handler = new CreateEdgeCommandHandler(db);
|
||||
var command = new CreateEdgeCommand(
|
||||
DefinitionId: definition.Id,
|
||||
SourceNodeId: sourceNode.Id,
|
||||
TargetNodeId: targetNode.Id,
|
||||
EdgeType: EdgeType.Normal,
|
||||
Label: "Normal Flow",
|
||||
Condition: null,
|
||||
Order: 1
|
||||
);
|
||||
|
||||
// Act
|
||||
var result = await handler.Handle(command, CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
result.Should().NotBeNull();
|
||||
result.SourceNodeId.Should().Be(sourceNode.Id);
|
||||
result.TargetNodeId.Should().Be(targetNode.Id);
|
||||
result.EdgeType.Should().Be(EdgeType.Normal);
|
||||
result.Label.Should().Be("Normal Flow");
|
||||
result.Condition.Should().BeNull();
|
||||
result.Order.Should().Be(1);
|
||||
result.Id.Should().NotBeEmpty();
|
||||
|
||||
var entity = await db.WorkflowEdges.FindAsync(result.Id);
|
||||
entity.Should().NotBeNull();
|
||||
entity!.DefinitionId.Should().Be(definition.Id);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CreateEdge_WithMissingSource_ThrowsNotFoundException()
|
||||
{
|
||||
// Arrange
|
||||
await using var db = CreateDbContext();
|
||||
var (definition, _, targetNode) = await SeedDefinitionWithNodes(db);
|
||||
|
||||
var handler = new CreateEdgeCommandHandler(db);
|
||||
var command = new CreateEdgeCommand(
|
||||
DefinitionId: definition.Id,
|
||||
SourceNodeId: Guid.NewGuid(), // Non-existent source
|
||||
TargetNodeId: targetNode.Id,
|
||||
EdgeType: EdgeType.Normal,
|
||||
Label: null,
|
||||
Condition: null,
|
||||
Order: 0
|
||||
);
|
||||
|
||||
// Act
|
||||
var act = () => handler.Handle(command, CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
await act.Should().ThrowAsync<NotFoundException>();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region UpdateEdge
|
||||
|
||||
[Fact]
|
||||
public async Task UpdateEdge_UpdatesLabelAndCondition()
|
||||
{
|
||||
// Arrange
|
||||
await using var db = CreateDbContext();
|
||||
var (definition, sourceNode, targetNode) = await SeedDefinitionWithNodes(db);
|
||||
|
||||
var edgeId = Guid.NewGuid();
|
||||
db.WorkflowEdges.Add(new WorkflowEdge
|
||||
{
|
||||
Id = edgeId,
|
||||
DefinitionId = definition.Id,
|
||||
SourceNodeId = sourceNode.Id,
|
||||
TargetNodeId = targetNode.Id,
|
||||
EdgeType = EdgeType.Normal,
|
||||
Label = "Old Label",
|
||||
Condition = null,
|
||||
Order = 0
|
||||
});
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
var handler = new UpdateEdgeCommandHandler(db);
|
||||
var command = new UpdateEdgeCommand(
|
||||
EdgeId: edgeId,
|
||||
EdgeType: EdgeType.Approved,
|
||||
Label: "Approved Path",
|
||||
Condition: "amount > 5000",
|
||||
Order: 2
|
||||
);
|
||||
|
||||
// Act
|
||||
var result = await handler.Handle(command, CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
result.Should().NotBeNull();
|
||||
result.Id.Should().Be(edgeId);
|
||||
result.Label.Should().Be("Approved Path");
|
||||
result.Condition.Should().Be("amount > 5000");
|
||||
result.EdgeType.Should().Be(EdgeType.Approved);
|
||||
result.Order.Should().Be(2);
|
||||
result.SourceNodeId.Should().Be(sourceNode.Id);
|
||||
result.TargetNodeId.Should().Be(targetNode.Id);
|
||||
|
||||
var entity = await db.WorkflowEdges.FindAsync(edgeId);
|
||||
entity.Should().NotBeNull();
|
||||
entity!.Label.Should().Be("Approved Path");
|
||||
entity.Condition.Should().Be("amount > 5000");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task UpdateEdge_WithMissingEdge_ThrowsNotFoundException()
|
||||
{
|
||||
// Arrange
|
||||
await using var db = CreateDbContext();
|
||||
var handler = new UpdateEdgeCommandHandler(db);
|
||||
var command = new UpdateEdgeCommand(
|
||||
EdgeId: Guid.NewGuid(),
|
||||
EdgeType: EdgeType.Normal,
|
||||
Label: "Ghost",
|
||||
Condition: null,
|
||||
Order: 0
|
||||
);
|
||||
|
||||
// Act
|
||||
var act = () => handler.Handle(command, CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
await act.Should().ThrowAsync<NotFoundException>();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region DeleteEdge
|
||||
|
||||
[Fact]
|
||||
public async Task DeleteEdge_RemovesEdge()
|
||||
{
|
||||
// Arrange
|
||||
await using var db = CreateDbContext();
|
||||
var (definition, sourceNode, targetNode) = await SeedDefinitionWithNodes(db);
|
||||
|
||||
var edgeId = Guid.NewGuid();
|
||||
db.WorkflowEdges.Add(new WorkflowEdge
|
||||
{
|
||||
Id = edgeId,
|
||||
DefinitionId = definition.Id,
|
||||
SourceNodeId = sourceNode.Id,
|
||||
TargetNodeId = targetNode.Id,
|
||||
EdgeType = EdgeType.Normal,
|
||||
Label = "To Delete",
|
||||
Condition = null,
|
||||
Order = 0
|
||||
});
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
var handler = new DeleteEdgeCommandHandler(db);
|
||||
var command = new DeleteEdgeCommand(EdgeId: edgeId);
|
||||
|
||||
// Act
|
||||
await handler.Handle(command, CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
var edges = await db.WorkflowEdges.ToListAsync();
|
||||
edges.Should().BeEmpty();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user