cli + tui to publish to leaflet (wip) & manage tasks, notes & watch/read lists 馃崈
charm
leaflet
readability
golang
1package repo
2
3import (
4 "context"
5 "testing"
6 "time"
7
8 _ "github.com/mattn/go-sqlite3"
9 "github.com/stormlightlabs/noteleaf/internal/models"
10 "github.com/stormlightlabs/noteleaf/internal/shared"
11)
12
13func TestTVRepository(t *testing.T) {
14 t.Run("CRUD Operations", func(t *testing.T) {
15 db := CreateTestDB(t)
16 repo := NewTVRepository(db)
17 ctx := context.Background()
18
19 t.Run("Create TV Show", func(t *testing.T) {
20 tvShow := CreateSampleTVShow()
21
22 id, err := repo.Create(ctx, tvShow)
23 if err != nil {
24 t.Errorf("Failed to create TV show: %v", err)
25 }
26
27 if id == 0 {
28 t.Error("Expected non-zero ID")
29 }
30
31 if tvShow.ID != id {
32 t.Errorf("Expected TV show ID to be set to %d, got %d", id, tvShow.ID)
33 }
34
35 if tvShow.Added.IsZero() {
36 t.Error("Expected Added timestamp to be set")
37 }
38 })
39
40 t.Run("Get TV Show", func(t *testing.T) {
41 original := CreateSampleTVShow()
42 id, err := repo.Create(ctx, original)
43 if err != nil {
44 t.Fatalf("Failed to create TV show: %v", err)
45 }
46
47 retrieved, err := repo.Get(ctx, id)
48 if err != nil {
49 t.Errorf("Failed to get TV show: %v", err)
50 }
51
52 if retrieved.Title != original.Title {
53 t.Errorf("Expected title %s, got %s", original.Title, retrieved.Title)
54 }
55 if retrieved.Season != original.Season {
56 t.Errorf("Expected season %d, got %d", original.Season, retrieved.Season)
57 }
58 if retrieved.Episode != original.Episode {
59 t.Errorf("Expected episode %d, got %d", original.Episode, retrieved.Episode)
60 }
61 if retrieved.Status != original.Status {
62 t.Errorf("Expected status %s, got %s", original.Status, retrieved.Status)
63 }
64 if retrieved.Rating != original.Rating {
65 t.Errorf("Expected rating %f, got %f", original.Rating, retrieved.Rating)
66 }
67 if retrieved.Notes != original.Notes {
68 t.Errorf("Expected notes %s, got %s", original.Notes, retrieved.Notes)
69 }
70 })
71
72 t.Run("Update TV Show", func(t *testing.T) {
73 tvShow := CreateSampleTVShow()
74 id, err := repo.Create(ctx, tvShow)
75 if err != nil {
76 t.Fatalf("Failed to create TV show: %v", err)
77 }
78
79 tvShow.Title = "Updated Show"
80 tvShow.Season = 2
81 tvShow.Episode = 5
82 tvShow.Status = "watching"
83 tvShow.Rating = 9.5
84 now := time.Now()
85 tvShow.LastWatched = &now
86
87 err = repo.Update(ctx, tvShow)
88 if err != nil {
89 t.Errorf("Failed to update TV show: %v", err)
90 }
91
92 updated, err := repo.Get(ctx, id)
93 if err != nil {
94 t.Fatalf("Failed to get updated TV show: %v", err)
95 }
96
97 if updated.Title != "Updated Show" {
98 t.Errorf("Expected updated title, got %s", updated.Title)
99 }
100 if updated.Season != 2 {
101 t.Errorf("Expected season 2, got %d", updated.Season)
102 }
103 if updated.Episode != 5 {
104 t.Errorf("Expected episode 5, got %d", updated.Episode)
105 }
106 if updated.Status != "watching" {
107 t.Errorf("Expected status watching, got %s", updated.Status)
108 }
109 if updated.Rating != 9.5 {
110 t.Errorf("Expected rating 9.5, got %f", updated.Rating)
111 }
112 if updated.LastWatched == nil {
113 t.Error("Expected last watched time to be set")
114 }
115 })
116
117 t.Run("Delete TV Show", func(t *testing.T) {
118 tvShow := CreateSampleTVShow()
119 id, err := repo.Create(ctx, tvShow)
120 if err != nil {
121 t.Fatalf("Failed to create TV show: %v", err)
122 }
123
124 err = repo.Delete(ctx, id)
125 if err != nil {
126 t.Errorf("Failed to delete TV show: %v", err)
127 }
128
129 _, err = repo.Get(ctx, id)
130 if err == nil {
131 t.Error("Expected error when getting deleted TV show")
132 }
133 })
134 })
135
136 t.Run("List", func(t *testing.T) {
137 db := CreateTestDB(t)
138 repo := NewTVRepository(db)
139 ctx := context.Background()
140
141 tvShows := []*models.TVShow{
142 {Title: "Show A", Season: 1, Episode: 1, Status: "queued", Rating: 8.0},
143 {Title: "Show A", Season: 1, Episode: 2, Status: "watching", Rating: 8.5},
144 {Title: "Show B", Season: 1, Episode: 1, Status: "queued", Rating: 9.0},
145 {Title: "Show B", Season: 2, Episode: 1, Status: "watched", Rating: 9.2},
146 }
147
148 for _, tvShow := range tvShows {
149 _, err := repo.Create(ctx, tvShow)
150 if err != nil {
151 t.Fatalf("Failed to create TV show: %v", err)
152 }
153 }
154
155 t.Run("List All TV Shows", func(t *testing.T) {
156 results, err := repo.List(ctx, TVListOptions{})
157 if err != nil {
158 t.Errorf("Failed to list TV shows: %v", err)
159 }
160
161 if len(results) != 4 {
162 t.Errorf("Expected 4 TV shows, got %d", len(results))
163 }
164 })
165
166 t.Run("List TV Shows with Status Filter", func(t *testing.T) {
167 results, err := repo.List(ctx, TVListOptions{Status: "queued"})
168 if err != nil {
169 t.Errorf("Failed to list TV shows: %v", err)
170 }
171
172 if len(results) != 2 {
173 t.Errorf("Expected 2 queued TV shows, got %d", len(results))
174 }
175
176 for _, tvShow := range results {
177 if tvShow.Status != "queued" {
178 t.Errorf("Expected queued status, got %s", tvShow.Status)
179 }
180 }
181 })
182
183 t.Run("List TV Shows by Title", func(t *testing.T) {
184 results, err := repo.List(ctx, TVListOptions{Title: "Show A"})
185 if err != nil {
186 t.Errorf("Failed to list TV shows: %v", err)
187 }
188
189 if len(results) != 2 {
190 t.Errorf("Expected 2 episodes of Show A, got %d", len(results))
191 }
192
193 for _, tvShow := range results {
194 if tvShow.Title != "Show A" {
195 t.Errorf("Expected title 'Show A', got %s", tvShow.Title)
196 }
197 }
198 })
199
200 t.Run("List TV Shows by Season", func(t *testing.T) {
201 results, err := repo.List(ctx, TVListOptions{Title: "Show B", Season: 1})
202 if err != nil {
203 t.Errorf("Failed to list TV shows: %v", err)
204 }
205
206 if len(results) != 1 {
207 t.Errorf("Expected 1 episode of Show B season 1, got %d", len(results))
208 }
209
210 if len(results) > 0 {
211 if results[0].Title != "Show B" || results[0].Season != 1 {
212 t.Errorf("Expected Show B season 1, got %s season %d", results[0].Title, results[0].Season)
213 }
214 }
215 })
216
217 t.Run("List TV Shows with Rating Filter", func(t *testing.T) {
218 results, err := repo.List(ctx, TVListOptions{MinRating: 9.0})
219 if err != nil {
220 t.Errorf("Failed to list TV shows: %v", err)
221 }
222
223 if len(results) != 2 {
224 t.Errorf("Expected 2 TV shows with rating >= 9.0, got %d", len(results))
225 }
226
227 for _, tvShow := range results {
228 if tvShow.Rating < 9.0 {
229 t.Errorf("Expected rating >= 9.0, got %f", tvShow.Rating)
230 }
231 }
232 })
233
234 t.Run("List TV Shows with Search", func(t *testing.T) {
235 results, err := repo.List(ctx, TVListOptions{Search: "Show A"})
236 if err != nil {
237 t.Errorf("Failed to list TV shows: %v", err)
238 }
239
240 if len(results) != 2 {
241 t.Errorf("Expected 2 TV shows matching search, got %d", len(results))
242 }
243
244 for _, tvShow := range results {
245 if tvShow.Title != "Show A" {
246 t.Errorf("Expected 'Show A', got %s", tvShow.Title)
247 }
248 }
249 })
250
251 t.Run("List TV Shows with Limit", func(t *testing.T) {
252 results, err := repo.List(ctx, TVListOptions{Limit: 2})
253 if err != nil {
254 t.Errorf("Failed to list TV shows: %v", err)
255 }
256
257 if len(results) != 2 {
258 t.Errorf("Expected 2 TV shows due to limit, got %d", len(results))
259 }
260 })
261 })
262
263 t.Run("Special Methods", func(t *testing.T) {
264 db := CreateTestDB(t)
265 repo := NewTVRepository(db)
266 ctx := context.Background()
267
268 tvShow1 := &models.TVShow{Title: "Queued Show", Status: "queued", Rating: 8.0}
269 tvShow2 := &models.TVShow{Title: "Watching Show", Status: "watching", Rating: 9.0}
270 tvShow3 := &models.TVShow{Title: "Watched Show", Status: "watched", Rating: 8.5}
271 tvShow4 := &models.TVShow{Title: "Test Series", Season: 1, Episode: 1, Status: "queued"}
272 tvShow5 := &models.TVShow{Title: "Test Series", Season: 1, Episode: 2, Status: "queued"}
273 tvShow6 := &models.TVShow{Title: "Test Series", Season: 2, Episode: 1, Status: "queued"}
274
275 var tvShow1ID int64
276 for _, tvShow := range []*models.TVShow{tvShow1, tvShow2, tvShow3, tvShow4, tvShow5, tvShow6} {
277 id, err := repo.Create(ctx, tvShow)
278 if err != nil {
279 t.Fatalf("Failed to create TV show: %v", err)
280 }
281 if tvShow == tvShow1 {
282 tvShow1ID = id
283 }
284 }
285
286 t.Run("GetQueued", func(t *testing.T) {
287 results, err := repo.GetQueued(ctx)
288 if err != nil {
289 t.Errorf("Failed to get queued TV shows: %v", err)
290 }
291
292 if len(results) != 4 {
293 t.Errorf("Expected 4 queued TV shows, got %d", len(results))
294 }
295
296 for _, tvShow := range results {
297 if tvShow.Status != "queued" {
298 t.Errorf("Expected queued status, got %s", tvShow.Status)
299 }
300 }
301 })
302
303 t.Run("GetWatching", func(t *testing.T) {
304 results, err := repo.GetWatching(ctx)
305 if err != nil {
306 t.Errorf("Failed to get watching TV shows: %v", err)
307 }
308
309 if len(results) != 1 {
310 t.Errorf("Expected 1 watching TV show, got %d", len(results))
311 }
312
313 if len(results) > 0 && results[0].Status != "watching" {
314 t.Errorf("Expected watching status, got %s", results[0].Status)
315 }
316 })
317
318 t.Run("GetWatched", func(t *testing.T) {
319 results, err := repo.GetWatched(ctx)
320 if err != nil {
321 t.Errorf("Failed to get watched TV shows: %v", err)
322 }
323
324 if len(results) != 1 {
325 t.Errorf("Expected 1 watched TV show, got %d", len(results))
326 }
327
328 if len(results) > 0 && results[0].Status != "watched" {
329 t.Errorf("Expected watched status, got %s", results[0].Status)
330 }
331 })
332
333 t.Run("GetByTitle", func(t *testing.T) {
334 results, err := repo.GetByTitle(ctx, "Test Series")
335 if err != nil {
336 t.Errorf("Failed to get TV shows by title: %v", err)
337 }
338
339 if len(results) != 3 {
340 t.Errorf("Expected 3 episodes of Test Series, got %d", len(results))
341 }
342
343 for _, tvShow := range results {
344 if tvShow.Title != "Test Series" {
345 t.Errorf("Expected title 'Test Series', got %s", tvShow.Title)
346 }
347 }
348 })
349
350 t.Run("GetBySeason", func(t *testing.T) {
351 results, err := repo.GetBySeason(ctx, "Test Series", 1)
352 if err != nil {
353 t.Errorf("Failed to get TV shows by season: %v", err)
354 }
355
356 if len(results) != 2 {
357 t.Errorf("Expected 2 episodes of Test Series season 1, got %d", len(results))
358 }
359
360 for _, tvShow := range results {
361 if tvShow.Title != "Test Series" || tvShow.Season != 1 {
362 t.Errorf("Expected Test Series season 1, got %s season %d", tvShow.Title, tvShow.Season)
363 }
364 }
365 })
366
367 t.Run("MarkWatched", func(t *testing.T) {
368 err := repo.MarkWatched(ctx, tvShow1ID)
369 if err != nil {
370 t.Errorf("Failed to mark TV show as watched: %v", err)
371 }
372
373 updated, err := repo.Get(ctx, tvShow1ID)
374 if err != nil {
375 t.Fatalf("Failed to get updated TV show: %v", err)
376 }
377
378 if updated.Status != "watched" {
379 t.Errorf("Expected status to be watched, got %s", updated.Status)
380 }
381
382 if updated.LastWatched == nil {
383 t.Error("Expected last watched timestamp to be set")
384 }
385 })
386
387 t.Run("StartWatching", func(t *testing.T) {
388 newShow := &models.TVShow{Title: "New Show", Status: "queued"}
389 id, err := repo.Create(ctx, newShow)
390 if err != nil {
391 t.Fatalf("Failed to create new TV show: %v", err)
392 }
393
394 err = repo.StartWatching(ctx, id)
395 if err != nil {
396 t.Errorf("Failed to start watching TV show: %v", err)
397 }
398
399 updated, err := repo.Get(ctx, id)
400 if err != nil {
401 t.Fatalf("Failed to get updated TV show: %v", err)
402 }
403
404 if updated.Status != "watching" {
405 t.Errorf("Expected status to be watching, got %s", updated.Status)
406 }
407
408 if updated.LastWatched == nil {
409 t.Error("Expected last watched timestamp to be set")
410 }
411 })
412 })
413
414 t.Run("Count", func(t *testing.T) {
415 db := CreateTestDB(t)
416 repo := NewTVRepository(db)
417 ctx := context.Background()
418
419 tvShows := []*models.TVShow{
420 {Title: "Show 1", Status: "queued", Rating: 8.0},
421 {Title: "Show 2", Status: "watching", Rating: 7.0},
422 {Title: "Show 3", Status: "watched", Rating: 9.0},
423 {Title: "Show 4", Status: "queued", Rating: 8.5},
424 }
425
426 for _, tvShow := range tvShows {
427 _, err := repo.Create(ctx, tvShow)
428 if err != nil {
429 t.Fatalf("Failed to create TV show: %v", err)
430 }
431 }
432
433 t.Run("Count all TV shows", func(t *testing.T) {
434 count, err := repo.Count(ctx, TVListOptions{})
435 if err != nil {
436 t.Errorf("Failed to count TV shows: %v", err)
437 }
438
439 if count != 4 {
440 t.Errorf("Expected 4 TV shows, got %d", count)
441 }
442 })
443
444 t.Run("Count queued TV shows", func(t *testing.T) {
445 count, err := repo.Count(ctx, TVListOptions{Status: "queued"})
446 if err != nil {
447 t.Errorf("Failed to count queued TV shows: %v", err)
448 }
449
450 if count != 2 {
451 t.Errorf("Expected 2 queued TV shows, got %d", count)
452 }
453 })
454
455 t.Run("Count TV shows by rating", func(t *testing.T) {
456 count, err := repo.Count(ctx, TVListOptions{MinRating: 8.0})
457 if err != nil {
458 t.Errorf("Failed to count high-rated TV shows: %v", err)
459 }
460
461 if count != 3 {
462 t.Errorf("Expected 3 TV shows with rating >= 8.0, got %d", count)
463 }
464 })
465
466 t.Run("Count with context cancellation", func(t *testing.T) {
467 _, err := repo.Count(NewCanceledContext(), TVListOptions{})
468 AssertCancelledContext(t, err)
469 })
470 })
471
472 t.Run("Context Cancellation Error Paths", func(t *testing.T) {
473 db := CreateTestDB(t)
474 repo := NewTVRepository(db)
475 ctx := context.Background()
476
477 tvShow := NewTVShowBuilder().WithTitle("Test Show").WithSeason(1).WithEpisode(1).Build()
478 id, err := repo.Create(ctx, tvShow)
479 shared.AssertNoError(t, err, "Failed to create TV show")
480
481 t.Run("Create with cancelled context", func(t *testing.T) {
482 newShow := NewTVShowBuilder().WithTitle("Cancelled").Build()
483 _, err := repo.Create(NewCanceledContext(), newShow)
484 AssertCancelledContext(t, err)
485 })
486
487 t.Run("Get with cancelled context", func(t *testing.T) {
488 _, err := repo.Get(NewCanceledContext(), id)
489 AssertCancelledContext(t, err)
490 })
491
492 t.Run("Update with cancelled context", func(t *testing.T) {
493 tvShow.Title = "Updated"
494 err := repo.Update(NewCanceledContext(), tvShow)
495 AssertCancelledContext(t, err)
496 })
497
498 t.Run("Delete with cancelled context", func(t *testing.T) {
499 err := repo.Delete(NewCanceledContext(), id)
500 AssertCancelledContext(t, err)
501 })
502
503 t.Run("List with cancelled context", func(t *testing.T) {
504 _, err := repo.List(NewCanceledContext(), TVListOptions{})
505 AssertCancelledContext(t, err)
506 })
507
508 t.Run("GetQueued with cancelled context", func(t *testing.T) {
509 _, err := repo.GetQueued(NewCanceledContext())
510 AssertCancelledContext(t, err)
511 })
512
513 t.Run("GetWatching with cancelled context", func(t *testing.T) {
514 _, err := repo.GetWatching(NewCanceledContext())
515 AssertCancelledContext(t, err)
516 })
517
518 t.Run("GetWatched with cancelled context", func(t *testing.T) {
519 _, err := repo.GetWatched(NewCanceledContext())
520 AssertCancelledContext(t, err)
521 })
522
523 t.Run("GetByTitle with cancelled context", func(t *testing.T) {
524 _, err := repo.GetByTitle(NewCanceledContext(), "Test Show")
525 AssertCancelledContext(t, err)
526 })
527
528 t.Run("GetBySeason with cancelled context", func(t *testing.T) {
529 _, err := repo.GetBySeason(NewCanceledContext(), "Test Show", 1)
530 AssertCancelledContext(t, err)
531 })
532
533 t.Run("MarkWatched with cancelled context", func(t *testing.T) {
534 err := repo.MarkWatched(NewCanceledContext(), id)
535 AssertCancelledContext(t, err)
536 })
537
538 t.Run("StartWatching with cancelled context", func(t *testing.T) {
539 err := repo.StartWatching(NewCanceledContext(), id)
540 AssertCancelledContext(t, err)
541 })
542 })
543
544 t.Run("Edge Cases", func(t *testing.T) {
545 db := CreateTestDB(t)
546 repo := NewTVRepository(db)
547 ctx := context.Background()
548
549 t.Run("Get non-existent TV show", func(t *testing.T) {
550 _, err := repo.Get(ctx, 99999)
551 shared.AssertError(t, err, "Expected error for non-existent TV show")
552 })
553
554 t.Run("Update non-existent TV show succeeds with no rows affected", func(t *testing.T) {
555 show := NewTVShowBuilder().WithTitle("Non-existent").Build()
556 show.ID = 99999
557 err := repo.Update(ctx, show)
558 shared.AssertNoError(t, err, "Update should not error when no rows affected")
559 })
560
561 t.Run("Delete non-existent TV show succeeds with no rows affected", func(t *testing.T) {
562 err := repo.Delete(ctx, 99999)
563 shared.AssertNoError(t, err, "Delete should not error when no rows affected")
564 })
565
566 t.Run("MarkWatched non-existent TV show", func(t *testing.T) {
567 err := repo.MarkWatched(ctx, 99999)
568 shared.AssertError(t, err, "Expected error for non-existent TV show")
569 })
570
571 t.Run("StartWatching non-existent TV show", func(t *testing.T) {
572 err := repo.StartWatching(ctx, 99999)
573 shared.AssertError(t, err, "Expected error for non-existent TV show")
574 })
575
576 t.Run("GetByTitle with no results", func(t *testing.T) {
577 shows, err := repo.GetByTitle(ctx, "NonExistentShow")
578 shared.AssertNoError(t, err, "Should not error when no shows found")
579 shared.AssertEqual(t, 0, len(shows), "Expected empty result set")
580 })
581
582 t.Run("GetBySeason with no results", func(t *testing.T) {
583 shows, err := repo.GetBySeason(ctx, "NonExistentShow", 1)
584 shared.AssertNoError(t, err, "Should not error when no shows found")
585 shared.AssertEqual(t, 0, len(shows), "Expected empty result set")
586 })
587 })
588}