blob: 033e11c419c8351c7e0ead17d86474ff335901f8 [file] [log] [blame]
iomodo3b89bdf2025-07-25 15:19:22 +04001package git_tm
2
3import (
4 "context"
iomodo0c203b12025-07-26 19:44:57 +04005 "log/slog"
iomodo3b89bdf2025-07-25 15:19:22 +04006 "os"
7 "path/filepath"
8 "testing"
9 "time"
10
11 "github.com/iomodo/staff/git"
12 "github.com/iomodo/staff/tm"
13 "github.com/stretchr/testify/assert"
14 "github.com/stretchr/testify/require"
15)
16
17// Test helper functions
18func setupTestDir(t *testing.T) (string, func()) {
19 tempDir, err := os.MkdirTemp("", "git-task-manager-test")
20 require.NoError(t, err)
21
22 cleanup := func() {
23 os.RemoveAll(tempDir)
24 }
25
26 return tempDir, cleanup
27}
28
29func createTestTaskManager(t *testing.T, repoPath string) (*GitTaskManager, git.GitInterface) {
30 // Initialize git repository
31 gitImpl := git.DefaultGit(repoPath)
32 ctx := context.Background()
33
34 err := gitImpl.Init(ctx, repoPath)
35 require.NoError(t, err)
36
37 // Set up git user config for commits
38 userConfig := git.UserConfig{
39 Name: "Test User",
40 Email: "test@example.com",
41 }
42 err = gitImpl.SetUserConfig(ctx, userConfig)
43 require.NoError(t, err)
44
iomodo0c203b12025-07-26 19:44:57 +040045 // Create logger for testing
46 logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelInfo}))
47
48 gtm := NewGitTaskManagerWithLogger(gitImpl, repoPath, logger)
iomodo3b89bdf2025-07-25 15:19:22 +040049 return gtm, gitImpl
50}
51
52// Test cases
53func TestNewGitTaskManager(t *testing.T) {
54 tempDir, cleanup := setupTestDir(t)
55 defer cleanup()
56
57 gitImpl := git.DefaultGit(tempDir)
iomodo0c203b12025-07-26 19:44:57 +040058
59 // Create logger for testing
60 logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelInfo}))
61
62 gtm := NewGitTaskManagerWithLogger(gitImpl, tempDir, logger)
iomodo3b89bdf2025-07-25 15:19:22 +040063
64 assert.NotNil(t, gtm)
65 assert.Equal(t, gitImpl, gtm.git)
66 assert.Equal(t, tempDir, gtm.repoPath)
67 assert.Equal(t, filepath.Join(tempDir, "tasks"), gtm.tasksDir)
68}
69
70func TestEnsureTasksDir(t *testing.T) {
71 tempDir, cleanup := setupTestDir(t)
72 defer cleanup()
73
74 gtm, _ := createTestTaskManager(t, tempDir)
75
76 // Test creating tasks directory
77 err := gtm.ensureTasksDir()
78 assert.NoError(t, err)
79
80 // Verify directory exists
81 _, err = os.Stat(gtm.tasksDir)
82 assert.NoError(t, err)
83
84 // Test creating again (should not error)
85 err = gtm.ensureTasksDir()
86 assert.NoError(t, err)
87}
88
89func TestGenerateTaskID(t *testing.T) {
90 tempDir, cleanup := setupTestDir(t)
91 defer cleanup()
92
93 gtm, _ := createTestTaskManager(t, tempDir)
94
95 id1 := gtm.generateTaskID()
96 id2 := gtm.generateTaskID()
97
98 assert.NotEmpty(t, id1)
99 assert.NotEmpty(t, id2)
100 assert.NotEqual(t, id1, id2)
101 assert.Contains(t, id1, "task-")
102}
103
104func TestTaskToMarkdown(t *testing.T) {
105 tempDir, cleanup := setupTestDir(t)
106 defer cleanup()
107
108 gtm, _ := createTestTaskManager(t, tempDir)
109
110 now := time.Now()
111 dueDate := now.Add(24 * time.Hour)
112 completedAt := now.Add(12 * time.Hour)
113
114 task := &tm.Task{
115 ID: "test-task-123",
116 Title: "Test Task",
117 Description: "This is a test task",
118 Owner: tm.Owner{
119 ID: "user123",
120 Name: "Test User",
121 },
122 Status: tm.StatusToDo,
123 Priority: tm.PriorityHigh,
124 CreatedAt: now,
125 UpdatedAt: now,
126 DueDate: &dueDate,
127 CompletedAt: &completedAt,
128 }
129
130 markdown, err := gtm.taskToMarkdown(task)
131 assert.NoError(t, err)
132 assert.NotEmpty(t, markdown)
133 assert.Contains(t, markdown, "---")
134 assert.Contains(t, markdown, "id: test-task-123")
135 assert.Contains(t, markdown, "title: Test Task")
136 assert.Contains(t, markdown, "description: This is a test task")
137 assert.Contains(t, markdown, "owner_id: user123")
138 assert.Contains(t, markdown, "owner_name: Test User")
139 assert.Contains(t, markdown, "status: todo")
140 assert.Contains(t, markdown, "priority: high")
141 assert.Contains(t, markdown, "# Task Description")
142 assert.Contains(t, markdown, "This is a test task")
143}
144
145func TestParseTaskFromMarkdown(t *testing.T) {
146 tempDir, cleanup := setupTestDir(t)
147 defer cleanup()
148
149 gtm, _ := createTestTaskManager(t, tempDir)
150
151 markdown := `---
152id: test-task-123
153title: Test Task
154description: This is a test task
155owner_id: user123
156owner_name: Test User
157status: todo
158priority: high
159created_at: 2023-01-01T00:00:00Z
160updated_at: 2023-01-01T00:00:00Z
161due_date: 2023-01-02T00:00:00Z
162completed_at: 2023-01-01T12:00:00Z
163---
164
165# Task Description
166
167This is a test task
168`
169
170 task, err := gtm.parseTaskFromMarkdown(markdown)
171 assert.NoError(t, err)
172 assert.NotNil(t, task)
173 assert.Equal(t, "test-task-123", task.ID)
174 assert.Equal(t, "Test Task", task.Title)
175 assert.Equal(t, "This is a test task", task.Description)
176 assert.Equal(t, "user123", task.Owner.ID)
177 assert.Equal(t, "Test User", task.Owner.Name)
178 assert.Equal(t, tm.StatusToDo, task.Status)
179 assert.Equal(t, tm.PriorityHigh, task.Priority)
180}
181
182func TestParseTaskFromMarkdownInvalid(t *testing.T) {
183 tempDir, cleanup := setupTestDir(t)
184 defer cleanup()
185
186 gtm, _ := createTestTaskManager(t, tempDir)
187
188 // Test invalid markdown format
189 invalidMarkdown := "This is not valid markdown"
190
191 task, err := gtm.parseTaskFromMarkdown(invalidMarkdown)
192 assert.Error(t, err)
193 assert.Nil(t, task)
194 assert.Contains(t, err.Error(), "invalid markdown format")
195}
196
197func TestWriteAndReadTaskFile(t *testing.T) {
198 tempDir, cleanup := setupTestDir(t)
199 defer cleanup()
200
201 gtm, _ := createTestTaskManager(t, tempDir)
202
203 // Ensure tasks directory exists
204 err := gtm.ensureTasksDir()
205 require.NoError(t, err)
206
207 // Create test task
208 task := &tm.Task{
209 ID: "test-task-123",
210 Title: "Test Task",
211 Description: "This is a test task",
212 Owner: tm.Owner{
213 ID: "user123",
214 Name: "Test User",
215 },
216 Status: tm.StatusToDo,
217 Priority: tm.PriorityHigh,
218 CreatedAt: time.Now(),
219 UpdatedAt: time.Now(),
220 }
221
222 // Write task file
223 err = gtm.writeTaskFile(task)
224 assert.NoError(t, err)
225
226 // Verify file exists
227 filePath := filepath.Join(gtm.tasksDir, task.ID+".md")
228 _, err = os.Stat(filePath)
229 assert.NoError(t, err)
230
231 // Read task file
232 readTask, err := gtm.readTaskFile(task.ID)
233 assert.NoError(t, err)
234 assert.NotNil(t, readTask)
235 assert.Equal(t, task.ID, readTask.ID)
236 assert.Equal(t, task.Title, readTask.Title)
237 assert.Equal(t, task.Description, readTask.Description)
238 assert.Equal(t, task.Owner.ID, readTask.Owner.ID)
239 assert.Equal(t, task.Owner.Name, readTask.Owner.Name)
240 assert.Equal(t, task.Status, readTask.Status)
241 assert.Equal(t, task.Priority, readTask.Priority)
242}
243
244func TestReadTaskFileNotFound(t *testing.T) {
245 tempDir, cleanup := setupTestDir(t)
246 defer cleanup()
247
248 gtm, _ := createTestTaskManager(t, tempDir)
249
250 // Try to read non-existent task
251 task, err := gtm.readTaskFile("non-existent-task")
252 assert.Error(t, err)
253 assert.Nil(t, task)
254 assert.Equal(t, tm.ErrTaskNotFound, err)
255}
256
257func TestListTaskFiles(t *testing.T) {
258 tempDir, cleanup := setupTestDir(t)
259 defer cleanup()
260
261 gtm, _ := createTestTaskManager(t, tempDir)
262
263 // Ensure tasks directory exists
264 err := gtm.ensureTasksDir()
265 require.NoError(t, err)
266
267 // Create some test task files
268 taskIDs := []string{"task-1", "task-2", "task-3"}
269 for _, id := range taskIDs {
270 task := &tm.Task{
271 ID: id,
272 Title: "Test Task " + id,
273 Description: "Test task description",
274 Owner: tm.Owner{
275 ID: "user123",
276 Name: "Test User",
277 },
278 Status: tm.StatusToDo,
279 Priority: tm.PriorityMedium,
280 CreatedAt: time.Now(),
281 UpdatedAt: time.Now(),
282 }
283 err = gtm.writeTaskFile(task)
284 require.NoError(t, err)
285 }
286
287 // Create a non-task file
288 nonTaskFile := filepath.Join(gtm.tasksDir, "readme.txt")
289 err = os.WriteFile(nonTaskFile, []byte("This is not a task"), 0644)
290 require.NoError(t, err)
291
292 // List task files
293 taskFiles, err := gtm.listTaskFiles()
294 assert.NoError(t, err)
295 assert.Len(t, taskFiles, 3)
296
297 // Verify all task IDs are present
298 for _, id := range taskIDs {
299 assert.Contains(t, taskFiles, id)
300 }
301}
302
303func TestListTaskFilesEmpty(t *testing.T) {
304 tempDir, cleanup := setupTestDir(t)
305 defer cleanup()
306
307 gtm, _ := createTestTaskManager(t, tempDir)
308
309 // List task files in non-existent directory
310 taskFiles, err := gtm.listTaskFiles()
311 assert.NoError(t, err)
312 assert.Empty(t, taskFiles)
313}
314
315func TestCommitTaskChange(t *testing.T) {
316 tempDir, cleanup := setupTestDir(t)
317 defer cleanup()
318
319 gtm, gitImpl := createTestTaskManager(t, tempDir)
320
321 // Create a test task file first
322 task := &tm.Task{
323 ID: "test-task-123",
324 Title: "Test Task",
325 Description: "Test description",
326 Owner: tm.Owner{
327 ID: "user123",
328 Name: "Test User",
329 },
330 Status: tm.StatusToDo,
331 Priority: tm.PriorityMedium,
332 CreatedAt: time.Now(),
333 UpdatedAt: time.Now(),
334 }
335
336 err := gtm.ensureTasksDir()
337 require.NoError(t, err)
338 err = gtm.writeTaskFile(task)
339 require.NoError(t, err)
340
341 // Test successful commit
iomodo97555d02025-07-27 15:07:14 +0400342 err = gtm.commitTaskChange("test-task-123", "created", task.Owner)
iomodo3b89bdf2025-07-25 15:19:22 +0400343 assert.NoError(t, err)
344
345 // Verify commit was created
346 ctx := context.Background()
347 commits, err := gitImpl.Log(ctx, git.LogOptions{MaxCount: 1})
348 assert.NoError(t, err)
349 if len(commits) > 0 {
350 assert.Contains(t, commits[0].Message, "test-task-123")
351 assert.Contains(t, commits[0].Message, "created")
352 }
353}
354
355func TestCreateTask(t *testing.T) {
356 tempDir, cleanup := setupTestDir(t)
357 defer cleanup()
358
359 gtm, gitImpl := createTestTaskManager(t, tempDir)
360
361 ctx := context.Background()
362 req := &tm.TaskCreateRequest{
363 Title: "New Test Task",
364 Description: "This is a new test task",
365 OwnerID: "user123",
366 Priority: tm.PriorityHigh,
367 }
368
369 task, err := gtm.CreateTask(ctx, req)
370 assert.NoError(t, err)
371 assert.NotNil(t, task)
372
373 // Verify task properties
374 assert.NotEmpty(t, task.ID)
375 assert.Contains(t, task.ID, "task-")
376 assert.Equal(t, req.Title, task.Title)
377 assert.Equal(t, req.Description, task.Description)
378 assert.Equal(t, req.OwnerID, task.Owner.ID)
379 assert.Equal(t, req.OwnerID, task.Owner.Name) // TODO: Should look up actual name
380 assert.Equal(t, tm.StatusToDo, task.Status)
381 assert.Equal(t, req.Priority, task.Priority)
382 assert.False(t, task.CreatedAt.IsZero())
383 assert.False(t, task.UpdatedAt.IsZero())
384
385 // Verify git commit was created
386 commits, err := gitImpl.Log(ctx, git.LogOptions{MaxCount: 1})
387 assert.NoError(t, err)
388 if len(commits) > 0 {
389 assert.Contains(t, commits[0].Message, task.ID)
390 assert.Contains(t, commits[0].Message, "created")
391 }
392}
393
394func TestCreateTaskInvalidData(t *testing.T) {
395 tempDir, cleanup := setupTestDir(t)
396 defer cleanup()
397
398 gtm, _ := createTestTaskManager(t, tempDir)
399
400 ctx := context.Background()
401
402 // Test empty title
403 req := &tm.TaskCreateRequest{
404 Title: "",
405 OwnerID: "user123",
406 }
407
408 task, err := gtm.CreateTask(ctx, req)
409 assert.Error(t, err)
410 assert.Nil(t, task)
411 assert.Equal(t, tm.ErrInvalidTaskData, err)
412
413 // Test empty owner ID
414 req = &tm.TaskCreateRequest{
415 Title: "Valid Title",
416 OwnerID: "",
417 }
418
419 task, err = gtm.CreateTask(ctx, req)
420 assert.Error(t, err)
421 assert.Nil(t, task)
422 assert.Equal(t, tm.ErrInvalidOwner, err)
423}
424
425func TestGetTask(t *testing.T) {
426 tempDir, cleanup := setupTestDir(t)
427 defer cleanup()
428
429 gtm, _ := createTestTaskManager(t, tempDir)
430
431 // Create a test task
432 task := &tm.Task{
433 ID: "test-task-123",
434 Title: "Test Task",
435 Description: "Test task description",
436 Owner: tm.Owner{
437 ID: "user123",
438 Name: "Test User",
439 },
440 Status: tm.StatusToDo,
441 Priority: tm.PriorityMedium,
442 CreatedAt: time.Now(),
443 UpdatedAt: time.Now(),
444 }
445
446 err := gtm.ensureTasksDir()
447 require.NoError(t, err)
448 err = gtm.writeTaskFile(task)
449 require.NoError(t, err)
450
451 // Get the task
452 ctx := context.Background()
453 retrievedTask, err := gtm.GetTask(ctx, task.ID)
454 assert.NoError(t, err)
455 assert.NotNil(t, retrievedTask)
456 assert.Equal(t, task.ID, retrievedTask.ID)
457 assert.Equal(t, task.Title, retrievedTask.Title)
458}
459
460func TestGetTaskNotFound(t *testing.T) {
461 tempDir, cleanup := setupTestDir(t)
462 defer cleanup()
463
464 gtm, _ := createTestTaskManager(t, tempDir)
465
466 ctx := context.Background()
467 task, err := gtm.GetTask(ctx, "non-existent-task")
468 assert.Error(t, err)
469 assert.Nil(t, task)
470 assert.Equal(t, tm.ErrTaskNotFound, err)
471}
472
473func TestUpdateTask(t *testing.T) {
474 tempDir, cleanup := setupTestDir(t)
475 defer cleanup()
476
477 gtm, gitImpl := createTestTaskManager(t, tempDir)
478
479 // Create a test task
480 originalTask := &tm.Task{
481 ID: "test-task-123",
482 Title: "Original Title",
483 Description: "Original description",
484 Owner: tm.Owner{
485 ID: "user123",
486 Name: "Original User",
487 },
488 Status: tm.StatusToDo,
489 Priority: tm.PriorityLow,
490 CreatedAt: time.Now().Add(-time.Hour),
491 UpdatedAt: time.Now().Add(-time.Hour),
492 }
493
494 err := gtm.ensureTasksDir()
495 require.NoError(t, err)
496 err = gtm.writeTaskFile(originalTask)
497 require.NoError(t, err)
498
499 // Commit the initial task
iomodo97555d02025-07-27 15:07:14 +0400500 err = gtm.commitTaskChange(originalTask.ID, "created", originalTask.Owner)
iomodo3b89bdf2025-07-25 15:19:22 +0400501 require.NoError(t, err)
502
503 // Update the task
504 ctx := context.Background()
505 newTitle := "Updated Title"
506 newDescription := "Updated description"
507 newStatus := tm.StatusInProgress
508 newPriority := tm.PriorityHigh
509 newOwnerID := "user456"
510
511 req := &tm.TaskUpdateRequest{
512 Title: &newTitle,
513 Description: &newDescription,
514 Status: &newStatus,
515 Priority: &newPriority,
516 OwnerID: &newOwnerID,
517 }
518
519 updatedTask, err := gtm.UpdateTask(ctx, originalTask.ID, req)
520 assert.NoError(t, err)
521 assert.NotNil(t, updatedTask)
522
523 // Verify updated properties
524 assert.Equal(t, newTitle, updatedTask.Title)
525 assert.Equal(t, newDescription, updatedTask.Description)
526 assert.Equal(t, newStatus, updatedTask.Status)
527 assert.Equal(t, newPriority, updatedTask.Priority)
528 assert.Equal(t, newOwnerID, updatedTask.Owner.ID)
529 assert.Equal(t, newOwnerID, updatedTask.Owner.Name)
530
531 // Verify timestamps were updated
532 assert.True(t, updatedTask.UpdatedAt.After(originalTask.UpdatedAt))
533
534 // Verify git commit was created
535 commits, err := gitImpl.Log(ctx, git.LogOptions{MaxCount: 2})
536 assert.NoError(t, err)
537 if len(commits) > 0 {
538 assert.Contains(t, commits[0].Message, "updated")
539 }
540}
541
542func TestUpdateTaskNotFound(t *testing.T) {
543 tempDir, cleanup := setupTestDir(t)
544 defer cleanup()
545
546 gtm, _ := createTestTaskManager(t, tempDir)
547
548 ctx := context.Background()
549 newTitle := "Updated Title"
550 req := &tm.TaskUpdateRequest{
551 Title: &newTitle,
552 }
553
554 task, err := gtm.UpdateTask(ctx, "non-existent-task", req)
555 assert.Error(t, err)
556 assert.Nil(t, task)
557 assert.Equal(t, tm.ErrTaskNotFound, err)
558}
559
560func TestUpdateTaskNoChanges(t *testing.T) {
561 tempDir, cleanup := setupTestDir(t)
562 defer cleanup()
563
564 gtm, _ := createTestTaskManager(t, tempDir)
565
566 // Create a test task
567 originalTask := &tm.Task{
568 ID: "test-task-123",
569 Title: "Test Task",
570 Description: "Test description",
571 Owner: tm.Owner{
572 ID: "user123",
573 Name: "Test User",
574 },
575 Status: tm.StatusToDo,
576 Priority: tm.PriorityMedium,
577 CreatedAt: time.Now().Add(-time.Hour),
578 UpdatedAt: time.Now().Add(-time.Hour),
579 }
580
581 err := gtm.ensureTasksDir()
582 require.NoError(t, err)
583 err = gtm.writeTaskFile(originalTask)
584 require.NoError(t, err)
585
586 // Update with no changes
587 ctx := context.Background()
588 req := &tm.TaskUpdateRequest{}
589
590 updatedTask, err := gtm.UpdateTask(ctx, originalTask.ID, req)
591 assert.NoError(t, err)
592 assert.NotNil(t, updatedTask)
593
594 // Verify no changes were made
595 assert.Equal(t, originalTask.Title, updatedTask.Title)
596 assert.Equal(t, originalTask.Description, updatedTask.Description)
597 assert.Equal(t, originalTask.Status, updatedTask.Status)
598 assert.Equal(t, originalTask.Priority, updatedTask.Priority)
599 assert.Equal(t, originalTask.Owner.ID, updatedTask.Owner.ID)
600}
601
602func TestUpdateTaskStatusTimestamps(t *testing.T) {
603 tempDir, cleanup := setupTestDir(t)
604 defer cleanup()
605
606 gtm, _ := createTestTaskManager(t, tempDir)
607
608 // Create a test task
609 task := &tm.Task{
610 ID: "test-task-123",
611 Title: "Test Task",
612 Description: "Test description",
613 Owner: tm.Owner{
614 ID: "user123",
615 Name: "Test User",
616 },
617 Status: tm.StatusToDo,
618 Priority: tm.PriorityMedium,
619 CreatedAt: time.Now().Add(-time.Hour),
620 UpdatedAt: time.Now().Add(-time.Hour),
621 }
622
623 err := gtm.ensureTasksDir()
624 require.NoError(t, err)
625 err = gtm.writeTaskFile(task)
626 require.NoError(t, err)
627
628 ctx := context.Background()
629
630 // Test completing a task
631 completedStatus := tm.StatusCompleted
632 req := &tm.TaskUpdateRequest{
633 Status: &completedStatus,
634 }
635
636 updatedTask, err := gtm.UpdateTask(ctx, task.ID, req)
637 assert.NoError(t, err)
638 assert.NotNil(t, updatedTask)
639 assert.Equal(t, tm.StatusCompleted, updatedTask.Status)
640 assert.NotNil(t, updatedTask.CompletedAt)
641
642 // Test archiving a task
643 archivedStatus := tm.StatusArchived
644 req = &tm.TaskUpdateRequest{
645 Status: &archivedStatus,
646 }
647
648 updatedTask, err = gtm.UpdateTask(ctx, task.ID, req)
649 assert.NoError(t, err)
650 assert.NotNil(t, updatedTask)
651 assert.Equal(t, tm.StatusArchived, updatedTask.Status)
652 assert.NotNil(t, updatedTask.ArchivedAt)
653}
654
655func TestArchiveTask(t *testing.T) {
656 tempDir, cleanup := setupTestDir(t)
657 defer cleanup()
658
659 gtm, _ := createTestTaskManager(t, tempDir)
660
661 // Create a test task
662 task := &tm.Task{
663 ID: "test-task-123",
664 Title: "Test Task",
665 Description: "Test description",
666 Owner: tm.Owner{
667 ID: "user123",
668 Name: "Test User",
669 },
670 Status: tm.StatusToDo,
671 Priority: tm.PriorityMedium,
672 CreatedAt: time.Now().Add(-time.Hour),
673 UpdatedAt: time.Now().Add(-time.Hour),
674 }
675
676 err := gtm.ensureTasksDir()
677 require.NoError(t, err)
678 err = gtm.writeTaskFile(task)
679 require.NoError(t, err)
680
681 // Archive the task
682 ctx := context.Background()
683 err = gtm.ArchiveTask(ctx, task.ID)
684 assert.NoError(t, err)
685
686 // Verify task was archived
687 archivedTask, err := gtm.GetTask(ctx, task.ID)
688 assert.NoError(t, err)
689 assert.Equal(t, tm.StatusArchived, archivedTask.Status)
690 assert.NotNil(t, archivedTask.ArchivedAt)
691}
692
693func TestStartTask(t *testing.T) {
694 tempDir, cleanup := setupTestDir(t)
695 defer cleanup()
696
697 gtm, _ := createTestTaskManager(t, tempDir)
698
699 // Create a test task
700 task := &tm.Task{
701 ID: "test-task-123",
702 Title: "Test Task",
703 Description: "Test description",
704 Owner: tm.Owner{
705 ID: "user123",
706 Name: "Test User",
707 },
708 Status: tm.StatusToDo,
709 Priority: tm.PriorityMedium,
710 CreatedAt: time.Now().Add(-time.Hour),
711 UpdatedAt: time.Now().Add(-time.Hour),
712 }
713
714 err := gtm.ensureTasksDir()
715 require.NoError(t, err)
716 err = gtm.writeTaskFile(task)
717 require.NoError(t, err)
718
719 // Start the task
720 ctx := context.Background()
721 startedTask, err := gtm.StartTask(ctx, task.ID)
722 assert.NoError(t, err)
723 assert.NotNil(t, startedTask)
724 assert.Equal(t, tm.StatusInProgress, startedTask.Status)
725}
726
727func TestCompleteTask(t *testing.T) {
728 tempDir, cleanup := setupTestDir(t)
729 defer cleanup()
730
731 gtm, _ := createTestTaskManager(t, tempDir)
732
733 // Create a test task
734 task := &tm.Task{
735 ID: "test-task-123",
736 Title: "Test Task",
737 Description: "Test description",
738 Owner: tm.Owner{
739 ID: "user123",
740 Name: "Test User",
741 },
742 Status: tm.StatusInProgress,
743 Priority: tm.PriorityMedium,
744 CreatedAt: time.Now().Add(-time.Hour),
745 UpdatedAt: time.Now().Add(-time.Hour),
746 }
747
748 err := gtm.ensureTasksDir()
749 require.NoError(t, err)
750 err = gtm.writeTaskFile(task)
751 require.NoError(t, err)
752
753 // Complete the task
754 ctx := context.Background()
755 completedTask, err := gtm.CompleteTask(ctx, task.ID)
756 assert.NoError(t, err)
757 assert.NotNil(t, completedTask)
758 assert.Equal(t, tm.StatusCompleted, completedTask.Status)
759 assert.NotNil(t, completedTask.CompletedAt)
760}
761
762func TestListTasks(t *testing.T) {
763 tempDir, cleanup := setupTestDir(t)
764 defer cleanup()
765
766 gtm, _ := createTestTaskManager(t, tempDir)
767
768 // Create test tasks
769 tasks := []*tm.Task{
770 {
771 ID: "task-1",
772 Title: "Task 1",
773 Description: "First task",
774 Owner: tm.Owner{ID: "user1", Name: "User 1"},
775 Status: tm.StatusToDo,
776 Priority: tm.PriorityHigh,
777 CreatedAt: time.Now().Add(-2 * time.Hour),
778 UpdatedAt: time.Now().Add(-2 * time.Hour),
779 },
780 {
781 ID: "task-2",
782 Title: "Task 2",
783 Description: "Second task",
784 Owner: tm.Owner{ID: "user2", Name: "User 2"},
785 Status: tm.StatusInProgress,
786 Priority: tm.PriorityMedium,
787 CreatedAt: time.Now().Add(-1 * time.Hour),
788 UpdatedAt: time.Now().Add(-1 * time.Hour),
789 },
790 {
791 ID: "task-3",
792 Title: "Task 3",
793 Description: "Third task",
794 Owner: tm.Owner{ID: "user1", Name: "User 1"},
795 Status: tm.StatusCompleted,
796 Priority: tm.PriorityLow,
797 CreatedAt: time.Now(),
798 UpdatedAt: time.Now(),
799 },
800 }
801
802 err := gtm.ensureTasksDir()
803 require.NoError(t, err)
804
805 for _, task := range tasks {
806 err = gtm.writeTaskFile(task)
807 require.NoError(t, err)
808 }
809
810 ctx := context.Background()
811
812 // Test listing all tasks
813 taskList, err := gtm.ListTasks(ctx, nil, 0, 10)
814 assert.NoError(t, err)
815 assert.NotNil(t, taskList)
816 assert.Len(t, taskList.Tasks, 3)
817 assert.Equal(t, 3, taskList.TotalCount)
818 assert.Equal(t, 0, taskList.Page)
819 assert.Equal(t, 10, taskList.PageSize)
820 assert.False(t, taskList.HasMore)
821
822 // Test pagination
823 taskList, err = gtm.ListTasks(ctx, nil, 0, 2)
824 assert.NoError(t, err)
825 assert.Len(t, taskList.Tasks, 2)
826 assert.Equal(t, 3, taskList.TotalCount)
827 assert.True(t, taskList.HasMore)
828
829 // Test filtering by owner
830 ownerFilter := &tm.TaskFilter{OwnerID: stringPtr("user1")}
831 taskList, err = gtm.ListTasks(ctx, ownerFilter, 0, 10)
832 assert.NoError(t, err)
833 assert.Len(t, taskList.Tasks, 2)
834
835 // Test filtering by status
836 statusFilter := &tm.TaskFilter{Status: taskStatusPtr(tm.StatusToDo)}
837 taskList, err = gtm.ListTasks(ctx, statusFilter, 0, 10)
838 assert.NoError(t, err)
839 assert.Len(t, taskList.Tasks, 1)
840 assert.Equal(t, "task-1", taskList.Tasks[0].ID)
841
842 // Test filtering by priority
843 priorityFilter := &tm.TaskFilter{Priority: taskPriorityPtr(tm.PriorityHigh)}
844 taskList, err = gtm.ListTasks(ctx, priorityFilter, 0, 10)
845 assert.NoError(t, err)
846 assert.Len(t, taskList.Tasks, 1)
847 assert.Equal(t, "task-1", taskList.Tasks[0].ID)
848}
849
850func TestGetTasksByOwner(t *testing.T) {
851 tempDir, cleanup := setupTestDir(t)
852 defer cleanup()
853
854 gtm, _ := createTestTaskManager(t, tempDir)
855
856 // Create test tasks
857 tasks := []*tm.Task{
858 {
859 ID: "task-1",
860 Title: "Task 1",
861 Owner: tm.Owner{ID: "user1", Name: "User 1"},
862 Status: tm.StatusToDo,
863 Priority: tm.PriorityHigh,
864 CreatedAt: time.Now().Add(-2 * time.Hour),
865 UpdatedAt: time.Now().Add(-2 * time.Hour),
866 },
867 {
868 ID: "task-2",
869 Title: "Task 2",
870 Owner: tm.Owner{ID: "user2", Name: "User 2"},
871 Status: tm.StatusInProgress,
872 Priority: tm.PriorityMedium,
873 CreatedAt: time.Now().Add(-1 * time.Hour),
874 UpdatedAt: time.Now().Add(-1 * time.Hour),
875 },
876 {
877 ID: "task-3",
878 Title: "Task 3",
879 Owner: tm.Owner{ID: "user1", Name: "User 1"},
880 Status: tm.StatusCompleted,
881 Priority: tm.PriorityLow,
882 CreatedAt: time.Now(),
883 UpdatedAt: time.Now(),
884 },
885 }
886
887 err := gtm.ensureTasksDir()
888 require.NoError(t, err)
889
890 for _, task := range tasks {
891 err = gtm.writeTaskFile(task)
892 require.NoError(t, err)
893 }
894
895 ctx := context.Background()
896
897 // Get tasks by owner
898 taskList, err := gtm.GetTasksByOwner(ctx, "user1", 0, 10)
899 assert.NoError(t, err)
900 assert.NotNil(t, taskList)
901 assert.Len(t, taskList.Tasks, 2)
902
903 for _, task := range taskList.Tasks {
904 assert.Equal(t, "user1", task.Owner.ID)
905 }
906}
907
908func TestGetTasksByStatus(t *testing.T) {
909 tempDir, cleanup := setupTestDir(t)
910 defer cleanup()
911
912 gtm, _ := createTestTaskManager(t, tempDir)
913
914 // Create test tasks
915 tasks := []*tm.Task{
916 {
917 ID: "task-1",
918 Title: "Task 1",
919 Owner: tm.Owner{ID: "user1", Name: "User 1"},
920 Status: tm.StatusToDo,
921 Priority: tm.PriorityHigh,
922 CreatedAt: time.Now().Add(-2 * time.Hour),
923 UpdatedAt: time.Now().Add(-2 * time.Hour),
924 },
925 {
926 ID: "task-2",
927 Title: "Task 2",
928 Owner: tm.Owner{ID: "user2", Name: "User 2"},
929 Status: tm.StatusInProgress,
930 Priority: tm.PriorityMedium,
931 CreatedAt: time.Now().Add(-1 * time.Hour),
932 UpdatedAt: time.Now().Add(-1 * time.Hour),
933 },
934 {
935 ID: "task-3",
936 Title: "Task 3",
937 Owner: tm.Owner{ID: "user1", Name: "User 1"},
938 Status: tm.StatusCompleted,
939 Priority: tm.PriorityLow,
940 CreatedAt: time.Now(),
941 UpdatedAt: time.Now(),
942 },
943 }
944
945 err := gtm.ensureTasksDir()
946 require.NoError(t, err)
947
948 for _, task := range tasks {
949 err = gtm.writeTaskFile(task)
950 require.NoError(t, err)
951 }
952
953 ctx := context.Background()
954
955 // Get tasks by status
956 taskList, err := gtm.GetTasksByStatus(ctx, tm.StatusToDo, 0, 10)
957 assert.NoError(t, err)
958 assert.NotNil(t, taskList)
959 assert.Len(t, taskList.Tasks, 1)
960 assert.Equal(t, tm.StatusToDo, taskList.Tasks[0].Status)
961}
962
963func TestGetTasksByPriority(t *testing.T) {
964 tempDir, cleanup := setupTestDir(t)
965 defer cleanup()
966
967 gtm, _ := createTestTaskManager(t, tempDir)
968
969 // Create test tasks
970 tasks := []*tm.Task{
971 {
972 ID: "task-1",
973 Title: "Task 1",
974 Owner: tm.Owner{ID: "user1", Name: "User 1"},
975 Status: tm.StatusToDo,
976 Priority: tm.PriorityHigh,
977 CreatedAt: time.Now().Add(-2 * time.Hour),
978 UpdatedAt: time.Now().Add(-2 * time.Hour),
979 },
980 {
981 ID: "task-2",
982 Title: "Task 2",
983 Owner: tm.Owner{ID: "user2", Name: "User 2"},
984 Status: tm.StatusInProgress,
985 Priority: tm.PriorityMedium,
986 CreatedAt: time.Now().Add(-1 * time.Hour),
987 UpdatedAt: time.Now().Add(-1 * time.Hour),
988 },
989 {
990 ID: "task-3",
991 Title: "Task 3",
992 Owner: tm.Owner{ID: "user1", Name: "User 1"},
993 Status: tm.StatusCompleted,
994 Priority: tm.PriorityLow,
995 CreatedAt: time.Now(),
996 UpdatedAt: time.Now(),
997 },
998 }
999
1000 err := gtm.ensureTasksDir()
1001 require.NoError(t, err)
1002
1003 for _, task := range tasks {
1004 err = gtm.writeTaskFile(task)
1005 require.NoError(t, err)
1006 }
1007
1008 ctx := context.Background()
1009
1010 // Get tasks by priority
1011 taskList, err := gtm.GetTasksByPriority(ctx, tm.PriorityHigh, 0, 10)
1012 assert.NoError(t, err)
1013 assert.NotNil(t, taskList)
1014 assert.Len(t, taskList.Tasks, 1)
1015 assert.Equal(t, tm.PriorityHigh, taskList.Tasks[0].Priority)
1016}
1017
1018// Helper functions for creating pointers to string, TaskStatus, and TaskPriority
1019func stringPtr(s string) *string {
1020 return &s
1021}
1022
1023func taskStatusPtr(status tm.TaskStatus) *tm.TaskStatus {
1024 return &status
1025}
1026
1027func taskPriorityPtr(priority tm.TaskPriority) *tm.TaskPriority {
1028 return &priority
1029}