cli + tui to publish to leaflet (wip) & manage tasks, notes & watch/read lists 馃崈
charm leaflet readability golang
29
fork

Configure Feed

Select the types of activity you want to include in your feed.

at 3db8ba04787ff855d831eb3fedd8a38fabe838ad 1096 lines 30 kB view raw
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}