Project Init
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,462 @@
|
||||
using ColaFlow.Modules.ProjectManagement.Domain.Aggregates.ProjectAggregate;
|
||||
using ColaFlow.Modules.ProjectManagement.Domain.ValueObjects;
|
||||
using ColaFlow.Modules.ProjectManagement.Domain.Exceptions;
|
||||
using FluentAssertions;
|
||||
|
||||
namespace ColaFlow.Domain.Tests.Aggregates;
|
||||
|
||||
/// <summary>
|
||||
/// Unit tests for WorkTask entity
|
||||
/// </summary>
|
||||
public class WorkTaskTests
|
||||
{
|
||||
#region Create Tests
|
||||
|
||||
[Fact]
|
||||
public void Create_WithValidData_ShouldCreateTask()
|
||||
{
|
||||
// Arrange
|
||||
var title = "Task 1";
|
||||
var description = "Task Description";
|
||||
var storyId = StoryId.Create();
|
||||
var priority = TaskPriority.High;
|
||||
var createdBy = UserId.Create();
|
||||
|
||||
// Act
|
||||
var task = WorkTask.Create(title, description, storyId, priority, createdBy);
|
||||
|
||||
// Assert
|
||||
task.Should().NotBeNull();
|
||||
task.Title.Should().Be(title);
|
||||
task.Description.Should().Be(description);
|
||||
task.StoryId.Should().Be(storyId);
|
||||
task.Status.Should().Be(WorkItemStatus.ToDo);
|
||||
task.Priority.Should().Be(priority);
|
||||
task.CreatedBy.Should().Be(createdBy);
|
||||
task.CreatedAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(5));
|
||||
task.UpdatedAt.Should().BeNull();
|
||||
task.EstimatedHours.Should().BeNull();
|
||||
task.ActualHours.Should().BeNull();
|
||||
task.AssigneeId.Should().BeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Create_WithNullDescription_ShouldCreateTaskWithEmptyDescription()
|
||||
{
|
||||
// Arrange
|
||||
var title = "Task 1";
|
||||
string? description = null;
|
||||
var storyId = StoryId.Create();
|
||||
var priority = TaskPriority.Medium;
|
||||
var createdBy = UserId.Create();
|
||||
|
||||
// Act
|
||||
var task = WorkTask.Create(title, description!, storyId, priority, createdBy);
|
||||
|
||||
// Assert
|
||||
task.Should().NotBeNull();
|
||||
task.Description.Should().Be(string.Empty);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("")]
|
||||
[InlineData(" ")]
|
||||
[InlineData(null)]
|
||||
public void Create_WithEmptyTitle_ShouldThrowDomainException(string invalidTitle)
|
||||
{
|
||||
// Arrange
|
||||
var storyId = StoryId.Create();
|
||||
var priority = TaskPriority.Medium;
|
||||
var createdBy = UserId.Create();
|
||||
|
||||
// Act
|
||||
Action act = () => WorkTask.Create(invalidTitle, "Description", storyId, priority, createdBy);
|
||||
|
||||
// Assert
|
||||
act.Should().Throw<DomainException>()
|
||||
.WithMessage("Task title cannot be empty");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Create_WithTitleExceeding200Characters_ShouldThrowDomainException()
|
||||
{
|
||||
// Arrange
|
||||
var title = new string('A', 201);
|
||||
var storyId = StoryId.Create();
|
||||
var priority = TaskPriority.Medium;
|
||||
var createdBy = UserId.Create();
|
||||
|
||||
// Act
|
||||
Action act = () => WorkTask.Create(title, "Description", storyId, priority, createdBy);
|
||||
|
||||
// Assert
|
||||
act.Should().Throw<DomainException>()
|
||||
.WithMessage("Task title cannot exceed 200 characters");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Create_WithTitleExactly200Characters_ShouldSucceed()
|
||||
{
|
||||
// Arrange
|
||||
var title = new string('A', 200);
|
||||
var storyId = StoryId.Create();
|
||||
var priority = TaskPriority.Medium;
|
||||
var createdBy = UserId.Create();
|
||||
|
||||
// Act
|
||||
var task = WorkTask.Create(title, "Description", storyId, priority, createdBy);
|
||||
|
||||
// Assert
|
||||
task.Should().NotBeNull();
|
||||
task.Title.Should().Be(title);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Create_WithDifferentPriorities_ShouldSetCorrectPriority()
|
||||
{
|
||||
// Arrange
|
||||
var storyId = StoryId.Create();
|
||||
var createdBy = UserId.Create();
|
||||
|
||||
// Act
|
||||
var taskLow = WorkTask.Create("Task Low", "Desc", storyId, TaskPriority.Low, createdBy);
|
||||
var taskMedium = WorkTask.Create("Task Medium", "Desc", storyId, TaskPriority.Medium, createdBy);
|
||||
var taskHigh = WorkTask.Create("Task High", "Desc", storyId, TaskPriority.High, createdBy);
|
||||
var taskUrgent = WorkTask.Create("Task Urgent", "Desc", storyId, TaskPriority.Urgent, createdBy);
|
||||
|
||||
// Assert
|
||||
taskLow.Priority.Should().Be(TaskPriority.Low);
|
||||
taskMedium.Priority.Should().Be(TaskPriority.Medium);
|
||||
taskHigh.Priority.Should().Be(TaskPriority.High);
|
||||
taskUrgent.Priority.Should().Be(TaskPriority.Urgent);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region UpdateDetails Tests
|
||||
|
||||
[Fact]
|
||||
public void UpdateDetails_WithValidData_ShouldUpdateTask()
|
||||
{
|
||||
// Arrange
|
||||
var task = WorkTask.Create("Original Title", "Original Description", StoryId.Create(), TaskPriority.Medium, UserId.Create());
|
||||
var originalCreatedAt = task.CreatedAt;
|
||||
var newTitle = "Updated Title";
|
||||
var newDescription = "Updated Description";
|
||||
|
||||
// Act
|
||||
task.UpdateDetails(newTitle, newDescription);
|
||||
|
||||
// Assert
|
||||
task.Title.Should().Be(newTitle);
|
||||
task.Description.Should().Be(newDescription);
|
||||
task.CreatedAt.Should().Be(originalCreatedAt);
|
||||
task.UpdatedAt.Should().NotBeNull();
|
||||
task.UpdatedAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(5));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UpdateDetails_WithNullDescription_ShouldSetEmptyDescription()
|
||||
{
|
||||
// Arrange
|
||||
var task = WorkTask.Create("Original Title", "Original Description", StoryId.Create(), TaskPriority.Medium, UserId.Create());
|
||||
|
||||
// Act
|
||||
task.UpdateDetails("Updated Title", null!);
|
||||
|
||||
// Assert
|
||||
task.Description.Should().Be(string.Empty);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("")]
|
||||
[InlineData(" ")]
|
||||
[InlineData(null)]
|
||||
public void UpdateDetails_WithEmptyTitle_ShouldThrowDomainException(string invalidTitle)
|
||||
{
|
||||
// Arrange
|
||||
var task = WorkTask.Create("Original Title", "Original Description", StoryId.Create(), TaskPriority.Medium, UserId.Create());
|
||||
|
||||
// Act
|
||||
Action act = () => task.UpdateDetails(invalidTitle, "Updated Description");
|
||||
|
||||
// Assert
|
||||
act.Should().Throw<DomainException>()
|
||||
.WithMessage("Task title cannot be empty");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UpdateDetails_WithTitleExceeding200Characters_ShouldThrowDomainException()
|
||||
{
|
||||
// Arrange
|
||||
var task = WorkTask.Create("Original Title", "Original Description", StoryId.Create(), TaskPriority.Medium, UserId.Create());
|
||||
var title = new string('A', 201);
|
||||
|
||||
// Act
|
||||
Action act = () => task.UpdateDetails(title, "Updated Description");
|
||||
|
||||
// Assert
|
||||
act.Should().Throw<DomainException>()
|
||||
.WithMessage("Task title cannot exceed 200 characters");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region UpdateStatus Tests
|
||||
|
||||
[Fact]
|
||||
public void UpdateStatus_WithValidStatus_ShouldUpdateStatus()
|
||||
{
|
||||
// Arrange
|
||||
var task = WorkTask.Create("Task 1", "Description", StoryId.Create(), TaskPriority.Medium, UserId.Create());
|
||||
var newStatus = WorkItemStatus.InProgress;
|
||||
|
||||
// Act
|
||||
task.UpdateStatus(newStatus);
|
||||
|
||||
// Assert
|
||||
task.Status.Should().Be(newStatus);
|
||||
task.UpdatedAt.Should().NotBeNull();
|
||||
task.UpdatedAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(5));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UpdateStatus_ToAllStatuses_ShouldSucceed()
|
||||
{
|
||||
// Arrange
|
||||
var task = WorkTask.Create("Task 1", "Description", StoryId.Create(), TaskPriority.Medium, UserId.Create());
|
||||
|
||||
// Act & Assert
|
||||
task.UpdateStatus(WorkItemStatus.InProgress);
|
||||
task.Status.Should().Be(WorkItemStatus.InProgress);
|
||||
|
||||
task.UpdateStatus(WorkItemStatus.InReview);
|
||||
task.Status.Should().Be(WorkItemStatus.InReview);
|
||||
|
||||
task.UpdateStatus(WorkItemStatus.Done);
|
||||
task.Status.Should().Be(WorkItemStatus.Done);
|
||||
|
||||
task.UpdateStatus(WorkItemStatus.Blocked);
|
||||
task.Status.Should().Be(WorkItemStatus.Blocked);
|
||||
|
||||
task.UpdateStatus(WorkItemStatus.ToDo);
|
||||
task.Status.Should().Be(WorkItemStatus.ToDo);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region AssignTo Tests
|
||||
|
||||
[Fact]
|
||||
public void AssignTo_WithValidUserId_ShouldAssignTask()
|
||||
{
|
||||
// Arrange
|
||||
var task = WorkTask.Create("Task 1", "Description", StoryId.Create(), TaskPriority.Medium, UserId.Create());
|
||||
var assigneeId = UserId.Create();
|
||||
|
||||
// Act
|
||||
task.AssignTo(assigneeId);
|
||||
|
||||
// Assert
|
||||
task.AssigneeId.Should().Be(assigneeId);
|
||||
task.UpdatedAt.Should().NotBeNull();
|
||||
task.UpdatedAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(5));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AssignTo_ReassignToDifferentUser_ShouldUpdateAssignee()
|
||||
{
|
||||
// Arrange
|
||||
var task = WorkTask.Create("Task 1", "Description", StoryId.Create(), TaskPriority.Medium, UserId.Create());
|
||||
var firstAssignee = UserId.Create();
|
||||
var secondAssignee = UserId.Create();
|
||||
|
||||
// Act
|
||||
task.AssignTo(firstAssignee);
|
||||
task.AssignTo(secondAssignee);
|
||||
|
||||
// Assert
|
||||
task.AssigneeId.Should().Be(secondAssignee);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region UpdatePriority Tests
|
||||
|
||||
[Fact]
|
||||
public void UpdatePriority_WithValidPriority_ShouldUpdatePriority()
|
||||
{
|
||||
// Arrange
|
||||
var task = WorkTask.Create("Task 1", "Description", StoryId.Create(), TaskPriority.Low, UserId.Create());
|
||||
var newPriority = TaskPriority.Urgent;
|
||||
|
||||
// Act
|
||||
task.UpdatePriority(newPriority);
|
||||
|
||||
// Assert
|
||||
task.Priority.Should().Be(newPriority);
|
||||
task.UpdatedAt.Should().NotBeNull();
|
||||
task.UpdatedAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(5));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UpdatePriority_ToAllPriorities_ShouldSucceed()
|
||||
{
|
||||
// Arrange
|
||||
var task = WorkTask.Create("Task 1", "Description", StoryId.Create(), TaskPriority.Medium, UserId.Create());
|
||||
|
||||
// Act & Assert
|
||||
task.UpdatePriority(TaskPriority.Low);
|
||||
task.Priority.Should().Be(TaskPriority.Low);
|
||||
|
||||
task.UpdatePriority(TaskPriority.Medium);
|
||||
task.Priority.Should().Be(TaskPriority.Medium);
|
||||
|
||||
task.UpdatePriority(TaskPriority.High);
|
||||
task.Priority.Should().Be(TaskPriority.High);
|
||||
|
||||
task.UpdatePriority(TaskPriority.Urgent);
|
||||
task.Priority.Should().Be(TaskPriority.Urgent);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region UpdateEstimate Tests
|
||||
|
||||
[Fact]
|
||||
public void UpdateEstimate_WithValidHours_ShouldUpdateEstimate()
|
||||
{
|
||||
// Arrange
|
||||
var task = WorkTask.Create("Task 1", "Description", StoryId.Create(), TaskPriority.Medium, UserId.Create());
|
||||
var hours = 4.5m;
|
||||
|
||||
// Act
|
||||
task.UpdateEstimate(hours);
|
||||
|
||||
// Assert
|
||||
task.EstimatedHours.Should().Be(hours);
|
||||
task.UpdatedAt.Should().NotBeNull();
|
||||
task.UpdatedAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(5));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UpdateEstimate_WithZeroHours_ShouldSucceed()
|
||||
{
|
||||
// Arrange
|
||||
var task = WorkTask.Create("Task 1", "Description", StoryId.Create(), TaskPriority.Medium, UserId.Create());
|
||||
|
||||
// Act
|
||||
task.UpdateEstimate(0);
|
||||
|
||||
// Assert
|
||||
task.EstimatedHours.Should().Be(0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UpdateEstimate_WithNegativeHours_ShouldThrowDomainException()
|
||||
{
|
||||
// Arrange
|
||||
var task = WorkTask.Create("Task 1", "Description", StoryId.Create(), TaskPriority.Medium, UserId.Create());
|
||||
|
||||
// Act
|
||||
Action act = () => task.UpdateEstimate(-1);
|
||||
|
||||
// Assert
|
||||
act.Should().Throw<DomainException>()
|
||||
.WithMessage("Estimated hours cannot be negative");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UpdateEstimate_MultipleUpdates_ShouldOverwritePreviousValue()
|
||||
{
|
||||
// Arrange
|
||||
var task = WorkTask.Create("Task 1", "Description", StoryId.Create(), TaskPriority.Medium, UserId.Create());
|
||||
|
||||
// Act
|
||||
task.UpdateEstimate(4);
|
||||
task.UpdateEstimate(8);
|
||||
|
||||
// Assert
|
||||
task.EstimatedHours.Should().Be(8);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region LogActualHours Tests
|
||||
|
||||
[Fact]
|
||||
public void LogActualHours_WithValidHours_ShouldLogHours()
|
||||
{
|
||||
// Arrange
|
||||
var task = WorkTask.Create("Task 1", "Description", StoryId.Create(), TaskPriority.Medium, UserId.Create());
|
||||
var hours = 5.5m;
|
||||
|
||||
// Act
|
||||
task.LogActualHours(hours);
|
||||
|
||||
// Assert
|
||||
task.ActualHours.Should().Be(hours);
|
||||
task.UpdatedAt.Should().NotBeNull();
|
||||
task.UpdatedAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(5));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LogActualHours_WithZeroHours_ShouldSucceed()
|
||||
{
|
||||
// Arrange
|
||||
var task = WorkTask.Create("Task 1", "Description", StoryId.Create(), TaskPriority.Medium, UserId.Create());
|
||||
|
||||
// Act
|
||||
task.LogActualHours(0);
|
||||
|
||||
// Assert
|
||||
task.ActualHours.Should().Be(0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LogActualHours_WithNegativeHours_ShouldThrowDomainException()
|
||||
{
|
||||
// Arrange
|
||||
var task = WorkTask.Create("Task 1", "Description", StoryId.Create(), TaskPriority.Medium, UserId.Create());
|
||||
|
||||
// Act
|
||||
Action act = () => task.LogActualHours(-1);
|
||||
|
||||
// Assert
|
||||
act.Should().Throw<DomainException>()
|
||||
.WithMessage("Actual hours cannot be negative");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LogActualHours_MultipleUpdates_ShouldOverwritePreviousValue()
|
||||
{
|
||||
// Arrange
|
||||
var task = WorkTask.Create("Task 1", "Description", StoryId.Create(), TaskPriority.Medium, UserId.Create());
|
||||
|
||||
// Act
|
||||
task.LogActualHours(4);
|
||||
task.LogActualHours(6);
|
||||
|
||||
// Assert
|
||||
task.ActualHours.Should().Be(6);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Entity Characteristics Tests
|
||||
|
||||
[Fact]
|
||||
public void WorkTask_ShouldHaveUniqueId()
|
||||
{
|
||||
// Arrange & Act
|
||||
var storyId = StoryId.Create();
|
||||
var createdBy = UserId.Create();
|
||||
var task1 = WorkTask.Create("Task 1", "Description", storyId, TaskPriority.Medium, createdBy);
|
||||
var task2 = WorkTask.Create("Task 2", "Description", storyId, TaskPriority.Medium, createdBy);
|
||||
|
||||
// Assert
|
||||
task1.Id.Should().NotBe(task2.Id);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
Reference in New Issue
Block a user