268 lines
9.1 KiB
Go
268 lines
9.1 KiB
Go
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)
|
|
}
|
|
})
|
|
}
|