Add git implementation of task manager
Change-Id: I1e0925e54fa167af9459eceea7a2cae082bc4504
diff --git a/server/tm/git_tm/git_task_manager_test.go b/server/tm/git_tm/git_task_manager_test.go
new file mode 100644
index 0000000..cd37838
--- /dev/null
+++ b/server/tm/git_tm/git_task_manager_test.go
@@ -0,0 +1,1021 @@
+package git_tm
+
+import (
+ "context"
+ "os"
+ "path/filepath"
+ "testing"
+ "time"
+
+ "github.com/iomodo/staff/git"
+ "github.com/iomodo/staff/tm"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+// Test helper functions
+func setupTestDir(t *testing.T) (string, func()) {
+ tempDir, err := os.MkdirTemp("", "git-task-manager-test")
+ require.NoError(t, err)
+
+ cleanup := func() {
+ os.RemoveAll(tempDir)
+ }
+
+ return tempDir, cleanup
+}
+
+func createTestTaskManager(t *testing.T, repoPath string) (*GitTaskManager, git.GitInterface) {
+ // Initialize git repository
+ gitImpl := git.DefaultGit(repoPath)
+ ctx := context.Background()
+
+ err := gitImpl.Init(ctx, repoPath)
+ require.NoError(t, err)
+
+ // Set up git user config for commits
+ userConfig := git.UserConfig{
+ Name: "Test User",
+ Email: "test@example.com",
+ }
+ err = gitImpl.SetUserConfig(ctx, userConfig)
+ require.NoError(t, err)
+
+ gtm := NewGitTaskManager(gitImpl, repoPath)
+ return gtm, gitImpl
+}
+
+// Test cases
+func TestNewGitTaskManager(t *testing.T) {
+ tempDir, cleanup := setupTestDir(t)
+ defer cleanup()
+
+ gitImpl := git.DefaultGit(tempDir)
+ gtm := NewGitTaskManager(gitImpl, tempDir)
+
+ assert.NotNil(t, gtm)
+ assert.Equal(t, gitImpl, gtm.git)
+ assert.Equal(t, tempDir, gtm.repoPath)
+ assert.Equal(t, filepath.Join(tempDir, "tasks"), gtm.tasksDir)
+}
+
+func TestEnsureTasksDir(t *testing.T) {
+ tempDir, cleanup := setupTestDir(t)
+ defer cleanup()
+
+ gtm, _ := createTestTaskManager(t, tempDir)
+
+ // Test creating tasks directory
+ err := gtm.ensureTasksDir()
+ assert.NoError(t, err)
+
+ // Verify directory exists
+ _, err = os.Stat(gtm.tasksDir)
+ assert.NoError(t, err)
+
+ // Test creating again (should not error)
+ err = gtm.ensureTasksDir()
+ assert.NoError(t, err)
+}
+
+func TestGenerateTaskID(t *testing.T) {
+ tempDir, cleanup := setupTestDir(t)
+ defer cleanup()
+
+ gtm, _ := createTestTaskManager(t, tempDir)
+
+ id1 := gtm.generateTaskID()
+ id2 := gtm.generateTaskID()
+
+ assert.NotEmpty(t, id1)
+ assert.NotEmpty(t, id2)
+ assert.NotEqual(t, id1, id2)
+ assert.Contains(t, id1, "task-")
+}
+
+func TestTaskToMarkdown(t *testing.T) {
+ tempDir, cleanup := setupTestDir(t)
+ defer cleanup()
+
+ gtm, _ := createTestTaskManager(t, tempDir)
+
+ now := time.Now()
+ dueDate := now.Add(24 * time.Hour)
+ completedAt := now.Add(12 * time.Hour)
+
+ task := &tm.Task{
+ ID: "test-task-123",
+ Title: "Test Task",
+ Description: "This is a test task",
+ Owner: tm.Owner{
+ ID: "user123",
+ Name: "Test User",
+ },
+ Status: tm.StatusToDo,
+ Priority: tm.PriorityHigh,
+ CreatedAt: now,
+ UpdatedAt: now,
+ DueDate: &dueDate,
+ CompletedAt: &completedAt,
+ }
+
+ markdown, err := gtm.taskToMarkdown(task)
+ assert.NoError(t, err)
+ assert.NotEmpty(t, markdown)
+ assert.Contains(t, markdown, "---")
+ assert.Contains(t, markdown, "id: test-task-123")
+ assert.Contains(t, markdown, "title: Test Task")
+ assert.Contains(t, markdown, "description: This is a test task")
+ assert.Contains(t, markdown, "owner_id: user123")
+ assert.Contains(t, markdown, "owner_name: Test User")
+ assert.Contains(t, markdown, "status: todo")
+ assert.Contains(t, markdown, "priority: high")
+ assert.Contains(t, markdown, "# Task Description")
+ assert.Contains(t, markdown, "This is a test task")
+}
+
+func TestParseTaskFromMarkdown(t *testing.T) {
+ tempDir, cleanup := setupTestDir(t)
+ defer cleanup()
+
+ gtm, _ := createTestTaskManager(t, tempDir)
+
+ markdown := `---
+id: test-task-123
+title: Test Task
+description: This is a test task
+owner_id: user123
+owner_name: Test User
+status: todo
+priority: high
+created_at: 2023-01-01T00:00:00Z
+updated_at: 2023-01-01T00:00:00Z
+due_date: 2023-01-02T00:00:00Z
+completed_at: 2023-01-01T12:00:00Z
+---
+
+# Task Description
+
+This is a test task
+`
+
+ task, err := gtm.parseTaskFromMarkdown(markdown)
+ assert.NoError(t, err)
+ assert.NotNil(t, task)
+ assert.Equal(t, "test-task-123", task.ID)
+ assert.Equal(t, "Test Task", task.Title)
+ assert.Equal(t, "This is a test task", task.Description)
+ assert.Equal(t, "user123", task.Owner.ID)
+ assert.Equal(t, "Test User", task.Owner.Name)
+ assert.Equal(t, tm.StatusToDo, task.Status)
+ assert.Equal(t, tm.PriorityHigh, task.Priority)
+}
+
+func TestParseTaskFromMarkdownInvalid(t *testing.T) {
+ tempDir, cleanup := setupTestDir(t)
+ defer cleanup()
+
+ gtm, _ := createTestTaskManager(t, tempDir)
+
+ // Test invalid markdown format
+ invalidMarkdown := "This is not valid markdown"
+
+ task, err := gtm.parseTaskFromMarkdown(invalidMarkdown)
+ assert.Error(t, err)
+ assert.Nil(t, task)
+ assert.Contains(t, err.Error(), "invalid markdown format")
+}
+
+func TestWriteAndReadTaskFile(t *testing.T) {
+ tempDir, cleanup := setupTestDir(t)
+ defer cleanup()
+
+ gtm, _ := createTestTaskManager(t, tempDir)
+
+ // Ensure tasks directory exists
+ err := gtm.ensureTasksDir()
+ require.NoError(t, err)
+
+ // Create test task
+ task := &tm.Task{
+ ID: "test-task-123",
+ Title: "Test Task",
+ Description: "This is a test task",
+ Owner: tm.Owner{
+ ID: "user123",
+ Name: "Test User",
+ },
+ Status: tm.StatusToDo,
+ Priority: tm.PriorityHigh,
+ CreatedAt: time.Now(),
+ UpdatedAt: time.Now(),
+ }
+
+ // Write task file
+ err = gtm.writeTaskFile(task)
+ assert.NoError(t, err)
+
+ // Verify file exists
+ filePath := filepath.Join(gtm.tasksDir, task.ID+".md")
+ _, err = os.Stat(filePath)
+ assert.NoError(t, err)
+
+ // Read task file
+ readTask, err := gtm.readTaskFile(task.ID)
+ assert.NoError(t, err)
+ assert.NotNil(t, readTask)
+ assert.Equal(t, task.ID, readTask.ID)
+ assert.Equal(t, task.Title, readTask.Title)
+ assert.Equal(t, task.Description, readTask.Description)
+ assert.Equal(t, task.Owner.ID, readTask.Owner.ID)
+ assert.Equal(t, task.Owner.Name, readTask.Owner.Name)
+ assert.Equal(t, task.Status, readTask.Status)
+ assert.Equal(t, task.Priority, readTask.Priority)
+}
+
+func TestReadTaskFileNotFound(t *testing.T) {
+ tempDir, cleanup := setupTestDir(t)
+ defer cleanup()
+
+ gtm, _ := createTestTaskManager(t, tempDir)
+
+ // Try to read non-existent task
+ task, err := gtm.readTaskFile("non-existent-task")
+ assert.Error(t, err)
+ assert.Nil(t, task)
+ assert.Equal(t, tm.ErrTaskNotFound, err)
+}
+
+func TestListTaskFiles(t *testing.T) {
+ tempDir, cleanup := setupTestDir(t)
+ defer cleanup()
+
+ gtm, _ := createTestTaskManager(t, tempDir)
+
+ // Ensure tasks directory exists
+ err := gtm.ensureTasksDir()
+ require.NoError(t, err)
+
+ // Create some test task files
+ taskIDs := []string{"task-1", "task-2", "task-3"}
+ for _, id := range taskIDs {
+ task := &tm.Task{
+ ID: id,
+ Title: "Test Task " + id,
+ Description: "Test task description",
+ Owner: tm.Owner{
+ ID: "user123",
+ Name: "Test User",
+ },
+ Status: tm.StatusToDo,
+ Priority: tm.PriorityMedium,
+ CreatedAt: time.Now(),
+ UpdatedAt: time.Now(),
+ }
+ err = gtm.writeTaskFile(task)
+ require.NoError(t, err)
+ }
+
+ // Create a non-task file
+ nonTaskFile := filepath.Join(gtm.tasksDir, "readme.txt")
+ err = os.WriteFile(nonTaskFile, []byte("This is not a task"), 0644)
+ require.NoError(t, err)
+
+ // List task files
+ taskFiles, err := gtm.listTaskFiles()
+ assert.NoError(t, err)
+ assert.Len(t, taskFiles, 3)
+
+ // Verify all task IDs are present
+ for _, id := range taskIDs {
+ assert.Contains(t, taskFiles, id)
+ }
+}
+
+func TestListTaskFilesEmpty(t *testing.T) {
+ tempDir, cleanup := setupTestDir(t)
+ defer cleanup()
+
+ gtm, _ := createTestTaskManager(t, tempDir)
+
+ // List task files in non-existent directory
+ taskFiles, err := gtm.listTaskFiles()
+ assert.NoError(t, err)
+ assert.Empty(t, taskFiles)
+}
+
+func TestCommitTaskChange(t *testing.T) {
+ tempDir, cleanup := setupTestDir(t)
+ defer cleanup()
+
+ gtm, gitImpl := createTestTaskManager(t, tempDir)
+
+ // Create a test task file first
+ task := &tm.Task{
+ ID: "test-task-123",
+ Title: "Test Task",
+ Description: "Test description",
+ Owner: tm.Owner{
+ ID: "user123",
+ Name: "Test User",
+ },
+ Status: tm.StatusToDo,
+ Priority: tm.PriorityMedium,
+ CreatedAt: time.Now(),
+ UpdatedAt: time.Now(),
+ }
+
+ err := gtm.ensureTasksDir()
+ require.NoError(t, err)
+ err = gtm.writeTaskFile(task)
+ require.NoError(t, err)
+
+ // Test successful commit
+ err = gtm.commitTaskChange("test-task-123", "created")
+ assert.NoError(t, err)
+
+ // Verify commit was created
+ ctx := context.Background()
+ commits, err := gitImpl.Log(ctx, git.LogOptions{MaxCount: 1})
+ assert.NoError(t, err)
+ if len(commits) > 0 {
+ assert.Contains(t, commits[0].Message, "test-task-123")
+ assert.Contains(t, commits[0].Message, "created")
+ }
+}
+
+func TestCreateTask(t *testing.T) {
+ tempDir, cleanup := setupTestDir(t)
+ defer cleanup()
+
+ gtm, gitImpl := createTestTaskManager(t, tempDir)
+
+ ctx := context.Background()
+ req := &tm.TaskCreateRequest{
+ Title: "New Test Task",
+ Description: "This is a new test task",
+ OwnerID: "user123",
+ Priority: tm.PriorityHigh,
+ }
+
+ task, err := gtm.CreateTask(ctx, req)
+ assert.NoError(t, err)
+ assert.NotNil(t, task)
+
+ // Verify task properties
+ assert.NotEmpty(t, task.ID)
+ assert.Contains(t, task.ID, "task-")
+ assert.Equal(t, req.Title, task.Title)
+ assert.Equal(t, req.Description, task.Description)
+ assert.Equal(t, req.OwnerID, task.Owner.ID)
+ assert.Equal(t, req.OwnerID, task.Owner.Name) // TODO: Should look up actual name
+ assert.Equal(t, tm.StatusToDo, task.Status)
+ assert.Equal(t, req.Priority, task.Priority)
+ assert.False(t, task.CreatedAt.IsZero())
+ assert.False(t, task.UpdatedAt.IsZero())
+
+ // Verify git commit was created
+ commits, err := gitImpl.Log(ctx, git.LogOptions{MaxCount: 1})
+ assert.NoError(t, err)
+ if len(commits) > 0 {
+ assert.Contains(t, commits[0].Message, task.ID)
+ assert.Contains(t, commits[0].Message, "created")
+ }
+}
+
+func TestCreateTaskInvalidData(t *testing.T) {
+ tempDir, cleanup := setupTestDir(t)
+ defer cleanup()
+
+ gtm, _ := createTestTaskManager(t, tempDir)
+
+ ctx := context.Background()
+
+ // Test empty title
+ req := &tm.TaskCreateRequest{
+ Title: "",
+ OwnerID: "user123",
+ }
+
+ task, err := gtm.CreateTask(ctx, req)
+ assert.Error(t, err)
+ assert.Nil(t, task)
+ assert.Equal(t, tm.ErrInvalidTaskData, err)
+
+ // Test empty owner ID
+ req = &tm.TaskCreateRequest{
+ Title: "Valid Title",
+ OwnerID: "",
+ }
+
+ task, err = gtm.CreateTask(ctx, req)
+ assert.Error(t, err)
+ assert.Nil(t, task)
+ assert.Equal(t, tm.ErrInvalidOwner, err)
+}
+
+func TestGetTask(t *testing.T) {
+ tempDir, cleanup := setupTestDir(t)
+ defer cleanup()
+
+ gtm, _ := createTestTaskManager(t, tempDir)
+
+ // Create a test task
+ task := &tm.Task{
+ ID: "test-task-123",
+ Title: "Test Task",
+ Description: "Test task description",
+ Owner: tm.Owner{
+ ID: "user123",
+ Name: "Test User",
+ },
+ Status: tm.StatusToDo,
+ Priority: tm.PriorityMedium,
+ CreatedAt: time.Now(),
+ UpdatedAt: time.Now(),
+ }
+
+ err := gtm.ensureTasksDir()
+ require.NoError(t, err)
+ err = gtm.writeTaskFile(task)
+ require.NoError(t, err)
+
+ // Get the task
+ ctx := context.Background()
+ retrievedTask, err := gtm.GetTask(ctx, task.ID)
+ assert.NoError(t, err)
+ assert.NotNil(t, retrievedTask)
+ assert.Equal(t, task.ID, retrievedTask.ID)
+ assert.Equal(t, task.Title, retrievedTask.Title)
+}
+
+func TestGetTaskNotFound(t *testing.T) {
+ tempDir, cleanup := setupTestDir(t)
+ defer cleanup()
+
+ gtm, _ := createTestTaskManager(t, tempDir)
+
+ ctx := context.Background()
+ task, err := gtm.GetTask(ctx, "non-existent-task")
+ assert.Error(t, err)
+ assert.Nil(t, task)
+ assert.Equal(t, tm.ErrTaskNotFound, err)
+}
+
+func TestUpdateTask(t *testing.T) {
+ tempDir, cleanup := setupTestDir(t)
+ defer cleanup()
+
+ gtm, gitImpl := createTestTaskManager(t, tempDir)
+
+ // Create a test task
+ originalTask := &tm.Task{
+ ID: "test-task-123",
+ Title: "Original Title",
+ Description: "Original description",
+ Owner: tm.Owner{
+ ID: "user123",
+ Name: "Original User",
+ },
+ Status: tm.StatusToDo,
+ Priority: tm.PriorityLow,
+ CreatedAt: time.Now().Add(-time.Hour),
+ UpdatedAt: time.Now().Add(-time.Hour),
+ }
+
+ err := gtm.ensureTasksDir()
+ require.NoError(t, err)
+ err = gtm.writeTaskFile(originalTask)
+ require.NoError(t, err)
+
+ // Commit the initial task
+ err = gtm.commitTaskChange(originalTask.ID, "created")
+ require.NoError(t, err)
+
+ // Update the task
+ ctx := context.Background()
+ newTitle := "Updated Title"
+ newDescription := "Updated description"
+ newStatus := tm.StatusInProgress
+ newPriority := tm.PriorityHigh
+ newOwnerID := "user456"
+
+ req := &tm.TaskUpdateRequest{
+ Title: &newTitle,
+ Description: &newDescription,
+ Status: &newStatus,
+ Priority: &newPriority,
+ OwnerID: &newOwnerID,
+ }
+
+ updatedTask, err := gtm.UpdateTask(ctx, originalTask.ID, req)
+ assert.NoError(t, err)
+ assert.NotNil(t, updatedTask)
+
+ // Verify updated properties
+ assert.Equal(t, newTitle, updatedTask.Title)
+ assert.Equal(t, newDescription, updatedTask.Description)
+ assert.Equal(t, newStatus, updatedTask.Status)
+ assert.Equal(t, newPriority, updatedTask.Priority)
+ assert.Equal(t, newOwnerID, updatedTask.Owner.ID)
+ assert.Equal(t, newOwnerID, updatedTask.Owner.Name)
+
+ // Verify timestamps were updated
+ assert.True(t, updatedTask.UpdatedAt.After(originalTask.UpdatedAt))
+
+ // Verify git commit was created
+ commits, err := gitImpl.Log(ctx, git.LogOptions{MaxCount: 2})
+ assert.NoError(t, err)
+ if len(commits) > 0 {
+ assert.Contains(t, commits[0].Message, "updated")
+ }
+}
+
+func TestUpdateTaskNotFound(t *testing.T) {
+ tempDir, cleanup := setupTestDir(t)
+ defer cleanup()
+
+ gtm, _ := createTestTaskManager(t, tempDir)
+
+ ctx := context.Background()
+ newTitle := "Updated Title"
+ req := &tm.TaskUpdateRequest{
+ Title: &newTitle,
+ }
+
+ task, err := gtm.UpdateTask(ctx, "non-existent-task", req)
+ assert.Error(t, err)
+ assert.Nil(t, task)
+ assert.Equal(t, tm.ErrTaskNotFound, err)
+}
+
+func TestUpdateTaskNoChanges(t *testing.T) {
+ tempDir, cleanup := setupTestDir(t)
+ defer cleanup()
+
+ gtm, _ := createTestTaskManager(t, tempDir)
+
+ // Create a test task
+ originalTask := &tm.Task{
+ ID: "test-task-123",
+ Title: "Test Task",
+ Description: "Test description",
+ Owner: tm.Owner{
+ ID: "user123",
+ Name: "Test User",
+ },
+ Status: tm.StatusToDo,
+ Priority: tm.PriorityMedium,
+ CreatedAt: time.Now().Add(-time.Hour),
+ UpdatedAt: time.Now().Add(-time.Hour),
+ }
+
+ err := gtm.ensureTasksDir()
+ require.NoError(t, err)
+ err = gtm.writeTaskFile(originalTask)
+ require.NoError(t, err)
+
+ // Update with no changes
+ ctx := context.Background()
+ req := &tm.TaskUpdateRequest{}
+
+ updatedTask, err := gtm.UpdateTask(ctx, originalTask.ID, req)
+ assert.NoError(t, err)
+ assert.NotNil(t, updatedTask)
+
+ // Verify no changes were made
+ assert.Equal(t, originalTask.Title, updatedTask.Title)
+ assert.Equal(t, originalTask.Description, updatedTask.Description)
+ assert.Equal(t, originalTask.Status, updatedTask.Status)
+ assert.Equal(t, originalTask.Priority, updatedTask.Priority)
+ assert.Equal(t, originalTask.Owner.ID, updatedTask.Owner.ID)
+}
+
+func TestUpdateTaskStatusTimestamps(t *testing.T) {
+ tempDir, cleanup := setupTestDir(t)
+ defer cleanup()
+
+ gtm, _ := createTestTaskManager(t, tempDir)
+
+ // Create a test task
+ task := &tm.Task{
+ ID: "test-task-123",
+ Title: "Test Task",
+ Description: "Test description",
+ Owner: tm.Owner{
+ ID: "user123",
+ Name: "Test User",
+ },
+ Status: tm.StatusToDo,
+ Priority: tm.PriorityMedium,
+ CreatedAt: time.Now().Add(-time.Hour),
+ UpdatedAt: time.Now().Add(-time.Hour),
+ }
+
+ err := gtm.ensureTasksDir()
+ require.NoError(t, err)
+ err = gtm.writeTaskFile(task)
+ require.NoError(t, err)
+
+ ctx := context.Background()
+
+ // Test completing a task
+ completedStatus := tm.StatusCompleted
+ req := &tm.TaskUpdateRequest{
+ Status: &completedStatus,
+ }
+
+ updatedTask, err := gtm.UpdateTask(ctx, task.ID, req)
+ assert.NoError(t, err)
+ assert.NotNil(t, updatedTask)
+ assert.Equal(t, tm.StatusCompleted, updatedTask.Status)
+ assert.NotNil(t, updatedTask.CompletedAt)
+
+ // Test archiving a task
+ archivedStatus := tm.StatusArchived
+ req = &tm.TaskUpdateRequest{
+ Status: &archivedStatus,
+ }
+
+ updatedTask, err = gtm.UpdateTask(ctx, task.ID, req)
+ assert.NoError(t, err)
+ assert.NotNil(t, updatedTask)
+ assert.Equal(t, tm.StatusArchived, updatedTask.Status)
+ assert.NotNil(t, updatedTask.ArchivedAt)
+}
+
+func TestArchiveTask(t *testing.T) {
+ tempDir, cleanup := setupTestDir(t)
+ defer cleanup()
+
+ gtm, _ := createTestTaskManager(t, tempDir)
+
+ // Create a test task
+ task := &tm.Task{
+ ID: "test-task-123",
+ Title: "Test Task",
+ Description: "Test description",
+ Owner: tm.Owner{
+ ID: "user123",
+ Name: "Test User",
+ },
+ Status: tm.StatusToDo,
+ Priority: tm.PriorityMedium,
+ CreatedAt: time.Now().Add(-time.Hour),
+ UpdatedAt: time.Now().Add(-time.Hour),
+ }
+
+ err := gtm.ensureTasksDir()
+ require.NoError(t, err)
+ err = gtm.writeTaskFile(task)
+ require.NoError(t, err)
+
+ // Archive the task
+ ctx := context.Background()
+ err = gtm.ArchiveTask(ctx, task.ID)
+ assert.NoError(t, err)
+
+ // Verify task was archived
+ archivedTask, err := gtm.GetTask(ctx, task.ID)
+ assert.NoError(t, err)
+ assert.Equal(t, tm.StatusArchived, archivedTask.Status)
+ assert.NotNil(t, archivedTask.ArchivedAt)
+}
+
+func TestStartTask(t *testing.T) {
+ tempDir, cleanup := setupTestDir(t)
+ defer cleanup()
+
+ gtm, _ := createTestTaskManager(t, tempDir)
+
+ // Create a test task
+ task := &tm.Task{
+ ID: "test-task-123",
+ Title: "Test Task",
+ Description: "Test description",
+ Owner: tm.Owner{
+ ID: "user123",
+ Name: "Test User",
+ },
+ Status: tm.StatusToDo,
+ Priority: tm.PriorityMedium,
+ CreatedAt: time.Now().Add(-time.Hour),
+ UpdatedAt: time.Now().Add(-time.Hour),
+ }
+
+ err := gtm.ensureTasksDir()
+ require.NoError(t, err)
+ err = gtm.writeTaskFile(task)
+ require.NoError(t, err)
+
+ // Start the task
+ ctx := context.Background()
+ startedTask, err := gtm.StartTask(ctx, task.ID)
+ assert.NoError(t, err)
+ assert.NotNil(t, startedTask)
+ assert.Equal(t, tm.StatusInProgress, startedTask.Status)
+}
+
+func TestCompleteTask(t *testing.T) {
+ tempDir, cleanup := setupTestDir(t)
+ defer cleanup()
+
+ gtm, _ := createTestTaskManager(t, tempDir)
+
+ // Create a test task
+ task := &tm.Task{
+ ID: "test-task-123",
+ Title: "Test Task",
+ Description: "Test description",
+ Owner: tm.Owner{
+ ID: "user123",
+ Name: "Test User",
+ },
+ Status: tm.StatusInProgress,
+ Priority: tm.PriorityMedium,
+ CreatedAt: time.Now().Add(-time.Hour),
+ UpdatedAt: time.Now().Add(-time.Hour),
+ }
+
+ err := gtm.ensureTasksDir()
+ require.NoError(t, err)
+ err = gtm.writeTaskFile(task)
+ require.NoError(t, err)
+
+ // Complete the task
+ ctx := context.Background()
+ completedTask, err := gtm.CompleteTask(ctx, task.ID)
+ assert.NoError(t, err)
+ assert.NotNil(t, completedTask)
+ assert.Equal(t, tm.StatusCompleted, completedTask.Status)
+ assert.NotNil(t, completedTask.CompletedAt)
+}
+
+func TestListTasks(t *testing.T) {
+ tempDir, cleanup := setupTestDir(t)
+ defer cleanup()
+
+ gtm, _ := createTestTaskManager(t, tempDir)
+
+ // Create test tasks
+ tasks := []*tm.Task{
+ {
+ ID: "task-1",
+ Title: "Task 1",
+ Description: "First task",
+ Owner: tm.Owner{ID: "user1", Name: "User 1"},
+ Status: tm.StatusToDo,
+ Priority: tm.PriorityHigh,
+ CreatedAt: time.Now().Add(-2 * time.Hour),
+ UpdatedAt: time.Now().Add(-2 * time.Hour),
+ },
+ {
+ ID: "task-2",
+ Title: "Task 2",
+ Description: "Second task",
+ Owner: tm.Owner{ID: "user2", Name: "User 2"},
+ Status: tm.StatusInProgress,
+ Priority: tm.PriorityMedium,
+ CreatedAt: time.Now().Add(-1 * time.Hour),
+ UpdatedAt: time.Now().Add(-1 * time.Hour),
+ },
+ {
+ ID: "task-3",
+ Title: "Task 3",
+ Description: "Third task",
+ Owner: tm.Owner{ID: "user1", Name: "User 1"},
+ Status: tm.StatusCompleted,
+ Priority: tm.PriorityLow,
+ CreatedAt: time.Now(),
+ UpdatedAt: time.Now(),
+ },
+ }
+
+ err := gtm.ensureTasksDir()
+ require.NoError(t, err)
+
+ for _, task := range tasks {
+ err = gtm.writeTaskFile(task)
+ require.NoError(t, err)
+ }
+
+ ctx := context.Background()
+
+ // Test listing all tasks
+ taskList, err := gtm.ListTasks(ctx, nil, 0, 10)
+ assert.NoError(t, err)
+ assert.NotNil(t, taskList)
+ assert.Len(t, taskList.Tasks, 3)
+ assert.Equal(t, 3, taskList.TotalCount)
+ assert.Equal(t, 0, taskList.Page)
+ assert.Equal(t, 10, taskList.PageSize)
+ assert.False(t, taskList.HasMore)
+
+ // Test pagination
+ taskList, err = gtm.ListTasks(ctx, nil, 0, 2)
+ assert.NoError(t, err)
+ assert.Len(t, taskList.Tasks, 2)
+ assert.Equal(t, 3, taskList.TotalCount)
+ assert.True(t, taskList.HasMore)
+
+ // Test filtering by owner
+ ownerFilter := &tm.TaskFilter{OwnerID: stringPtr("user1")}
+ taskList, err = gtm.ListTasks(ctx, ownerFilter, 0, 10)
+ assert.NoError(t, err)
+ assert.Len(t, taskList.Tasks, 2)
+
+ // Test filtering by status
+ statusFilter := &tm.TaskFilter{Status: taskStatusPtr(tm.StatusToDo)}
+ taskList, err = gtm.ListTasks(ctx, statusFilter, 0, 10)
+ assert.NoError(t, err)
+ assert.Len(t, taskList.Tasks, 1)
+ assert.Equal(t, "task-1", taskList.Tasks[0].ID)
+
+ // Test filtering by priority
+ priorityFilter := &tm.TaskFilter{Priority: taskPriorityPtr(tm.PriorityHigh)}
+ taskList, err = gtm.ListTasks(ctx, priorityFilter, 0, 10)
+ assert.NoError(t, err)
+ assert.Len(t, taskList.Tasks, 1)
+ assert.Equal(t, "task-1", taskList.Tasks[0].ID)
+}
+
+func TestGetTasksByOwner(t *testing.T) {
+ tempDir, cleanup := setupTestDir(t)
+ defer cleanup()
+
+ gtm, _ := createTestTaskManager(t, tempDir)
+
+ // Create test tasks
+ tasks := []*tm.Task{
+ {
+ ID: "task-1",
+ Title: "Task 1",
+ Owner: tm.Owner{ID: "user1", Name: "User 1"},
+ Status: tm.StatusToDo,
+ Priority: tm.PriorityHigh,
+ CreatedAt: time.Now().Add(-2 * time.Hour),
+ UpdatedAt: time.Now().Add(-2 * time.Hour),
+ },
+ {
+ ID: "task-2",
+ Title: "Task 2",
+ Owner: tm.Owner{ID: "user2", Name: "User 2"},
+ Status: tm.StatusInProgress,
+ Priority: tm.PriorityMedium,
+ CreatedAt: time.Now().Add(-1 * time.Hour),
+ UpdatedAt: time.Now().Add(-1 * time.Hour),
+ },
+ {
+ ID: "task-3",
+ Title: "Task 3",
+ Owner: tm.Owner{ID: "user1", Name: "User 1"},
+ Status: tm.StatusCompleted,
+ Priority: tm.PriorityLow,
+ CreatedAt: time.Now(),
+ UpdatedAt: time.Now(),
+ },
+ }
+
+ err := gtm.ensureTasksDir()
+ require.NoError(t, err)
+
+ for _, task := range tasks {
+ err = gtm.writeTaskFile(task)
+ require.NoError(t, err)
+ }
+
+ ctx := context.Background()
+
+ // Get tasks by owner
+ taskList, err := gtm.GetTasksByOwner(ctx, "user1", 0, 10)
+ assert.NoError(t, err)
+ assert.NotNil(t, taskList)
+ assert.Len(t, taskList.Tasks, 2)
+
+ for _, task := range taskList.Tasks {
+ assert.Equal(t, "user1", task.Owner.ID)
+ }
+}
+
+func TestGetTasksByStatus(t *testing.T) {
+ tempDir, cleanup := setupTestDir(t)
+ defer cleanup()
+
+ gtm, _ := createTestTaskManager(t, tempDir)
+
+ // Create test tasks
+ tasks := []*tm.Task{
+ {
+ ID: "task-1",
+ Title: "Task 1",
+ Owner: tm.Owner{ID: "user1", Name: "User 1"},
+ Status: tm.StatusToDo,
+ Priority: tm.PriorityHigh,
+ CreatedAt: time.Now().Add(-2 * time.Hour),
+ UpdatedAt: time.Now().Add(-2 * time.Hour),
+ },
+ {
+ ID: "task-2",
+ Title: "Task 2",
+ Owner: tm.Owner{ID: "user2", Name: "User 2"},
+ Status: tm.StatusInProgress,
+ Priority: tm.PriorityMedium,
+ CreatedAt: time.Now().Add(-1 * time.Hour),
+ UpdatedAt: time.Now().Add(-1 * time.Hour),
+ },
+ {
+ ID: "task-3",
+ Title: "Task 3",
+ Owner: tm.Owner{ID: "user1", Name: "User 1"},
+ Status: tm.StatusCompleted,
+ Priority: tm.PriorityLow,
+ CreatedAt: time.Now(),
+ UpdatedAt: time.Now(),
+ },
+ }
+
+ err := gtm.ensureTasksDir()
+ require.NoError(t, err)
+
+ for _, task := range tasks {
+ err = gtm.writeTaskFile(task)
+ require.NoError(t, err)
+ }
+
+ ctx := context.Background()
+
+ // Get tasks by status
+ taskList, err := gtm.GetTasksByStatus(ctx, tm.StatusToDo, 0, 10)
+ assert.NoError(t, err)
+ assert.NotNil(t, taskList)
+ assert.Len(t, taskList.Tasks, 1)
+ assert.Equal(t, tm.StatusToDo, taskList.Tasks[0].Status)
+}
+
+func TestGetTasksByPriority(t *testing.T) {
+ tempDir, cleanup := setupTestDir(t)
+ defer cleanup()
+
+ gtm, _ := createTestTaskManager(t, tempDir)
+
+ // Create test tasks
+ tasks := []*tm.Task{
+ {
+ ID: "task-1",
+ Title: "Task 1",
+ Owner: tm.Owner{ID: "user1", Name: "User 1"},
+ Status: tm.StatusToDo,
+ Priority: tm.PriorityHigh,
+ CreatedAt: time.Now().Add(-2 * time.Hour),
+ UpdatedAt: time.Now().Add(-2 * time.Hour),
+ },
+ {
+ ID: "task-2",
+ Title: "Task 2",
+ Owner: tm.Owner{ID: "user2", Name: "User 2"},
+ Status: tm.StatusInProgress,
+ Priority: tm.PriorityMedium,
+ CreatedAt: time.Now().Add(-1 * time.Hour),
+ UpdatedAt: time.Now().Add(-1 * time.Hour),
+ },
+ {
+ ID: "task-3",
+ Title: "Task 3",
+ Owner: tm.Owner{ID: "user1", Name: "User 1"},
+ Status: tm.StatusCompleted,
+ Priority: tm.PriorityLow,
+ CreatedAt: time.Now(),
+ UpdatedAt: time.Now(),
+ },
+ }
+
+ err := gtm.ensureTasksDir()
+ require.NoError(t, err)
+
+ for _, task := range tasks {
+ err = gtm.writeTaskFile(task)
+ require.NoError(t, err)
+ }
+
+ ctx := context.Background()
+
+ // Get tasks by priority
+ taskList, err := gtm.GetTasksByPriority(ctx, tm.PriorityHigh, 0, 10)
+ assert.NoError(t, err)
+ assert.NotNil(t, taskList)
+ assert.Len(t, taskList.Tasks, 1)
+ assert.Equal(t, tm.PriorityHigh, taskList.Tasks[0].Priority)
+}
+
+// Helper functions for creating pointers to string, TaskStatus, and TaskPriority
+func stringPtr(s string) *string {
+ return &s
+}
+
+func taskStatusPtr(status tm.TaskStatus) *tm.TaskStatus {
+ return &status
+}
+
+func taskPriorityPtr(priority tm.TaskPriority) *tm.TaskPriority {
+ return &priority
+}