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; /// /// Unit tests for WorkTask entity /// 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() .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() .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() .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() .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() .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() .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 }