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 7da458848ced66d8eab8b2e86c031c8dc4e0e6fc 968 lines 27 kB view raw
1package models 2 3import ( 4 "encoding/json" 5 "testing" 6 "time" 7) 8 9func TestModels(t *testing.T) { 10 t.Run("Task Model", func(t *testing.T) { 11 t.Run("Model Interface Implementation", func(t *testing.T) { 12 task := &Task{ 13 ID: 1, 14 UUID: "test-uuid", 15 Description: "Test task", 16 Status: "pending", 17 Entry: time.Now(), 18 Modified: time.Now(), 19 } 20 21 if task.GetID() != 1 { 22 t.Errorf("Expected ID 1, got %d", task.GetID()) 23 } 24 25 task.SetID(2) 26 if task.GetID() != 2 { 27 t.Errorf("Expected ID 2 after SetID, got %d", task.GetID()) 28 } 29 30 if task.GetTableName() != "tasks" { 31 t.Errorf("Expected table name 'tasks', got '%s'", task.GetTableName()) 32 } 33 34 createdAt := time.Now() 35 task.SetCreatedAt(createdAt) 36 if !task.GetCreatedAt().Equal(createdAt) { 37 t.Errorf("Expected created at %v, got %v", createdAt, task.GetCreatedAt()) 38 } 39 40 updatedAt := time.Now().Add(time.Hour) 41 task.SetUpdatedAt(updatedAt) 42 if !task.GetUpdatedAt().Equal(updatedAt) { 43 t.Errorf("Expected updated at %v, got %v", updatedAt, task.GetUpdatedAt()) 44 } 45 }) 46 47 t.Run("Status Methods", func(t *testing.T) { 48 testCases := []struct { 49 status string 50 isCompleted bool 51 isPending bool 52 isDeleted bool 53 }{ 54 {"pending", false, true, false}, 55 {"completed", true, false, false}, 56 {"deleted", false, false, true}, 57 {"unknown", false, false, false}, 58 } 59 60 for _, tc := range testCases { 61 task := &Task{Status: tc.status} 62 63 if task.IsCompleted() != tc.isCompleted { 64 t.Errorf("Status %s: expected IsCompleted %v, got %v", tc.status, tc.isCompleted, task.IsCompleted()) 65 } 66 if task.IsPending() != tc.isPending { 67 t.Errorf("Status %s: expected IsPending %v, got %v", tc.status, tc.isPending, task.IsPending()) 68 } 69 if task.IsDeleted() != tc.isDeleted { 70 t.Errorf("Status %s: expected IsDeleted %v, got %v", tc.status, tc.isDeleted, task.IsDeleted()) 71 } 72 } 73 }) 74 75 t.Run("Priority Methods", func(t *testing.T) { 76 task := &Task{} 77 78 if task.HasPriority() { 79 t.Error("Task with empty priority should return false for HasPriority") 80 } 81 82 task.Priority = "A" 83 if !task.HasPriority() { 84 t.Error("Task with priority should return true for HasPriority") 85 } 86 }) 87 88 t.Run("Tags Marshaling", func(t *testing.T) { 89 task := &Task{} 90 91 result, err := task.MarshalTags() 92 if err != nil { 93 t.Fatalf("MarshalTags failed: %v", err) 94 } 95 if result != "" { 96 t.Errorf("Expected empty string for empty tags, got '%s'", result) 97 } 98 99 task.Tags = []string{"work", "urgent", "project-x"} 100 result, err = task.MarshalTags() 101 if err != nil { 102 t.Fatalf("MarshalTags failed: %v", err) 103 } 104 105 expected := `["work","urgent","project-x"]` 106 if result != expected { 107 t.Errorf("Expected %s, got %s", expected, result) 108 } 109 110 newTask := &Task{} 111 err = newTask.UnmarshalTags(result) 112 if err != nil { 113 t.Fatalf("UnmarshalTags failed: %v", err) 114 } 115 116 if len(newTask.Tags) != 3 { 117 t.Errorf("Expected 3 tags, got %d", len(newTask.Tags)) 118 } 119 if newTask.Tags[0] != "work" || newTask.Tags[1] != "urgent" || newTask.Tags[2] != "project-x" { 120 t.Errorf("Tags not unmarshaled correctly: %v", newTask.Tags) 121 } 122 123 emptyTask := &Task{} 124 err = emptyTask.UnmarshalTags("") 125 if err != nil { 126 t.Fatalf("UnmarshalTags with empty string failed: %v", err) 127 } 128 if emptyTask.Tags != nil { 129 t.Error("Expected nil tags for empty string") 130 } 131 }) 132 133 t.Run("Annotations Marshaling", func(t *testing.T) { 134 task := &Task{} 135 136 result, err := task.MarshalAnnotations() 137 if err != nil { 138 t.Fatalf("MarshalAnnotations failed: %v", err) 139 } 140 if result != "" { 141 t.Errorf("Expected empty string for empty annotations, got '%s'", result) 142 } 143 144 task.Annotations = []string{"Note 1", "Note 2", "Important reminder"} 145 result, err = task.MarshalAnnotations() 146 if err != nil { 147 t.Fatalf("MarshalAnnotations failed: %v", err) 148 } 149 150 expected := `["Note 1","Note 2","Important reminder"]` 151 if result != expected { 152 t.Errorf("Expected %s, got %s", expected, result) 153 } 154 155 newTask := &Task{} 156 err = newTask.UnmarshalAnnotations(result) 157 if err != nil { 158 t.Fatalf("UnmarshalAnnotations failed: %v", err) 159 } 160 161 if len(newTask.Annotations) != 3 { 162 t.Errorf("Expected 3 annotations, got %d", len(newTask.Annotations)) 163 } 164 if newTask.Annotations[0] != "Note 1" || newTask.Annotations[1] != "Note 2" || newTask.Annotations[2] != "Important reminder" { 165 t.Errorf("Annotations not unmarshaled correctly: %v", newTask.Annotations) 166 } 167 168 emptyTask := &Task{} 169 err = emptyTask.UnmarshalAnnotations("") 170 if err != nil { 171 t.Fatalf("UnmarshalAnnotations with empty string failed: %v", err) 172 } 173 if emptyTask.Annotations != nil { 174 t.Error("Expected nil annotations for empty string") 175 } 176 }) 177 178 t.Run("JSON Marshaling", func(t *testing.T) { 179 now := time.Now() 180 due := now.Add(24 * time.Hour) 181 task := &Task{ 182 ID: 1, 183 UUID: "test-uuid", 184 Description: "Test task", 185 Status: "pending", 186 Priority: "A", 187 Project: "test-project", 188 Tags: []string{"work", "urgent"}, 189 Due: &due, 190 Entry: now, 191 Modified: now, 192 Annotations: []string{"Note 1"}, 193 } 194 195 data, err := json.Marshal(task) 196 if err != nil { 197 t.Fatalf("JSON marshal failed: %v", err) 198 } 199 200 var unmarshaled Task 201 err = json.Unmarshal(data, &unmarshaled) 202 if err != nil { 203 t.Fatalf("JSON unmarshal failed: %v", err) 204 } 205 206 if unmarshaled.ID != task.ID { 207 t.Errorf("Expected ID %d, got %d", task.ID, unmarshaled.ID) 208 } 209 if unmarshaled.UUID != task.UUID { 210 t.Errorf("Expected UUID %s, got %s", task.UUID, unmarshaled.UUID) 211 } 212 if unmarshaled.Description != task.Description { 213 t.Errorf("Expected description %s, got %s", task.Description, unmarshaled.Description) 214 } 215 }) 216 }) 217 218 t.Run("Movie Model", func(t *testing.T) { 219 t.Run("Model Interface Implementation", func(t *testing.T) { 220 movie := &Movie{ 221 ID: 1, 222 Title: "Test Movie", 223 Year: 2023, 224 Added: time.Now(), 225 } 226 227 if movie.GetID() != 1 { 228 t.Errorf("Expected ID 1, got %d", movie.GetID()) 229 } 230 231 movie.SetID(2) 232 if movie.GetID() != 2 { 233 t.Errorf("Expected ID 2 after SetID, got %d", movie.GetID()) 234 } 235 236 if movie.GetTableName() != "movies" { 237 t.Errorf("Expected table name 'movies', got '%s'", movie.GetTableName()) 238 } 239 240 createdAt := time.Now() 241 movie.SetCreatedAt(createdAt) 242 if !movie.GetCreatedAt().Equal(createdAt) { 243 t.Errorf("Expected created at %v, got %v", createdAt, movie.GetCreatedAt()) 244 } 245 246 updatedAt := time.Now().Add(time.Hour) 247 movie.SetUpdatedAt(updatedAt) 248 if !movie.GetUpdatedAt().Equal(updatedAt) { 249 t.Errorf("Expected updated at %v, got %v", updatedAt, movie.GetUpdatedAt()) 250 } 251 }) 252 253 t.Run("Status Methods", func(t *testing.T) { 254 testCases := []struct { 255 status string 256 isWatched bool 257 isQueued bool 258 }{ 259 {"queued", false, true}, 260 {"watched", true, false}, 261 {"removed", false, false}, 262 {"unknown", false, false}, 263 } 264 265 for _, tc := range testCases { 266 movie := &Movie{Status: tc.status} 267 268 if movie.IsWatched() != tc.isWatched { 269 t.Errorf("Status %s: expected IsWatched %v, got %v", tc.status, tc.isWatched, movie.IsWatched()) 270 } 271 if movie.IsQueued() != tc.isQueued { 272 t.Errorf("Status %s: expected IsQueued %v, got %v", tc.status, tc.isQueued, movie.IsQueued()) 273 } 274 } 275 }) 276 277 t.Run("JSON Marshaling", func(t *testing.T) { 278 now := time.Now() 279 watched := now.Add(-24 * time.Hour) 280 movie := &Movie{ 281 ID: 1, 282 Title: "Test Movie", 283 Year: 2023, 284 Status: "watched", 285 Rating: 8.5, 286 Notes: "Great movie!", 287 Added: now, 288 Watched: &watched, 289 } 290 291 data, err := json.Marshal(movie) 292 if err != nil { 293 t.Fatalf("JSON marshal failed: %v", err) 294 } 295 296 var unmarshaled Movie 297 err = json.Unmarshal(data, &unmarshaled) 298 if err != nil { 299 t.Fatalf("JSON unmarshal failed: %v", err) 300 } 301 302 if unmarshaled.ID != movie.ID { 303 t.Errorf("Expected ID %d, got %d", movie.ID, unmarshaled.ID) 304 } 305 if unmarshaled.Title != movie.Title { 306 t.Errorf("Expected title %s, got %s", movie.Title, unmarshaled.Title) 307 } 308 if unmarshaled.Rating != movie.Rating { 309 t.Errorf("Expected rating %f, got %f", movie.Rating, unmarshaled.Rating) 310 } 311 }) 312 }) 313 314 t.Run("TV Show Model", func(t *testing.T) { 315 t.Run("Model Interface Implementation", func(t *testing.T) { 316 tvShow := &TVShow{ 317 ID: 1, 318 Title: "Test Show", 319 Added: time.Now(), 320 } 321 322 if tvShow.GetID() != 1 { 323 t.Errorf("Expected ID 1, got %d", tvShow.GetID()) 324 } 325 326 tvShow.SetID(2) 327 if tvShow.GetID() != 2 { 328 t.Errorf("Expected ID 2 after SetID, got %d", tvShow.GetID()) 329 } 330 331 if tvShow.GetTableName() != "tv_shows" { 332 t.Errorf("Expected table name 'tv_shows', got '%s'", tvShow.GetTableName()) 333 } 334 335 createdAt := time.Now() 336 tvShow.SetCreatedAt(createdAt) 337 if !tvShow.GetCreatedAt().Equal(createdAt) { 338 t.Errorf("Expected created at %v, got %v", createdAt, tvShow.GetCreatedAt()) 339 } 340 341 updatedAt := time.Now().Add(time.Hour) 342 tvShow.SetUpdatedAt(updatedAt) 343 if !tvShow.GetUpdatedAt().Equal(updatedAt) { 344 t.Errorf("Expected updated at %v, got %v", updatedAt, tvShow.GetUpdatedAt()) 345 } 346 }) 347 348 t.Run("Status Methods", func(t *testing.T) { 349 testCases := []struct { 350 status string 351 isWatching bool 352 isWatched bool 353 isQueued bool 354 }{ 355 {"queued", false, false, true}, 356 {"watching", true, false, false}, 357 {"watched", false, true, false}, 358 {"removed", false, false, false}, 359 {"unknown", false, false, false}, 360 } 361 362 for _, tc := range testCases { 363 tvShow := &TVShow{Status: tc.status} 364 365 if tvShow.IsWatching() != tc.isWatching { 366 t.Errorf("Status %s: expected IsWatching %v, got %v", tc.status, tc.isWatching, tvShow.IsWatching()) 367 } 368 if tvShow.IsWatched() != tc.isWatched { 369 t.Errorf("Status %s: expected IsWatched %v, got %v", tc.status, tc.isWatched, tvShow.IsWatched()) 370 } 371 if tvShow.IsQueued() != tc.isQueued { 372 t.Errorf("Status %s: expected IsQueued %v, got %v", tc.status, tc.isQueued, tvShow.IsQueued()) 373 } 374 } 375 }) 376 377 t.Run("JSON Marshaling", func(t *testing.T) { 378 now := time.Now() 379 lastWatched := now.Add(-24 * time.Hour) 380 tvShow := &TVShow{ 381 ID: 1, 382 Title: "Test Show", 383 Season: 1, 384 Episode: 5, 385 Status: "watching", 386 Rating: 9.0, 387 Notes: "Amazing series!", 388 Added: now, 389 LastWatched: &lastWatched, 390 } 391 392 data, err := json.Marshal(tvShow) 393 if err != nil { 394 t.Fatalf("JSON marshal failed: %v", err) 395 } 396 397 var unmarshaled TVShow 398 err = json.Unmarshal(data, &unmarshaled) 399 if err != nil { 400 t.Fatalf("JSON unmarshal failed: %v", err) 401 } 402 403 if unmarshaled.ID != tvShow.ID { 404 t.Errorf("Expected ID %d, got %d", tvShow.ID, unmarshaled.ID) 405 } 406 if unmarshaled.Title != tvShow.Title { 407 t.Errorf("Expected title %s, got %s", tvShow.Title, unmarshaled.Title) 408 } 409 if unmarshaled.Season != tvShow.Season { 410 t.Errorf("Expected season %d, got %d", tvShow.Season, unmarshaled.Season) 411 } 412 if unmarshaled.Episode != tvShow.Episode { 413 t.Errorf("Expected episode %d, got %d", tvShow.Episode, unmarshaled.Episode) 414 } 415 }) 416 }) 417 418 t.Run("Book Model", func(t *testing.T) { 419 t.Run("Model Interface Implementation", func(t *testing.T) { 420 book := &Book{ 421 ID: 1, 422 Title: "Test Book", 423 Added: time.Now(), 424 } 425 426 if book.GetID() != 1 { 427 t.Errorf("Expected ID 1, got %d", book.GetID()) 428 } 429 430 book.SetID(2) 431 if book.GetID() != 2 { 432 t.Errorf("Expected ID 2 after SetID, got %d", book.GetID()) 433 } 434 435 if book.GetTableName() != "books" { 436 t.Errorf("Expected table name 'books', got '%s'", book.GetTableName()) 437 } 438 439 createdAt := time.Now() 440 book.SetCreatedAt(createdAt) 441 if !book.GetCreatedAt().Equal(createdAt) { 442 t.Errorf("Expected created at %v, got %v", createdAt, book.GetCreatedAt()) 443 } 444 445 updatedAt := time.Now().Add(time.Hour) 446 book.SetUpdatedAt(updatedAt) 447 if !book.GetUpdatedAt().Equal(updatedAt) { 448 t.Errorf("Expected updated at %v, got %v", updatedAt, book.GetUpdatedAt()) 449 } 450 }) 451 452 t.Run("Status Methods", func(t *testing.T) { 453 testCases := []struct { 454 status string 455 isReading bool 456 isFinished bool 457 isQueued bool 458 }{ 459 {"queued", false, false, true}, 460 {"reading", true, false, false}, 461 {"finished", false, true, false}, 462 {"removed", false, false, false}, 463 {"unknown", false, false, false}, 464 } 465 466 for _, tc := range testCases { 467 book := &Book{Status: tc.status} 468 469 if book.IsReading() != tc.isReading { 470 t.Errorf("Status %s: expected IsReading %v, got %v", tc.status, tc.isReading, book.IsReading()) 471 } 472 if book.IsFinished() != tc.isFinished { 473 t.Errorf("Status %s: expected IsFinished %v, got %v", tc.status, tc.isFinished, book.IsFinished()) 474 } 475 if book.IsQueued() != tc.isQueued { 476 t.Errorf("Status %s: expected IsQueued %v, got %v", tc.status, tc.isQueued, book.IsQueued()) 477 } 478 } 479 }) 480 481 t.Run("Progress Methods", func(t *testing.T) { 482 book := &Book{Progress: 75} 483 484 if book.ProgressPercent() != 75 { 485 t.Errorf("Expected progress 75%%, got %d%%", book.ProgressPercent()) 486 } 487 }) 488 489 t.Run("JSON Marshaling", func(t *testing.T) { 490 now := time.Now() 491 started := now.Add(-7 * 24 * time.Hour) 492 finished := now.Add(-24 * time.Hour) 493 book := &Book{ 494 ID: 1, 495 Title: "Test Book", 496 Author: "Test Author", 497 Status: "finished", 498 Progress: 100, 499 Pages: 300, 500 Rating: 4.5, 501 Notes: "Excellent read!", 502 Added: now, 503 Started: &started, 504 Finished: &finished, 505 } 506 507 data, err := json.Marshal(book) 508 if err != nil { 509 t.Fatalf("JSON marshal failed: %v", err) 510 } 511 512 var unmarshaled Book 513 err = json.Unmarshal(data, &unmarshaled) 514 if err != nil { 515 t.Fatalf("JSON unmarshal failed: %v", err) 516 } 517 518 if unmarshaled.ID != book.ID { 519 t.Errorf("Expected ID %d, got %d", book.ID, unmarshaled.ID) 520 } 521 if unmarshaled.Title != book.Title { 522 t.Errorf("Expected title %s, got %s", book.Title, unmarshaled.Title) 523 } 524 if unmarshaled.Author != book.Author { 525 t.Errorf("Expected author %s, got %s", book.Author, unmarshaled.Author) 526 } 527 if unmarshaled.Progress != book.Progress { 528 t.Errorf("Expected progress %d, got %d", book.Progress, unmarshaled.Progress) 529 } 530 if unmarshaled.Pages != book.Pages { 531 t.Errorf("Expected pages %d, got %d", book.Pages, unmarshaled.Pages) 532 } 533 }) 534 }) 535 536 t.Run("Note Model", func(t *testing.T) { 537 t.Run("Model Interface Implementation", func(t *testing.T) { 538 note := &Note{ 539 ID: 1, 540 Title: "Test Note", 541 Content: "This is test content", 542 Created: time.Now(), 543 } 544 545 if note.GetID() != 1 { 546 t.Errorf("Expected ID 1, got %d", note.GetID()) 547 } 548 549 note.SetID(2) 550 if note.GetID() != 2 { 551 t.Errorf("Expected ID 2 after SetID, got %d", note.GetID()) 552 } 553 554 if note.GetTableName() != "notes" { 555 t.Errorf("Expected table name 'notes', got '%s'", note.GetTableName()) 556 } 557 558 createdAt := time.Now() 559 note.SetCreatedAt(createdAt) 560 if !note.GetCreatedAt().Equal(createdAt) { 561 t.Errorf("Expected created at %v, got %v", createdAt, note.GetCreatedAt()) 562 } 563 564 updatedAt := time.Now().Add(time.Hour) 565 note.SetUpdatedAt(updatedAt) 566 if !note.GetUpdatedAt().Equal(updatedAt) { 567 t.Errorf("Expected updated at %v, got %v", updatedAt, note.GetUpdatedAt()) 568 } 569 }) 570 571 t.Run("Archive Methods", func(t *testing.T) { 572 note := &Note{Archived: false} 573 574 if note.IsArchived() { 575 t.Error("Note should not be archived") 576 } 577 578 note.Archived = true 579 if !note.IsArchived() { 580 t.Error("Note should be archived") 581 } 582 }) 583 584 t.Run("Tags Marshaling", func(t *testing.T) { 585 note := &Note{} 586 587 result, err := note.MarshalTags() 588 if err != nil { 589 t.Fatalf("MarshalTags failed: %v", err) 590 } 591 if result != "" { 592 t.Errorf("Expected empty string for empty tags, got '%s'", result) 593 } 594 595 note.Tags = []string{"personal", "work", "idea"} 596 result, err = note.MarshalTags() 597 if err != nil { 598 t.Fatalf("MarshalTags failed: %v", err) 599 } 600 601 expected := `["personal","work","idea"]` 602 if result != expected { 603 t.Errorf("Expected %s, got %s", expected, result) 604 } 605 606 newNote := &Note{} 607 err = newNote.UnmarshalTags(result) 608 if err != nil { 609 t.Fatalf("UnmarshalTags failed: %v", err) 610 } 611 612 if len(newNote.Tags) != 3 { 613 t.Errorf("Expected 3 tags, got %d", len(newNote.Tags)) 614 } 615 if newNote.Tags[0] != "personal" || newNote.Tags[1] != "work" || newNote.Tags[2] != "idea" { 616 t.Errorf("Tags not unmarshaled correctly: %v", newNote.Tags) 617 } 618 619 emptyNote := &Note{} 620 err = emptyNote.UnmarshalTags("") 621 if err != nil { 622 t.Fatalf("UnmarshalTags with empty string failed: %v", err) 623 } 624 if emptyNote.Tags != nil { 625 t.Error("Expected nil tags for empty string") 626 } 627 }) 628 629 t.Run("JSON Marshaling", func(t *testing.T) { 630 now := time.Now() 631 modified := now.Add(time.Hour) 632 note := &Note{ 633 ID: 1, 634 Title: "Test Note", 635 Content: "This is test content with **markdown**", 636 Tags: []string{"personal", "markdown"}, 637 Archived: false, 638 Created: now, 639 Modified: modified, 640 FilePath: "/path/to/note.md", 641 } 642 643 data, err := json.Marshal(note) 644 if err != nil { 645 t.Fatalf("JSON marshal failed: %v", err) 646 } 647 648 var unmarshaled Note 649 err = json.Unmarshal(data, &unmarshaled) 650 if err != nil { 651 t.Fatalf("JSON unmarshal failed: %v", err) 652 } 653 654 if unmarshaled.ID != note.ID { 655 t.Errorf("Expected ID %d, got %d", note.ID, unmarshaled.ID) 656 } 657 if unmarshaled.Title != note.Title { 658 t.Errorf("Expected title %s, got %s", note.Title, unmarshaled.Title) 659 } 660 if unmarshaled.Content != note.Content { 661 t.Errorf("Expected content %s, got %s", note.Content, unmarshaled.Content) 662 } 663 if unmarshaled.Archived != note.Archived { 664 t.Errorf("Expected archived %v, got %v", note.Archived, unmarshaled.Archived) 665 } 666 if unmarshaled.FilePath != note.FilePath { 667 t.Errorf("Expected file path %s, got %s", note.FilePath, unmarshaled.FilePath) 668 } 669 }) 670 }) 671 672 t.Run("Album Model", func(t *testing.T) { 673 t.Run("Model Interface Implementation", func(t *testing.T) { 674 album := &Album{ 675 ID: 1, 676 Title: "Test Album", 677 Artist: "Test Artist", 678 Created: time.Now(), 679 } 680 681 if album.GetID() != 1 { 682 t.Errorf("Expected ID 1, got %d", album.GetID()) 683 } 684 685 album.SetID(2) 686 if album.GetID() != 2 { 687 t.Errorf("Expected ID 2 after SetID, got %d", album.GetID()) 688 } 689 690 if album.GetTableName() != "albums" { 691 t.Errorf("Expected table name 'albums', got '%s'", album.GetTableName()) 692 } 693 694 createdAt := time.Now() 695 album.SetCreatedAt(createdAt) 696 if !album.GetCreatedAt().Equal(createdAt) { 697 t.Errorf("Expected created at %v, got %v", createdAt, album.GetCreatedAt()) 698 } 699 700 updatedAt := time.Now().Add(time.Hour) 701 album.SetUpdatedAt(updatedAt) 702 if !album.GetUpdatedAt().Equal(updatedAt) { 703 t.Errorf("Expected updated at %v, got %v", updatedAt, album.GetUpdatedAt()) 704 } 705 }) 706 707 t.Run("Rating Methods", func(t *testing.T) { 708 album := &Album{} 709 710 if album.HasRating() { 711 t.Error("Album with zero rating should return false for HasRating") 712 } 713 714 if album.IsValidRating() { 715 t.Error("Album with zero rating should return false for IsValidRating") 716 } 717 718 album.Rating = 3 719 if !album.HasRating() { 720 t.Error("Album with rating should return true for HasRating") 721 } 722 723 if !album.IsValidRating() { 724 t.Error("Album with valid rating should return true for IsValidRating") 725 } 726 727 testCases := []struct { 728 rating int 729 isValid bool 730 }{ 731 {0, false}, 732 {1, true}, 733 {3, true}, 734 {5, true}, 735 {6, false}, 736 {-1, false}, 737 } 738 739 for _, tc := range testCases { 740 album.Rating = tc.rating 741 if album.IsValidRating() != tc.isValid { 742 t.Errorf("Rating %d: expected IsValidRating %v, got %v", tc.rating, tc.isValid, album.IsValidRating()) 743 } 744 } 745 }) 746 747 t.Run("Tracks Marshaling", func(t *testing.T) { 748 album := &Album{} 749 750 result, err := album.MarshalTracks() 751 if err != nil { 752 t.Fatalf("MarshalTracks failed: %v", err) 753 } 754 if result != "" { 755 t.Errorf("Expected empty string for empty tracks, got '%s'", result) 756 } 757 758 album.Tracks = []string{"Track 1", "Track 2", "Interlude"} 759 result, err = album.MarshalTracks() 760 if err != nil { 761 t.Fatalf("MarshalTracks failed: %v", err) 762 } 763 764 expected := `["Track 1","Track 2","Interlude"]` 765 if result != expected { 766 t.Errorf("Expected %s, got %s", expected, result) 767 } 768 769 newAlbum := &Album{} 770 err = newAlbum.UnmarshalTracks(result) 771 if err != nil { 772 t.Fatalf("UnmarshalTracks failed: %v", err) 773 } 774 775 if len(newAlbum.Tracks) != 3 { 776 t.Errorf("Expected 3 tracks, got %d", len(newAlbum.Tracks)) 777 } 778 if newAlbum.Tracks[0] != "Track 1" || newAlbum.Tracks[1] != "Track 2" || newAlbum.Tracks[2] != "Interlude" { 779 t.Errorf("Tracks not unmarshaled correctly: %v", newAlbum.Tracks) 780 } 781 782 emptyAlbum := &Album{} 783 err = emptyAlbum.UnmarshalTracks("") 784 if err != nil { 785 t.Fatalf("UnmarshalTracks with empty string failed: %v", err) 786 } 787 if emptyAlbum.Tracks != nil { 788 t.Error("Expected nil tracks for empty string") 789 } 790 }) 791 792 t.Run("JSON Marshaling", func(t *testing.T) { 793 now := time.Now() 794 modified := now.Add(time.Hour) 795 album := &Album{ 796 ID: 1, 797 Title: "Test Album", 798 Artist: "Test Artist", 799 Genre: "Rock", 800 ReleaseYear: 2023, 801 Tracks: []string{"Track 1", "Track 2"}, 802 DurationSeconds: 3600, 803 AlbumArtPath: "/path/to/art.jpg", 804 Rating: 4, 805 Created: now, 806 Modified: modified, 807 } 808 809 data, err := json.Marshal(album) 810 if err != nil { 811 t.Fatalf("JSON marshal failed: %v", err) 812 } 813 814 var unmarshaled Album 815 err = json.Unmarshal(data, &unmarshaled) 816 if err != nil { 817 t.Fatalf("JSON unmarshal failed: %v", err) 818 } 819 820 if unmarshaled.ID != album.ID { 821 t.Errorf("Expected ID %d, got %d", album.ID, unmarshaled.ID) 822 } 823 if unmarshaled.Title != album.Title { 824 t.Errorf("Expected title %s, got %s", album.Title, unmarshaled.Title) 825 } 826 if unmarshaled.Artist != album.Artist { 827 t.Errorf("Expected artist %s, got %s", album.Artist, unmarshaled.Artist) 828 } 829 if unmarshaled.Genre != album.Genre { 830 t.Errorf("Expected genre %s, got %s", album.Genre, unmarshaled.Genre) 831 } 832 if unmarshaled.ReleaseYear != album.ReleaseYear { 833 t.Errorf("Expected release year %d, got %d", album.ReleaseYear, unmarshaled.ReleaseYear) 834 } 835 if unmarshaled.DurationSeconds != album.DurationSeconds { 836 t.Errorf("Expected duration %d, got %d", album.DurationSeconds, unmarshaled.DurationSeconds) 837 } 838 if unmarshaled.Rating != album.Rating { 839 t.Errorf("Expected rating %d, got %d", album.Rating, unmarshaled.Rating) 840 } 841 }) 842 }) 843 844 t.Run("Interface Implementations", func(t *testing.T) { 845 t.Run("All models implement Model interface", func(t *testing.T) { 846 var models []Model 847 848 task := &Task{} 849 movie := &Movie{} 850 tvShow := &TVShow{} 851 book := &Book{} 852 note := &Note{} 853 album := &Album{} 854 855 models = append(models, task, movie, tvShow, book, note, album) 856 857 if len(models) != 6 { 858 t.Errorf("Expected 6 models, got %d", len(models)) 859 } 860 861 // Test that all models have the required methods 862 for i, model := range models { 863 // Test ID methods 864 model.SetID(int64(i + 1)) 865 if model.GetID() != int64(i+1) { 866 t.Errorf("Model %d: ID not set correctly", i) 867 } 868 869 // Test table name method 870 tableName := model.GetTableName() 871 if tableName == "" { 872 t.Errorf("Model %d: table name should not be empty", i) 873 } 874 875 // Test timestamp methods 876 now := time.Now() 877 model.SetCreatedAt(now) 878 model.SetUpdatedAt(now) 879 880 // Note: We don't test exact equality due to potential precision differences 881 if model.GetCreatedAt().IsZero() { 882 t.Errorf("Model %d: created at should not be zero", i) 883 } 884 if model.GetUpdatedAt().IsZero() { 885 t.Errorf("Model %d: updated at should not be zero", i) 886 } 887 } 888 }) 889 }) 890 891 t.Run("Errors & Edge cases", func(t *testing.T) { 892 t.Run("Marshaling Errors", func(t *testing.T) { 893 t.Run("UnmarshalTags handles invalid JSON", func(t *testing.T) { 894 task := &Task{} 895 err := task.UnmarshalTags(`{"invalid": "json"}`) 896 if err == nil { 897 t.Error("Expected error for invalid JSON, got nil") 898 } 899 }) 900 901 t.Run("UnmarshalAnnotations handles invalid JSON", func(t *testing.T) { 902 task := &Task{} 903 err := task.UnmarshalAnnotations(`{"invalid": "json"}`) 904 if err == nil { 905 t.Error("Expected error for invalid JSON, got nil") 906 } 907 }) 908 }) 909 }) 910 911 t.Run("Edge Cases", func(t *testing.T) { 912 t.Run("Task with nil slices", func(t *testing.T) { 913 task := &Task{ 914 Tags: nil, 915 Annotations: nil, 916 } 917 918 tagsJSON, err := task.MarshalTags() 919 if err != nil { 920 t.Errorf("MarshalTags with nil slice failed: %v", err) 921 } 922 if tagsJSON != "" { 923 t.Errorf("Expected empty string for nil tags, got '%s'", tagsJSON) 924 } 925 926 annotationsJSON, err := task.MarshalAnnotations() 927 if err != nil { 928 t.Errorf("MarshalAnnotations with nil slice failed: %v", err) 929 } 930 if annotationsJSON != "" { 931 t.Errorf("Expected empty string for nil annotations, got '%s'", annotationsJSON) 932 } 933 }) 934 935 t.Run("Models with zero values", func(t *testing.T) { 936 task := &Task{} 937 movie := &Movie{} 938 tvShow := &TVShow{} 939 book := &Book{} 940 note := &Note{} 941 942 // Test that zero values don't cause panics 943 if task.IsCompleted() || task.IsPending() || task.IsDeleted() { 944 t.Error("Zero value task should have false status methods") 945 } 946 947 if movie.IsWatched() || movie.IsQueued() { 948 t.Error("Zero value movie should have false status methods") 949 } 950 951 if tvShow.IsWatching() || tvShow.IsWatched() || tvShow.IsQueued() { 952 t.Error("Zero value TV show should have false status methods") 953 } 954 955 if book.IsReading() || book.IsFinished() || book.IsQueued() { 956 t.Error("Zero value book should have false status methods") 957 } 958 959 if book.ProgressPercent() != 0 { 960 t.Errorf("Zero value book should have 0%% progress, got %d%%", book.ProgressPercent()) 961 } 962 963 if note.IsArchived() { 964 t.Error("Zero value note should not be archived") 965 } 966 }) 967 }) 968}