cli + tui to publish to leaflet (wip) & manage tasks, notes & watch/read lists 馃崈
charm
leaflet
readability
golang
1package main
2
3import (
4 "context"
5 "os"
6 "path/filepath"
7 "slices"
8 "strings"
9 "testing"
10
11 "github.com/stormlightlabs/noteleaf/internal/handlers"
12 "github.com/stormlightlabs/noteleaf/internal/services"
13 "github.com/stormlightlabs/noteleaf/internal/shared"
14)
15
16func setupCommandTest(t *testing.T) func() {
17 tempDir, err := os.MkdirTemp("", "noteleaf-cmd-test-*")
18 if err != nil {
19 t.Fatalf("Failed to create temp dir: %v", err)
20 }
21
22 oldNoteleafConfig := os.Getenv("NOTELEAF_CONFIG")
23 oldNoteleafDataDir := os.Getenv("NOTELEAF_DATA_DIR")
24 os.Setenv("NOTELEAF_CONFIG", filepath.Join(tempDir, ".noteleaf.conf.toml"))
25 os.Setenv("NOTELEAF_DATA_DIR", tempDir)
26
27 cleanup := func() {
28 os.Setenv("NOTELEAF_CONFIG", oldNoteleafConfig)
29 os.Setenv("NOTELEAF_DATA_DIR", oldNoteleafDataDir)
30 os.RemoveAll(tempDir)
31 }
32
33 ctx := context.Background()
34 err = handlers.Setup(ctx, []string{})
35 if err != nil {
36 cleanup()
37 t.Fatalf("Failed to setup database: %v", err)
38 }
39
40 return cleanup
41}
42
43func createTestTaskHandler(t *testing.T) (*handlers.TaskHandler, func()) {
44 cleanup := setupCommandTest(t)
45 handler, err := handlers.NewTaskHandler()
46 if err != nil {
47 cleanup()
48 t.Fatalf("Failed to create test task handler: %v", err)
49 }
50 return handler, func() {
51 handler.Close()
52 cleanup()
53 }
54}
55
56func createTestMovieHandler(t *testing.T) (*handlers.MovieHandler, func()) {
57 cleanup := setupCommandTest(t)
58 handler, err := handlers.NewMovieHandler()
59 if err != nil {
60 cleanup()
61 t.Fatalf("Failed to create test movie handler: %v", err)
62 }
63 return handler, func() {
64 handler.Close()
65 cleanup()
66 }
67}
68
69func createTestTVHandler(t *testing.T) (*handlers.TVHandler, func()) {
70 cleanup := setupCommandTest(t)
71 handler, err := handlers.NewTVHandler()
72 if err != nil {
73 cleanup()
74 t.Fatalf("Failed to create test TV handler: %v", err)
75 }
76 return handler, func() {
77 handler.Close()
78 cleanup()
79 }
80}
81
82func createTestNoteHandler(t *testing.T) (*handlers.NoteHandler, func()) {
83 cleanup := setupCommandTest(t)
84 handler, err := handlers.NewNoteHandler()
85 if err != nil {
86 cleanup()
87 t.Fatalf("Failed to create test note handler: %v", err)
88 }
89 return handler, func() {
90 handler.Close()
91 cleanup()
92 }
93}
94
95func createTestBookHandler(t *testing.T) (*handlers.BookHandler, func()) {
96 cleanup := setupCommandTest(t)
97 handler, err := handlers.NewBookHandler()
98 if err != nil {
99 cleanup()
100 t.Fatalf("Failed to create test book handler: %v", err)
101 }
102 return handler, func() {
103 handler.Close()
104 cleanup()
105 }
106}
107
108func createTestArticleHandler(t *testing.T) (*handlers.ArticleHandler, func()) {
109 cleanup := setupCommandTest(t)
110 handler, err := handlers.NewArticleHandler()
111 if err != nil {
112 cleanup()
113 t.Fatalf("Failed to create test article handler: %v", err)
114 }
115 return handler, func() {
116 handler.Close()
117 cleanup()
118 }
119}
120
121func createTestConfigHandler(t *testing.T) (*handlers.ConfigHandler, func()) {
122 cleanup := setupCommandTest(t)
123 handler, err := handlers.NewConfigHandler()
124 if err != nil {
125 cleanup()
126 t.Fatalf("failed to create test config handler: %v", err)
127 }
128 return handler, cleanup
129}
130
131func findSubcommand(commands []string, target string) bool {
132 return slices.Contains(commands, target)
133}
134
135func TestCommandGroup(t *testing.T) {
136 t.Run("Interface Implementations", func(t *testing.T) {
137 taskHandler, taskCleanup := createTestTaskHandler(t)
138 defer taskCleanup()
139
140 movieHandler, movieCleanup := createTestMovieHandler(t)
141 defer movieCleanup()
142
143 tvHandler, tvCleanup := createTestTVHandler(t)
144 defer tvCleanup()
145
146 noteHandler, noteCleanup := createTestNoteHandler(t)
147 defer noteCleanup()
148
149 bookHandler, bookCleanup := createTestBookHandler(t)
150 defer bookCleanup()
151
152 articleHandler, articleCleanup := createTestArticleHandler(t)
153 defer articleCleanup()
154
155 var _ CommandGroup = NewTaskCommand(taskHandler)
156 var _ CommandGroup = NewMovieCommand(movieHandler)
157 var _ CommandGroup = NewTVCommand(tvHandler)
158 var _ CommandGroup = NewNoteCommand(noteHandler)
159 var _ CommandGroup = NewBookCommand(bookHandler)
160 var _ CommandGroup = NewArticleCommand(articleHandler)
161 })
162
163 t.Run("Create", func(t *testing.T) {
164 t.Run("TaskCommand", func(t *testing.T) {
165 handler, cleanup := createTestTaskHandler(t)
166 defer cleanup()
167
168 commands := NewTaskCommand(handler)
169 cmd := commands.Create()
170
171 if cmd == nil {
172 t.Fatal("Create returned nil")
173 }
174 if cmd.Use != "todo" {
175 t.Errorf("Expected Use to be 'todo', got '%s'", cmd.Use)
176 }
177 if len(cmd.Aliases) != 1 || cmd.Aliases[0] != "task" {
178 t.Errorf("Expected aliases to be ['task'], got %v", cmd.Aliases)
179 }
180 if cmd.Short != "task management" {
181 t.Errorf("Expected Short to be 'task management', got '%s'", cmd.Short)
182 }
183 if !cmd.HasSubCommands() {
184 t.Error("Expected command to have subcommands")
185 }
186 })
187
188 t.Run("MovieCommand", func(t *testing.T) {
189 handler, cleanup := createTestMovieHandler(t)
190 defer cleanup()
191
192 commands := NewMovieCommand(handler)
193 cmd := commands.Create()
194
195 if cmd == nil {
196 t.Fatal("Create returned nil")
197 }
198 if cmd.Use != "movie" {
199 t.Errorf("Expected Use to be 'movie', got '%s'", cmd.Use)
200 }
201 if cmd.Short != "Manage movie watch queue" {
202 t.Errorf("Expected Short to be 'Manage movie watch queue', got '%s'", cmd.Short)
203 }
204 if !cmd.HasSubCommands() {
205 t.Error("Expected command to have subcommands")
206 }
207
208 subcommands := cmd.Commands()
209 subcommandNames := make([]string, len(subcommands))
210 for i, subcmd := range subcommands {
211 subcommandNames[i] = subcmd.Use
212 }
213
214 expectedSubcommands := []string{
215 "add [search query...]",
216 "list [--all|--watched|--queued]",
217 "watched [id]",
218 "remove [id]",
219 }
220
221 for _, expected := range expectedSubcommands {
222 if !findSubcommand(subcommandNames, expected) {
223 t.Errorf("Expected subcommand '%s' not found in %v", expected, subcommandNames)
224 }
225 }
226 })
227
228 t.Run("TVCommand", func(t *testing.T) {
229 handler, cleanup := createTestTVHandler(t)
230 defer cleanup()
231
232 commands := NewTVCommand(handler)
233 cmd := commands.Create()
234
235 if cmd == nil {
236 t.Fatal("Create returned nil")
237 }
238 if cmd.Use != "tv" {
239 t.Errorf("Expected Use to be 'tv', got '%s'", cmd.Use)
240 }
241 if cmd.Short != "Manage TV show watch queue" {
242 t.Errorf("Expected Short to be 'Manage TV show watch queue', got '%s'", cmd.Short)
243 }
244 if !cmd.HasSubCommands() {
245 t.Error("Expected command to have subcommands")
246 }
247
248 subcommands := cmd.Commands()
249 subcommandNames := make([]string, len(subcommands))
250 for i, subcmd := range subcommands {
251 subcommandNames[i] = subcmd.Use
252 }
253
254 expectedSubcommands := []string{
255 "add [search query...]",
256 "list [--all|--queued|--watching|--watched]",
257 "watching [id]",
258 "watched [id]",
259 "remove [id]",
260 }
261
262 for _, expected := range expectedSubcommands {
263 if !findSubcommand(subcommandNames, expected) {
264 t.Errorf("Expected subcommand '%s' not found in %v", expected, subcommandNames)
265 }
266 }
267 })
268
269 t.Run("NoteCommand", func(t *testing.T) {
270 handler, cleanup := createTestNoteHandler(t)
271 defer cleanup()
272
273 commands := NewNoteCommand(handler)
274 cmd := commands.Create()
275
276 if cmd == nil {
277 t.Fatal("Create returned nil")
278 }
279 if cmd.Use != "note" {
280 t.Errorf("Expected Use to be 'note', got '%s'", cmd.Use)
281 }
282 if cmd.Short != "Manage notes" {
283 t.Errorf("Expected Short to be 'Manage notes', got '%s'", cmd.Short)
284 }
285 if !cmd.HasSubCommands() {
286 t.Error("Expected command to have subcommands")
287 }
288
289 subcommands := cmd.Commands()
290 subcommandNames := make([]string, len(subcommands))
291 for i, subcmd := range subcommands {
292 subcommandNames[i] = subcmd.Use
293 }
294
295 expectedSubcommands := []string{
296 "create [title] [content...]",
297 "list [--archived] [--static] [--tags=tag1,tag2]",
298 "read [note-id]",
299 "edit [note-id]",
300 "remove [note-id]",
301 }
302
303 for _, expected := range expectedSubcommands {
304 if !findSubcommand(subcommandNames, expected) {
305 t.Errorf("Expected subcommand '%s' not found in %v", expected, subcommandNames)
306 }
307 }
308 })
309
310 t.Run("BookCommand", func(t *testing.T) {
311 handler, cleanup := createTestBookHandler(t)
312 defer cleanup()
313
314 commands := NewBookCommand(handler)
315 cmd := commands.Create()
316
317 if cmd == nil {
318 t.Fatal("Create returned nil")
319 }
320 if cmd.Use != "book" {
321 t.Errorf("Expected Use to be 'book', got '%s'", cmd.Use)
322 }
323 if cmd.Short != "Manage reading list" {
324 t.Errorf("Expected Short to be 'Manage reading list', got '%s'", cmd.Short)
325 }
326 if !cmd.HasSubCommands() {
327 t.Error("Expected command to have subcommands")
328 }
329
330 subcommands := cmd.Commands()
331 subcommandNames := make([]string, len(subcommands))
332 for i, subcmd := range subcommands {
333 subcommandNames[i] = subcmd.Use
334 }
335
336 expectedSubcommands := []string{
337 "add [search query...]",
338 "list [--all|--reading|--finished|--queued]",
339 "reading <id>",
340 "finished <id>",
341 "remove <id>",
342 "progress <id> <percentage>",
343 "update <id> <status>",
344 }
345
346 for _, expected := range expectedSubcommands {
347 if !findSubcommand(subcommandNames, expected) {
348 t.Errorf("Expected subcommand '%s' not found in %v", expected, subcommandNames)
349 }
350 }
351 })
352
353 t.Run("ArticleCommand", func(t *testing.T) {
354 handler, cleanup := createTestArticleHandler(t)
355 defer cleanup()
356
357 commands := NewArticleCommand(handler)
358 cmd := commands.Create()
359
360 if cmd == nil {
361 t.Fatal("Create returned nil")
362 }
363 if cmd.Use != "article" {
364 t.Errorf("Expected Use to be 'article', got '%s'", cmd.Use)
365 }
366 if cmd.Short != "Manage saved articles" {
367 t.Errorf("Expected Short to be 'Manage saved articles', got '%s'", cmd.Short)
368 }
369 if !cmd.HasSubCommands() {
370 t.Error("Expected command to have subcommands")
371 }
372
373 subcommands := cmd.Commands()
374 subcommandNames := make([]string, len(subcommands))
375 for i, subcmd := range subcommands {
376 subcommandNames[i] = subcmd.Use
377 }
378
379 for _, expected := range []string{"add <url>", "list [query]", "view <id>", "remove <id>"} {
380 if !findSubcommand(subcommandNames, expected) {
381 t.Errorf("Expected subcommand '%s' not found in %v", expected, subcommandNames)
382 }
383 }
384 })
385
386 t.Run("all command groups implement Create", func(t *testing.T) {
387 taskHandler, taskCleanup := createTestTaskHandler(t)
388 defer taskCleanup()
389
390 movieHandler, movieCleanup := createTestMovieHandler(t)
391 defer movieCleanup()
392
393 tvHandler, tvCleanup := createTestTVHandler(t)
394 defer tvCleanup()
395
396 noteHandler, noteCleanup := createTestNoteHandler(t)
397 defer noteCleanup()
398
399 bookHandler, bookCleanup := createTestBookHandler(t)
400 defer bookCleanup()
401
402 articleHandler, articleCleanup := createTestArticleHandler(t)
403 defer articleCleanup()
404
405 groups := []CommandGroup{
406 NewTaskCommand(taskHandler),
407 NewMovieCommand(movieHandler),
408 NewTVCommand(tvHandler),
409 NewNoteCommand(noteHandler),
410 NewBookCommand(bookHandler),
411 NewArticleCommand(articleHandler),
412 }
413
414 for i, group := range groups {
415 cmd := group.Create()
416 if cmd == nil {
417 t.Errorf("CommandGroup %d returned nil from Create()", i)
418 continue
419 }
420 if cmd.Use == "" {
421 t.Errorf("CommandGroup %d returned command with empty Use", i)
422 }
423 }
424 })
425 })
426
427}
428
429func TestCommandExecution(t *testing.T) {
430 t.Run("Movie Commands", func(t *testing.T) {
431 handler, cleanup := createTestMovieHandler(t)
432 defer cleanup()
433
434 t.Run("list command - default", func(t *testing.T) {
435 cmd := NewMovieCommand(handler).Create()
436 cmd.SetArgs([]string{"list"})
437 err := cmd.Execute()
438 if err != nil {
439 t.Errorf("movie list command failed: %v", err)
440 }
441 })
442
443 t.Run("add command with empty args", func(t *testing.T) {
444 cmd := NewMovieCommand(handler).Create()
445 cmd.SetArgs([]string{"add"})
446 err := cmd.Execute()
447 if err == nil {
448 t.Error("expected movie add command to fail with empty args")
449 }
450 })
451
452 t.Run("add command with valid args - successful search", func(t *testing.T) {
453 cleanup := services.SetupSuccessfulMovieMocks(t)
454 defer cleanup()
455
456 cmd := NewMovieCommand(handler).Create()
457 cmd.SetArgs([]string{"add", "Fantastic Four"})
458 err := cmd.Execute()
459
460 // NOTE: The command will find results but fail due to no user input in test environment
461 if err == nil {
462 t.Error("expected movie add command to fail due to no user input in test environment")
463 }
464 if !strings.Contains(err.Error(), "invalid input") {
465 t.Errorf("expected 'invalid input' error, got: %v", err)
466 }
467 })
468
469 t.Run("add command with valid args - search failure", func(t *testing.T) {
470 cleanup := services.SetupFailureMocks(t, "search failed")
471 defer cleanup()
472
473 cmd := NewMovieCommand(handler).Create()
474 cmd.SetArgs([]string{"add", "some movie"})
475 err := cmd.Execute()
476 if err == nil {
477 t.Error("expected movie add command to fail when search fails")
478 }
479 shared.AssertErrorContains(t, err, "search failed", "")
480 })
481
482 t.Run("remove command with non-existent movie ID", func(t *testing.T) {
483 cmd := NewMovieCommand(handler).Create()
484 cmd.SetArgs([]string{"remove", "999"})
485 err := cmd.Execute()
486 if err == nil {
487 t.Error("expected movie remove command to fail with non-existent ID")
488 }
489 })
490
491 t.Run("remove command with non-numeric ID", func(t *testing.T) {
492 cmd := NewMovieCommand(handler).Create()
493 cmd.SetArgs([]string{"remove", "invalid"})
494 err := cmd.Execute()
495 if err == nil {
496 t.Error("expected movie remove command to fail with non-numeric ID")
497 }
498 })
499
500 t.Run("watched command", func(t *testing.T) {
501 handler, cleanup := createTestMovieHandler(t)
502 defer cleanup()
503
504 cmd := NewMovieCommand(handler).Create()
505 cmd.SetArgs([]string{"watched", "1"})
506 err := cmd.Execute()
507 if err == nil {
508 t.Error("expected movie watched command to fail with non-existent ID")
509 }
510 })
511 })
512
513 t.Run("TV Commands", func(t *testing.T) {
514 handler, cleanup := createTestTVHandler(t)
515 defer cleanup()
516
517 t.Run("list command - default", func(t *testing.T) {
518 cmd := NewTVCommand(handler).Create()
519 cmd.SetArgs([]string{"list"})
520 err := cmd.Execute()
521 if err != nil {
522 t.Errorf("tv list command failed: %v", err)
523 }
524 })
525
526 t.Run("add command with empty args", func(t *testing.T) {
527 cmd := NewTVCommand(handler).Create()
528 cmd.SetArgs([]string{"add"})
529 err := cmd.Execute()
530 if err == nil {
531 t.Error("expected tv add command to fail with empty args")
532 }
533 })
534
535 t.Run("add command with valid args - successful search", func(t *testing.T) {
536 cleanup := services.SetupSuccessfulTVMocks(t)
537 defer cleanup()
538
539 cmd := NewTVCommand(handler).Create()
540 cmd.SetArgs([]string{"add", "Peacemaker"})
541 err := cmd.Execute()
542
543 // NOTE: The command will find results but fail due to no user input in test environment
544 if err == nil {
545 t.Error("expected tv add command to fail due to no user input in test environment")
546 }
547 if !strings.Contains(err.Error(), "invalid input") {
548 t.Errorf("expected 'invalid input' error, got: %v", err)
549 }
550 })
551
552 t.Run("add command with valid args - search failure", func(t *testing.T) {
553 cleanup := services.SetupFailureMocks(t, "tv search failed")
554 defer cleanup()
555
556 cmd := NewTVCommand(handler).Create()
557 cmd.SetArgs([]string{"add", "some show"})
558 err := cmd.Execute()
559 if err == nil {
560 t.Error("expected tv add command to fail when search fails")
561 }
562 shared.AssertErrorContains(t, err, "tv search failed", "")
563 })
564
565 t.Run("remove command with non-existent TV show ID", func(t *testing.T) {
566 cmd := NewTVCommand(handler).Create()
567 cmd.SetArgs([]string{"remove", "999"})
568 err := cmd.Execute()
569 if err == nil {
570 t.Error("expected tv remove command to fail with non-existent ID")
571 }
572 })
573
574 t.Run("remove command with non-numeric ID", func(t *testing.T) {
575 cmd := NewTVCommand(handler).Create()
576 cmd.SetArgs([]string{"remove", "invalid"})
577 err := cmd.Execute()
578 if err == nil {
579 t.Error("expected tv remove command to fail with non-numeric ID")
580 }
581 })
582
583 t.Run("watching command", func(t *testing.T) {
584 handler, cleanup := createTestTVHandler(t)
585 defer cleanup()
586
587 cmd := NewTVCommand(handler).Create()
588 cmd.SetArgs([]string{"watching", "1"})
589 err := cmd.Execute()
590 if err == nil {
591 t.Error("expected tv watching command to fail with non-existent ID")
592 }
593 })
594
595 t.Run("watched command", func(t *testing.T) {
596 handler, cleanup := createTestTVHandler(t)
597 defer cleanup()
598
599 cmd := NewTVCommand(handler).Create()
600 cmd.SetArgs([]string{"watched", "1"})
601 err := cmd.Execute()
602 if err == nil {
603 t.Error("expected tv watched command to fail with non-existent ID")
604 }
605 })
606 })
607
608 t.Run("Book Commands", func(t *testing.T) {
609 handler, cleanup := createTestBookHandler(t)
610 defer cleanup()
611
612 t.Run("list command - default", func(t *testing.T) {
613 cmd := NewBookCommand(handler).Create()
614 cmd.SetArgs([]string{"list"})
615 err := cmd.Execute()
616 if err != nil {
617 t.Errorf("book list command failed: %v", err)
618 }
619 })
620
621 t.Run("remove command with non-existent book ID", func(t *testing.T) {
622 cmd := NewBookCommand(handler).Create()
623 cmd.SetArgs([]string{"remove", "999"})
624 err := cmd.Execute()
625 if err == nil {
626 t.Error("expected book remove command to fail with non-existent ID")
627 }
628 })
629
630 t.Run("remove command with non-numeric ID", func(t *testing.T) {
631 cmd := NewBookCommand(handler).Create()
632 cmd.SetArgs([]string{"remove", "invalid"})
633 err := cmd.Execute()
634 if err == nil {
635 t.Error("expected book remove command to fail with non-numeric ID")
636 }
637 })
638
639 t.Run("update command with removed status", func(t *testing.T) {
640 cmd := NewBookCommand(handler).Create()
641 cmd.SetArgs([]string{"update", "999", "removed"})
642 err := cmd.Execute()
643 if err == nil {
644 t.Error("expected book update command to fail with non-existent ID")
645 }
646 })
647
648 t.Run("update command with invalid status", func(t *testing.T) {
649 cmd := NewBookCommand(handler).Create()
650 cmd.SetArgs([]string{"update", "1", "invalid_status"})
651 err := cmd.Execute()
652 if err == nil {
653 t.Error("expected book update command to fail with invalid status")
654 }
655 })
656
657 t.Run("reading command", func(t *testing.T) {
658 cmd := NewBookCommand(handler).Create()
659 cmd.SetArgs([]string{"reading", "1"})
660 err := cmd.Execute()
661 if err == nil {
662 t.Error("expected book reading command to fail with non-existent ID")
663 }
664 })
665
666 t.Run("finished command", func(t *testing.T) {
667 cmd := NewBookCommand(handler).Create()
668 cmd.SetArgs([]string{"finished", "1"})
669 err := cmd.Execute()
670 if err == nil {
671 t.Error("expected book finished command to fail with non-existent ID")
672 }
673 })
674
675 t.Run("progress command", func(t *testing.T) {
676 cmd := NewBookCommand(handler).Create()
677 cmd.SetArgs([]string{"progress", "1", "50"})
678 err := cmd.Execute()
679 if err == nil {
680 t.Error("expected book progress command to fail with non-existent ID")
681 }
682 })
683
684 t.Run("progress command with invalid percentage", func(t *testing.T) {
685 cmd := NewBookCommand(handler).Create()
686 cmd.SetArgs([]string{"progress", "1", "invalid"})
687 err := cmd.Execute()
688 if err == nil {
689 t.Error("expected book progress command to fail with invalid percentage")
690 }
691 })
692 })
693
694 t.Run("Article Commands", func(t *testing.T) {
695 handler, cleanup := createTestArticleHandler(t)
696 defer cleanup()
697
698 t.Run("list command - default", func(t *testing.T) {
699 cmd := NewArticleCommand(handler).Create()
700 cmd.SetArgs([]string{"list"})
701 err := cmd.Execute()
702 if err != nil {
703 t.Errorf("article list command failed: %v", err)
704 }
705 })
706
707 t.Run("help command", func(t *testing.T) {
708 cmd := NewArticleCommand(handler).Create()
709 cmd.SetArgs([]string{"help"})
710 err := cmd.Execute()
711 if err != nil {
712 t.Errorf("article help command failed: %v", err)
713 }
714 })
715
716 t.Run("add command with empty args", func(t *testing.T) {
717 cmd := NewArticleCommand(handler).Create()
718 cmd.SetArgs([]string{"add"})
719 err := cmd.Execute()
720 if err == nil {
721 t.Error("expected article add command to fail with empty args")
722 }
723 })
724
725 t.Run("add command with invalid URL", func(t *testing.T) {
726 cmd := NewArticleCommand(handler).Create()
727 cmd.SetArgs([]string{"add", "not-a-url"})
728 err := cmd.Execute()
729 if err == nil {
730 t.Error("expected article add command to fail with invalid URL")
731 }
732 })
733
734 t.Run("view command with non-existent article ID", func(t *testing.T) {
735 cmd := NewArticleCommand(handler).Create()
736 cmd.SetArgs([]string{"view", "999"})
737 err := cmd.Execute()
738 if err == nil {
739 t.Error("expected article view command to fail with non-existent ID")
740 }
741 })
742
743 t.Run("view command with non-numeric ID", func(t *testing.T) {
744 cmd := NewArticleCommand(handler).Create()
745 cmd.SetArgs([]string{"view", "invalid"})
746 err := cmd.Execute()
747 if err == nil {
748 t.Error("expected article view command to fail with non-numeric ID")
749 }
750 })
751
752 t.Run("read command with non-existent article ID", func(t *testing.T) {
753 cmd := NewArticleCommand(handler).Create()
754 cmd.SetArgs([]string{"read", "999"})
755 err := cmd.Execute()
756 if err == nil {
757 t.Error("expected article read command to fail with non-existent ID")
758 }
759 })
760
761 t.Run("read command with non-numeric ID", func(t *testing.T) {
762 cmd := NewArticleCommand(handler).Create()
763 cmd.SetArgs([]string{"read", "invalid"})
764 err := cmd.Execute()
765 if err == nil {
766 t.Error("expected article read command to fail with non-numeric ID")
767 }
768 })
769
770 t.Run("remove command with non-existent article ID", func(t *testing.T) {
771 cmd := NewArticleCommand(handler).Create()
772 cmd.SetArgs([]string{"remove", "999"})
773 err := cmd.Execute()
774 if err == nil {
775 t.Error("expected article remove command to fail with non-existent ID")
776 }
777 })
778
779 t.Run("remove command with non-numeric ID", func(t *testing.T) {
780 cmd := NewArticleCommand(handler).Create()
781 cmd.SetArgs([]string{"remove", "invalid"})
782 err := cmd.Execute()
783 if err == nil {
784 t.Error("expected article remove command to fail with non-numeric ID")
785 }
786 })
787 })
788
789 t.Run("Note Commands", func(t *testing.T) {
790 t.Run("create command - non-interactive", func(t *testing.T) {
791 handler, cleanup := createTestNoteHandler(t)
792 defer cleanup()
793
794 cmd := NewNoteCommand(handler).Create()
795 cmd.SetArgs([]string{"create", "test title", "test content"})
796 err := cmd.Execute()
797 if err != nil {
798 t.Errorf("note create command failed: %v", err)
799 }
800 })
801
802 t.Run("list command - static mode", func(t *testing.T) {
803 handler, cleanup := createTestNoteHandler(t)
804 defer cleanup()
805
806 cmd := NewNoteCommand(handler).Create()
807 cmd.SetArgs([]string{"list", "--static"})
808 err := cmd.Execute()
809 if err != nil {
810 t.Errorf("note list command failed: %v", err)
811 }
812 })
813
814 t.Run("read command with valid note ID", func(t *testing.T) {
815 handler, cleanup := createTestNoteHandler(t)
816 defer cleanup()
817
818 err := handler.CreateWithOptions(context.Background(), "test note", "test content", "", false, false)
819 if err != nil {
820 t.Fatalf("failed to create test note: %v", err)
821 }
822
823 cmd := NewNoteCommand(handler).Create()
824 cmd.SetArgs([]string{"read", "1"})
825 err = cmd.Execute()
826 if err != nil {
827 t.Errorf("note read command failed: %v", err)
828 }
829 })
830
831 t.Run("edit command with valid note ID", func(t *testing.T) {
832 t.Skip("edit command requires interactive editor")
833 })
834
835 t.Run("remove command with valid note ID", func(t *testing.T) {
836 handler, cleanup := createTestNoteHandler(t)
837 defer cleanup()
838
839 err := handler.CreateWithOptions(context.Background(), "test note", "test content", "", false, false)
840 if err != nil {
841 t.Fatalf("failed to create test note: %v", err)
842 }
843
844 cmd := NewNoteCommand(handler).Create()
845 cmd.SetArgs([]string{"remove", "1"})
846 err = cmd.Execute()
847 if err != nil {
848 t.Errorf("note remove command failed: %v", err)
849 }
850 })
851
852 t.Run("edit command with invalid ID", func(t *testing.T) {
853 handler, cleanup := createTestNoteHandler(t)
854 defer cleanup()
855
856 cmd := NewNoteCommand(handler).Create()
857 cmd.SetArgs([]string{"edit", "invalid"})
858 err := cmd.Execute()
859 if err == nil {
860 t.Error("expected note edit command to fail with invalid ID")
861 }
862 })
863
864 t.Run("remove command with invalid ID", func(t *testing.T) {
865 handler, cleanup := createTestNoteHandler(t)
866 defer cleanup()
867
868 cmd := NewNoteCommand(handler).Create()
869 cmd.SetArgs([]string{"remove", "invalid"})
870 err := cmd.Execute()
871 if err == nil {
872 t.Error("expected note remove command to fail with invalid ID")
873 }
874 })
875
876 t.Run("list command with static flag", func(t *testing.T) {
877 handler, cleanup := createTestNoteHandler(t)
878 defer cleanup()
879
880 cmd := NewNoteCommand(handler).Create()
881 cmd.SetArgs([]string{"list", "--static", "test query"})
882 err := cmd.Execute()
883 if err != nil {
884 t.Errorf("note list command with query failed: %v", err)
885 }
886 })
887 })
888
889 t.Run("Task Commands", func(t *testing.T) {
890 t.Run("list command - static", func(t *testing.T) {
891 handler, cleanup := createTestTaskHandler(t)
892 defer cleanup()
893
894 cmd := NewTaskCommand(handler).Create()
895 cmd.SetArgs([]string{"list", "--static"})
896 err := cmd.Execute()
897 if err != nil {
898 t.Errorf("task list command failed: %v", err)
899 }
900 })
901
902 t.Run("add command with valid args", func(t *testing.T) {
903 handler, cleanup := createTestTaskHandler(t)
904 defer cleanup()
905
906 cmd := NewTaskCommand(handler).Create()
907 cmd.SetArgs([]string{"add", "test task"})
908 err := cmd.Execute()
909 if err != nil {
910 t.Errorf("task add command failed: %v", err)
911 }
912 })
913
914 t.Run("projects command - static", func(t *testing.T) {
915 handler, cleanup := createTestTaskHandler(t)
916 defer cleanup()
917
918 cmd := NewTaskCommand(handler).Create()
919 cmd.SetArgs([]string{"projects", "--static"})
920 err := cmd.Execute()
921 if err != nil {
922 t.Errorf("task projects command failed: %v", err)
923 }
924 })
925
926 t.Run("tags command - static", func(t *testing.T) {
927 handler, cleanup := createTestTaskHandler(t)
928 defer cleanup()
929
930 cmd := NewTaskCommand(handler).Create()
931 cmd.SetArgs([]string{"tags", "--static"})
932 err := cmd.Execute()
933 if err != nil {
934 t.Errorf("task tags command failed: %v", err)
935 }
936 })
937
938 t.Run("contexts command - static", func(t *testing.T) {
939 handler, cleanup := createTestTaskHandler(t)
940 defer cleanup()
941
942 cmd := NewTaskCommand(handler).Create()
943 cmd.SetArgs([]string{"contexts", "--static"})
944 err := cmd.Execute()
945 if err != nil {
946 t.Errorf("task contexts command failed: %v", err)
947 }
948 })
949
950 t.Run("timesheet command", func(t *testing.T) {
951 handler, cleanup := createTestTaskHandler(t)
952 defer cleanup()
953
954 cmd := NewTaskCommand(handler).Create()
955 cmd.SetArgs([]string{"timesheet"})
956 err := cmd.Execute()
957 if err != nil {
958 t.Errorf("task timesheet command failed: %v", err)
959 }
960 })
961
962 t.Run("view command", func(t *testing.T) {
963 handler, cleanup := createTestTaskHandler(t)
964 defer cleanup()
965
966 cmd := NewTaskCommand(handler).Create()
967 cmd.SetArgs([]string{"view", "1"})
968 err := cmd.Execute()
969 if err == nil {
970 t.Error("expected task view command to fail with non-existent ID")
971 }
972 })
973
974 t.Run("update command", func(t *testing.T) {
975 handler, cleanup := createTestTaskHandler(t)
976 defer cleanup()
977
978 cmd := NewTaskCommand(handler).Create()
979 cmd.SetArgs([]string{"update", "1"})
980 err := cmd.Execute()
981 if err == nil {
982 t.Error("expected task update command to fail with non-existent ID")
983 }
984 })
985
986 t.Run("start command", func(t *testing.T) {
987 handler, cleanup := createTestTaskHandler(t)
988 defer cleanup()
989
990 cmd := NewTaskCommand(handler).Create()
991 cmd.SetArgs([]string{"start", "1"})
992 err := cmd.Execute()
993 if err == nil {
994 t.Error("expected task start command to fail with non-existent ID")
995 }
996 })
997
998 t.Run("stop command", func(t *testing.T) {
999 handler, cleanup := createTestTaskHandler(t)
1000 defer cleanup()
1001
1002 cmd := NewTaskCommand(handler).Create()
1003 cmd.SetArgs([]string{"stop", "1"})
1004 err := cmd.Execute()
1005 if err == nil {
1006 t.Error("expected task stop command to fail with non-existent ID")
1007 }
1008 })
1009
1010 t.Run("edit command", func(t *testing.T) {
1011 handler, cleanup := createTestTaskHandler(t)
1012 defer cleanup()
1013
1014 cmd := NewTaskCommand(handler).Create()
1015 cmd.SetArgs([]string{"edit", "1"})
1016 err := cmd.Execute()
1017 if err == nil {
1018 t.Error("expected task edit command to fail with non-existent ID")
1019 }
1020 })
1021
1022 t.Run("delete command", func(t *testing.T) {
1023 handler, cleanup := createTestTaskHandler(t)
1024 defer cleanup()
1025
1026 cmd := NewTaskCommand(handler).Create()
1027 cmd.SetArgs([]string{"delete", "1"})
1028 err := cmd.Execute()
1029 if err == nil {
1030 t.Error("expected task delete command to fail with non-existent ID")
1031 }
1032 })
1033
1034 t.Run("done command", func(t *testing.T) {
1035 handler, cleanup := createTestTaskHandler(t)
1036 defer cleanup()
1037
1038 cmd := NewTaskCommand(handler).Create()
1039 cmd.SetArgs([]string{"done", "1"})
1040 err := cmd.Execute()
1041 if err == nil {
1042 t.Error("expected task done command to fail with non-existent ID")
1043 }
1044 })
1045 })
1046
1047 t.Run("Config Command", func(t *testing.T) {
1048 handler, cleanup := createTestConfigHandler(t)
1049 defer cleanup()
1050
1051 cmd := NewConfigCommand(handler).Create()
1052
1053 if cmd.Use != "config" {
1054 t.Errorf("expected Use 'config', got %s", cmd.Use)
1055 }
1056 if cmd.Short == "" {
1057 t.Errorf("expected Short description to be set")
1058 }
1059 if len(cmd.Commands()) == 0 {
1060 t.Errorf("expected subcommands to be registered")
1061 }
1062
1063 t.Run("path command", func(t *testing.T) {
1064 cmd.SetArgs([]string{"path"})
1065 if err := cmd.Execute(); err != nil {
1066 t.Errorf("config path failed: %v", err)
1067 }
1068 })
1069
1070 t.Run("get with no args", func(t *testing.T) {
1071 cmd.SetArgs([]string{"get"})
1072 if err := cmd.Execute(); err != nil {
1073 t.Errorf("config get with no args failed: %v", err)
1074 }
1075 })
1076
1077 t.Run("set and get roundtrip", func(t *testing.T) {
1078 cmd.SetArgs([]string{"set", "editor", "vim"})
1079 if err := cmd.Execute(); err != nil {
1080 t.Fatalf("config set failed: %v", err)
1081 }
1082
1083 cmd.SetArgs([]string{"get", "editor"})
1084 if err := cmd.Execute(); err != nil {
1085 t.Errorf("config get after set failed: %v", err)
1086 }
1087 })
1088
1089 t.Run("reset command", func(t *testing.T) {
1090 cmd.SetArgs([]string{"reset"})
1091 if err := cmd.Execute(); err != nil {
1092 t.Errorf("config reset failed: %v", err)
1093 }
1094 })
1095 })
1096}