using FluentAssertions;
using Moq;
using ColaFlow.Modules.ProjectManagement.Application.Commands.UpdateTaskStatus;
using ColaFlow.Modules.ProjectManagement.Domain.Aggregates.ProjectAggregate;
using ColaFlow.Modules.ProjectManagement.Domain.Repositories;
using ColaFlow.Modules.ProjectManagement.Domain.ValueObjects;
using ColaFlow.Modules.ProjectManagement.Domain.Exceptions;
namespace ColaFlow.Application.Tests.Commands.UpdateTaskStatus;
///
/// Tests for UpdateTaskStatusCommandHandler
///
public class UpdateTaskStatusCommandHandlerTests
{
private readonly Mock _projectRepositoryMock;
private readonly Mock _unitOfWorkMock;
private readonly UpdateTaskStatusCommandHandler _handler;
public UpdateTaskStatusCommandHandlerTests()
{
_projectRepositoryMock = new Mock();
_unitOfWorkMock = new Mock();
_handler = new UpdateTaskStatusCommandHandler(_projectRepositoryMock.Object, _unitOfWorkMock.Object);
}
[Fact]
public async Task Should_Update_Task_Status_To_InProgress_Successfully()
{
// Arrange
var userId = UserId.Create();
var project = Project.Create("Test Project", "Test Description", "TST", userId);
var epic = project.CreateEpic("Test Epic", "Epic Description", userId);
var story = epic.CreateStory("Test Story", "Story Description", TaskPriority.Medium, userId);
var task = story.CreateTask("Test Task", "Task Description", TaskPriority.Medium, userId);
var taskId = task.Id;
_projectRepositoryMock
.Setup(x => x.GetProjectWithTaskAsync(taskId, It.IsAny()))
.ReturnsAsync(project);
var command = new UpdateTaskStatusCommand
{
TaskId = taskId.Value,
NewStatus = "In Progress" // Display name with space
};
// Act
var result = await _handler.Handle(command, CancellationToken.None);
// Assert
result.Should().NotBeNull();
result.Id.Should().Be(taskId.Value);
result.Status.Should().Be("In Progress");
task.Status.Should().Be(WorkItemStatus.InProgress);
_projectRepositoryMock.Verify(x => x.Update(project), Times.Once);
_unitOfWorkMock.Verify(x => x.SaveChangesAsync(It.IsAny()), Times.Once);
}
[Fact]
public async Task Should_Update_Task_Status_To_InProgress_With_CodeName_Successfully()
{
// Arrange - This tests the bug fix for accepting "InProgress" (without space)
var userId = UserId.Create();
var project = Project.Create("Test Project", "Test Description", "TST", userId);
var epic = project.CreateEpic("Test Epic", "Epic Description", userId);
var story = epic.CreateStory("Test Story", "Story Description", TaskPriority.Medium, userId);
var task = story.CreateTask("Test Task", "Task Description", TaskPriority.Medium, userId);
var taskId = task.Id;
_projectRepositoryMock
.Setup(x => x.GetProjectWithTaskAsync(taskId, It.IsAny()))
.ReturnsAsync(project);
var command = new UpdateTaskStatusCommand
{
TaskId = taskId.Value,
NewStatus = "InProgress" // Code name without space (this was causing the bug)
};
// Act
var result = await _handler.Handle(command, CancellationToken.None);
// Assert
result.Should().NotBeNull();
result.Id.Should().Be(taskId.Value);
result.Status.Should().Be("In Progress");
task.Status.Should().Be(WorkItemStatus.InProgress);
}
[Fact]
public async Task Should_Update_Task_Status_To_Done_Successfully()
{
// Arrange
var userId = UserId.Create();
var project = Project.Create("Test Project", "Test Description", "TST", userId);
var epic = project.CreateEpic("Test Epic", "Epic Description", userId);
var story = epic.CreateStory("Test Story", "Story Description", TaskPriority.Medium, userId);
var task = story.CreateTask("Test Task", "Task Description", TaskPriority.Medium, userId);
var taskId = task.Id;
_projectRepositoryMock
.Setup(x => x.GetProjectWithTaskAsync(taskId, It.IsAny()))
.ReturnsAsync(project);
var command = new UpdateTaskStatusCommand
{
TaskId = taskId.Value,
NewStatus = "Done"
};
// Act
var result = await _handler.Handle(command, CancellationToken.None);
// Assert
result.Should().NotBeNull();
result.Status.Should().Be("Done");
task.Status.Should().Be(WorkItemStatus.Done);
}
[Fact]
public async Task Should_Update_Task_Status_To_Blocked_Successfully()
{
// Arrange
var userId = UserId.Create();
var project = Project.Create("Test Project", "Test Description", "TST", userId);
var epic = project.CreateEpic("Test Epic", "Epic Description", userId);
var story = epic.CreateStory("Test Story", "Story Description", TaskPriority.Medium, userId);
var task = story.CreateTask("Test Task", "Task Description", TaskPriority.Medium, userId);
var taskId = task.Id;
_projectRepositoryMock
.Setup(x => x.GetProjectWithTaskAsync(taskId, It.IsAny()))
.ReturnsAsync(project);
var command = new UpdateTaskStatusCommand
{
TaskId = taskId.Value,
NewStatus = "Blocked"
};
// Act
var result = await _handler.Handle(command, CancellationToken.None);
// Assert
result.Should().NotBeNull();
result.Status.Should().Be("Blocked");
task.Status.Should().Be(WorkItemStatus.Blocked);
}
[Fact]
public async Task Should_Update_Task_Status_To_InReview_Successfully()
{
// Arrange
var userId = UserId.Create();
var project = Project.Create("Test Project", "Test Description", "TST", userId);
var epic = project.CreateEpic("Test Epic", "Epic Description", userId);
var story = epic.CreateStory("Test Story", "Story Description", TaskPriority.Medium, userId);
var task = story.CreateTask("Test Task", "Task Description", TaskPriority.Medium, userId);
var taskId = task.Id;
_projectRepositoryMock
.Setup(x => x.GetProjectWithTaskAsync(taskId, It.IsAny()))
.ReturnsAsync(project);
var command = new UpdateTaskStatusCommand
{
TaskId = taskId.Value,
NewStatus = "In Review"
};
// Act
var result = await _handler.Handle(command, CancellationToken.None);
// Assert
result.Should().NotBeNull();
result.Status.Should().Be("In Review");
task.Status.Should().Be(WorkItemStatus.InReview);
}
[Fact]
public async Task Should_Throw_NotFoundException_When_Task_Not_Found()
{
// Arrange
var taskId = TaskId.Create();
_projectRepositoryMock
.Setup(x => x.GetProjectWithTaskAsync(taskId, It.IsAny()))
.ReturnsAsync((Project?)null);
var command = new UpdateTaskStatusCommand
{
TaskId = taskId.Value,
NewStatus = "In Progress"
};
// Act
Func act = async () => await _handler.Handle(command, CancellationToken.None);
// Assert
await act.Should().ThrowAsync()
.WithMessage("*Task*");
}
[Fact]
public async Task Should_Throw_Exception_When_Moving_Done_To_ToDo()
{
// Arrange
var userId = UserId.Create();
var project = Project.Create("Test Project", "Test Description", "TST", userId);
var epic = project.CreateEpic("Test Epic", "Epic Description", userId);
var story = epic.CreateStory("Test Story", "Story Description", TaskPriority.Medium, userId);
var task = story.CreateTask("Test Task", "Task Description", TaskPriority.Medium, userId);
var taskId = task.Id;
// First set task to Done
task.UpdateStatus(WorkItemStatus.Done);
_projectRepositoryMock
.Setup(x => x.GetProjectWithTaskAsync(taskId, It.IsAny()))
.ReturnsAsync(project);
var command = new UpdateTaskStatusCommand
{
TaskId = taskId.Value,
NewStatus = "To Do"
};
// Act
Func act = async () => await _handler.Handle(command, CancellationToken.None);
// Assert
await act.Should().ThrowAsync()
.WithMessage("*Cannot move a completed task back to ToDo*");
}
[Fact]
public async Task Should_Allow_Blocked_To_Any_Status()
{
// Arrange
var userId = UserId.Create();
var project = Project.Create("Test Project", "Test Description", "TST", userId);
var epic = project.CreateEpic("Test Epic", "Epic Description", userId);
var story = epic.CreateStory("Test Story", "Story Description", TaskPriority.Medium, userId);
var task = story.CreateTask("Test Task", "Task Description", TaskPriority.Medium, userId);
var taskId = task.Id;
// Set task to Blocked
task.UpdateStatus(WorkItemStatus.Blocked);
_projectRepositoryMock
.Setup(x => x.GetProjectWithTaskAsync(taskId, It.IsAny()))
.ReturnsAsync(project);
var command = new UpdateTaskStatusCommand
{
TaskId = taskId.Value,
NewStatus = "In Progress"
};
// Act
var result = await _handler.Handle(command, CancellationToken.None);
// Assert
result.Should().NotBeNull();
result.Status.Should().Be("In Progress");
task.Status.Should().Be(WorkItemStatus.InProgress);
}
[Fact]
public async Task Should_Allow_Any_Status_To_Blocked()
{
// Arrange
var userId = UserId.Create();
var project = Project.Create("Test Project", "Test Description", "TST", userId);
var epic = project.CreateEpic("Test Epic", "Epic Description", userId);
var story = epic.CreateStory("Test Story", "Story Description", TaskPriority.Medium, userId);
var task = story.CreateTask("Test Task", "Task Description", TaskPriority.Medium, userId);
var taskId = task.Id;
// Task starts as ToDo
task.Status.Should().Be(WorkItemStatus.ToDo);
_projectRepositoryMock
.Setup(x => x.GetProjectWithTaskAsync(taskId, It.IsAny()))
.ReturnsAsync(project);
var command = new UpdateTaskStatusCommand
{
TaskId = taskId.Value,
NewStatus = "Blocked"
};
// Act
var result = await _handler.Handle(command, CancellationToken.None);
// Assert
result.Should().NotBeNull();
result.Status.Should().Be("Blocked");
task.Status.Should().Be(WorkItemStatus.Blocked);
}
[Fact]
public async Task Should_Throw_Exception_For_Invalid_Status()
{
// Arrange
var userId = UserId.Create();
var project = Project.Create("Test Project", "Test Description", "TST", userId);
var epic = project.CreateEpic("Test Epic", "Epic Description", userId);
var story = epic.CreateStory("Test Story", "Story Description", TaskPriority.Medium, userId);
var task = story.CreateTask("Test Task", "Task Description", TaskPriority.Medium, userId);
var taskId = task.Id;
_projectRepositoryMock
.Setup(x => x.GetProjectWithTaskAsync(taskId, It.IsAny()))
.ReturnsAsync(project);
var command = new UpdateTaskStatusCommand
{
TaskId = taskId.Value,
NewStatus = "InvalidStatus"
};
// Act
Func act = async () => await _handler.Handle(command, CancellationToken.None);
// Assert
await act.Should().ThrowAsync()
.WithMessage("*InvalidStatus*");
}
}