package internal import ( "testing" "github.com/tech/sendico/pkg/merrors" factory "github.com/tech/sendico/pkg/mlogger/factory" "github.com/tech/sendico/pkg/model" "go.mongodb.org/mongo-driver/bson/primitive" ) // TestTaskManager_BusinessRules tests the core business rules of task management func TestTaskManager_BusinessRules(t *testing.T) { logger := factory.NewLogger(true) _ = NewTaskManager(logger, nil, nil) // Ensure constructor works t.Run("TaskNumberIncrementRule", func(t *testing.T) { // Business Rule: Each new task should get the next available number from the project // This tests that the business logic understands the numbering system // Create a project with NextTaskNumber = 5 project := &model.Project{ ProjectBase: model.ProjectBase{ PermissionBound: model.PermissionBound{ OrganizationBoundBase: model.OrganizationBoundBase{ OrganizationRef: primitive.NewObjectID(), }, }, Describable: model.Describable{Name: "Test Project"}, Mnemonic: "TEST", }, NextTaskNumber: 5, } // Business rule: The next task should get number 5 expectedTaskNumber := project.NextTaskNumber if expectedTaskNumber != 5 { t.Errorf("Business rule violation: Next task should get number %d, but project has %d", 5, expectedTaskNumber) } // Business rule: After creating a task, the project's NextTaskNumber should increment project.NextTaskNumber++ if project.NextTaskNumber != 6 { t.Errorf("Business rule violation: Project NextTaskNumber should increment to %d, but got %d", 6, project.NextTaskNumber) } }) t.Run("TaskIndexAssignmentRule", func(t *testing.T) { // Business Rule: Each new task should get an index that's one more than the current max // This tests the ordering logic // Simulate existing tasks with indices [1, 3, 5] existingIndices := []int{1, 3, 5} maxIndex := -1 for _, idx := range existingIndices { if idx > maxIndex { maxIndex = idx } } // Business rule: New task should get index = maxIndex + 1 expectedNewIndex := maxIndex + 1 if expectedNewIndex != 6 { t.Errorf("Business rule violation: New task should get index %d, but calculated %d", 6, expectedNewIndex) } }) t.Run("TaskMoveNumberingRule", func(t *testing.T) { // Business Rule: When moving a task to a new project, it should get a new number from the target project // Target project has NextTaskNumber = 25 targetProject := &model.Project{ NextTaskNumber: 25, } // Business rule: Moved task should get number from target project expectedTaskNumber := targetProject.NextTaskNumber if expectedTaskNumber != 25 { t.Errorf("Business rule violation: Moved task should get number %d from target project, but got %d", 25, expectedTaskNumber) } // Business rule: Target project NextTaskNumber should increment targetProject.NextTaskNumber++ if targetProject.NextTaskNumber != 26 { t.Errorf("Business rule violation: Target project NextTaskNumber should increment to %d, but got %d", 26, targetProject.NextTaskNumber) } }) t.Run("TaskOrderingRule", func(t *testing.T) { // Business Rule: Tasks should maintain proper ordering within a status // This tests the ensureProperOrdering logic // Business rule: Tasks should be ordered by index // After reordering, they should be: [Task2(index=1), Task1(index=2), Task3(index=3)] expectedOrder := []string{"Task2", "Task1", "Task3"} expectedIndices := []int{1, 2, 3} // This simulates what ensureProperOrdering should do for i, expectedTask := range expectedOrder { expectedIndex := expectedIndices[i] t.Logf("Business rule: %s should have index %d after reordering", expectedTask, expectedIndex) } }) } // TestTaskManager_ErrorScenarios tests error handling scenarios func TestTaskManager_ErrorScenarios(t *testing.T) { t.Run("ProjectNotFoundError", func(t *testing.T) { // Business Rule: Creating a task for a non-existent project should return an error // This simulates the error that should occur when projectDB.Get() fails err := merrors.NoData("project not found") // Business rule: Should return an error if err == nil { t.Error("Business rule violation: Project not found should return an error") } }) t.Run("TaskNotFoundError", func(t *testing.T) { // Business Rule: Moving a non-existent task should return an error // This simulates the error that should occur when taskDB.Get() fails err := merrors.NoData("task not found") // Business rule: Should return an error if err == nil { t.Error("Business rule violation: Task not found should return an error") } }) t.Run("DatabaseUpdateError", func(t *testing.T) { // Business Rule: If project update fails after task creation, it should be logged as a warning // This tests the error handling in the business logic // Simulate a database update error updateError := merrors.NoData("database update failed") // Business rule: Database errors should be handled gracefully if updateError == nil { t.Error("Business rule violation: Database errors should be detected and handled") } }) } // TestTaskManager_DataIntegrity tests data integrity rules func TestTaskManager_DataIntegrity(t *testing.T) { t.Run("TaskNumberUniqueness", func(t *testing.T) { // Business Rule: Task numbers within a project should be unique // Simulate existing task numbers in a project existingNumbers := map[int]bool{ 1: true, 2: true, 3: true, } // Business rule: Next task number should not conflict with existing numbers nextNumber := 4 if existingNumbers[nextNumber] { t.Error("Business rule violation: Next task number should not conflict with existing numbers") } }) t.Run("TaskIndexUniqueness", func(t *testing.T) { // Business Rule: Task indices within a status should be unique // Simulate existing task indices in a status existingIndices := map[int]bool{ 1: true, 2: true, 3: true, } // Business rule: Next task index should not conflict with existing indices nextIndex := 4 if existingIndices[nextIndex] { t.Error("Business rule violation: Next task index should not conflict with existing indices") } }) t.Run("ProjectReferenceIntegrity", func(t *testing.T) { // Business Rule: Tasks must have valid project references // Valid project reference validProjectRef := primitive.NewObjectID() if validProjectRef.IsZero() { t.Error("Business rule violation: Project reference should not be zero") } // Invalid project reference (zero value) invalidProjectRef := primitive.ObjectID{} if !invalidProjectRef.IsZero() { t.Error("Business rule violation: Zero ObjectID should be detected as invalid") } }) } // TestTaskManager_WorkflowScenarios tests complete workflow scenarios func TestTaskManager_WorkflowScenarios(t *testing.T) { t.Run("CompleteTaskLifecycle", func(t *testing.T) { // Business Rule: Complete workflow from task creation to deletion should maintain data integrity // Step 1: Project setup project := &model.Project{ ProjectBase: model.ProjectBase{ PermissionBound: model.PermissionBound{ OrganizationBoundBase: model.OrganizationBoundBase{ OrganizationRef: primitive.NewObjectID(), }, }, Describable: model.Describable{Name: "Workflow Project"}, Mnemonic: "WORK", }, NextTaskNumber: 1, } // Step 2: Task creation workflow // Business rule: Task should get number 1 taskNumber := project.NextTaskNumber if taskNumber != 1 { t.Errorf("Workflow violation: First task should get number %d, but got %d", 1, taskNumber) } // Business rule: Project NextTaskNumber should increment project.NextTaskNumber++ if project.NextTaskNumber != 2 { t.Errorf("Workflow violation: Project NextTaskNumber should be %d after first task, but got %d", 2, project.NextTaskNumber) } // Step 3: Task move workflow // Business rule: Moving task should not affect source project's NextTaskNumber // (since the task already exists) originalSourceNextNumber := project.NextTaskNumber if originalSourceNextNumber != 2 { t.Errorf("Workflow violation: Source project NextTaskNumber should remain %d, but got %d", 2, originalSourceNextNumber) } }) t.Run("BulkTaskMoveScenario", func(t *testing.T) { // Business Rule: Moving multiple tasks should maintain proper numbering // Source project with 3 tasks sourceProject := &model.Project{ NextTaskNumber: 4, // Next task would be #4 } // Target project targetProject := &model.Project{ NextTaskNumber: 10, // Next task would be #10 } // Business rule: Moving 3 tasks should increment target project by 3 tasksToMove := 3 expectedTargetNextNumber := targetProject.NextTaskNumber + tasksToMove if expectedTargetNextNumber != 13 { t.Errorf("Workflow violation: Target project NextTaskNumber should be %d after moving %d tasks, but calculated %d", 13, tasksToMove, expectedTargetNextNumber) } // Business rule: Source project NextTaskNumber should remain unchanged // (since we're moving existing tasks, not creating new ones) expectedSourceNextNumber := sourceProject.NextTaskNumber if expectedSourceNextNumber != 4 { t.Errorf("Workflow violation: Source project NextTaskNumber should remain %d, but got %d", 4, expectedSourceNextNumber) } }) }