cli + tui to publish to leaflet (wip) & manage tasks, notes & watch/read lists 馃崈
charm
leaflet
readability
golang
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}