···1024 t.Errorf("Expected 6 models, got %d", len(models))
1025 }
10261027- // Test that all models have the required methods
1028 for i, model := range models {
1029- // Test ID methods
1030 model.SetID(int64(i + 1))
1031 if model.GetID() != int64(i+1) {
1032 t.Errorf("Model %d: ID not set correctly", i)
···1103 book := &Book{}
1104 note := &Note{}
11051106- // Test that zero values don't cause panics
1107 if task.IsCompleted() || task.IsPending() || task.IsDeleted() {
1108 t.Error("Zero value task should have false status methods")
1109 }
···11261127 if note.IsArchived() {
1128 t.Error("Zero value note should not be archived")
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001129 }
1130 })
1131 })
···1024 t.Errorf("Expected 6 models, got %d", len(models))
1025 }
102601027 for i, model := range models {
01028 model.SetID(int64(i + 1))
1029 if model.GetID() != int64(i+1) {
1030 t.Errorf("Model %d: ID not set correctly", i)
···1101 book := &Book{}
1102 note := &Note{}
110301104 if task.IsCompleted() || task.IsPending() || task.IsDeleted() {
1105 t.Error("Zero value task should have false status methods")
1106 }
···11231124 if note.IsArchived() {
1125 t.Error("Zero value note should not be archived")
1126+ }
1127+ })
1128+ })
1129+1130+ t.Run("TimeEntry Model", func(t *testing.T) {
1131+ t.Run("IsActive", func(t *testing.T) {
1132+ now := time.Now()
1133+1134+ t.Run("returns true when EndTime is nil", func(t *testing.T) {
1135+ te := &TimeEntry{
1136+ TaskID: 1,
1137+ StartTime: now,
1138+ EndTime: nil,
1139+ }
1140+1141+ if !te.IsActive() {
1142+ t.Error("TimeEntry with nil EndTime should be active")
1143+ }
1144+ })
1145+1146+ t.Run("returns false when EndTime is set", func(t *testing.T) {
1147+ endTime := now.Add(time.Hour)
1148+ te := &TimeEntry{
1149+ TaskID: 1,
1150+ StartTime: now,
1151+ EndTime: &endTime,
1152+ }
1153+1154+ if te.IsActive() {
1155+ t.Error("TimeEntry with EndTime should not be active")
1156+ }
1157+ })
1158+ })
1159+1160+ t.Run("Stop", func(t *testing.T) {
1161+ startTime := time.Now().Add(-time.Hour)
1162+ te := &TimeEntry{
1163+ TaskID: 1,
1164+ StartTime: startTime,
1165+ EndTime: nil,
1166+ Created: startTime,
1167+ Modified: startTime,
1168+ }
1169+1170+ if !te.IsActive() {
1171+ t.Error("TimeEntry should be active before Stop()")
1172+ }
1173+1174+ te.Stop()
1175+1176+ if te.IsActive() {
1177+ t.Error("TimeEntry should not be active after Stop()")
1178+ }
1179+1180+ if te.EndTime == nil {
1181+ t.Error("EndTime should be set after Stop()")
1182+ }
1183+1184+ if te.EndTime.Before(startTime) {
1185+ t.Error("EndTime should be after StartTime")
1186+ }
1187+1188+ expectedDuration := int64(te.EndTime.Sub(startTime).Seconds())
1189+ if te.DurationSeconds != expectedDuration {
1190+ t.Errorf("Expected DurationSeconds %d, got %d", expectedDuration, te.DurationSeconds)
1191+ }
1192+1193+ if te.Modified.Before(startTime) {
1194+ t.Error("Modified time should be updated after Stop()")
1195+ }
1196+ })
1197+1198+ t.Run("GetDuration", func(t *testing.T) {
1199+ startTime := time.Now().Add(-time.Hour)
1200+1201+ t.Run("returns calculated duration when stopped", func(t *testing.T) {
1202+ endTime := startTime.Add(30 * time.Minute)
1203+ te := &TimeEntry{
1204+ TaskID: 1,
1205+ StartTime: startTime,
1206+ EndTime: &endTime,
1207+ DurationSeconds: 1800,
1208+ }
1209+1210+ duration := te.GetDuration()
1211+ expectedDuration := 30 * time.Minute
1212+1213+ if duration != expectedDuration {
1214+ t.Errorf("Expected duration %v, got %v", expectedDuration, duration)
1215+ }
1216+ })
1217+1218+ t.Run("returns time since start when active", func(t *testing.T) {
1219+ te := &TimeEntry{
1220+ TaskID: 1,
1221+ StartTime: startTime,
1222+ EndTime: nil,
1223+ }
1224+1225+ duration := te.GetDuration()
1226+1227+ if duration < 59*time.Minute || duration > 61*time.Minute {
1228+ t.Errorf("Expected duration around 1 hour, got %v", duration)
1229+ }
1230+ })
1231+ })
1232+1233+ t.Run("Model Interface Implementation", func(t *testing.T) {
1234+ now := time.Now()
1235+ te := &TimeEntry{
1236+ ID: 1,
1237+ TaskID: 100,
1238+ Created: now,
1239+ Modified: now,
1240+ }
1241+1242+ if te.GetID() != 1 {
1243+ t.Errorf("Expected ID 1, got %d", te.GetID())
1244+ }
1245+1246+ te.SetID(2)
1247+ if te.GetID() != 2 {
1248+ t.Errorf("Expected ID 2 after SetID, got %d", te.GetID())
1249+ }
1250+1251+ if te.GetTableName() != "time_entries" {
1252+ t.Errorf("Expected table name 'time_entries', got '%s'", te.GetTableName())
1253+ }
1254+1255+ createdAt := time.Now()
1256+ te.SetCreatedAt(createdAt)
1257+ if !te.GetCreatedAt().Equal(createdAt) {
1258+ t.Errorf("Expected created at %v, got %v", createdAt, te.GetCreatedAt())
1259+ }
1260+1261+ updatedAt := time.Now().Add(time.Hour)
1262+ te.SetUpdatedAt(updatedAt)
1263+ if !te.GetUpdatedAt().Equal(updatedAt) {
1264+ t.Errorf("Expected updated at %v, got %v", updatedAt, te.GetUpdatedAt())
1265 }
1266 })
1267 })
+98-341
internal/repo/book_repository_test.go
···23import (
4 "context"
5- "database/sql"
6 "testing"
7 "time"
8···10 "github.com/stormlightlabs/noteleaf/internal/models"
11)
1213-func createBookTestDB(t *testing.T) *sql.DB {
14- db, err := sql.Open("sqlite3", ":memory:")
15- if err != nil {
16- t.Fatalf("Failed to create in-memory database: %v", err)
17- }
18-19- if _, err := db.Exec("PRAGMA foreign_keys = ON"); err != nil {
20- t.Fatalf("Failed to enable foreign keys: %v", err)
21- }
22-23- schema := `
24- CREATE TABLE IF NOT EXISTS books (
25- id INTEGER PRIMARY KEY AUTOINCREMENT,
26- title TEXT NOT NULL,
27- author TEXT,
28- status TEXT DEFAULT 'queued',
29- progress INTEGER DEFAULT 0,
30- pages INTEGER,
31- rating REAL,
32- notes TEXT,
33- added DATETIME DEFAULT CURRENT_TIMESTAMP,
34- started DATETIME,
35- finished DATETIME
36- );
37- `
38-39- if _, err := db.Exec(schema); err != nil {
40- t.Fatalf("Failed to create schema: %v", err)
41- }
42-43- t.Cleanup(func() {
44- db.Close()
45- })
46-47- return db
48-}
49-50-func createSampleBook() *models.Book {
51- return &models.Book{
52- Title: "Test Book",
53- Author: "Test Author",
54- Status: "queued",
55- Progress: 25,
56- Pages: 300,
57- Rating: 4.5,
58- Notes: "Interesting read",
59- }
60-}
61-62func TestBookRepository(t *testing.T) {
63 t.Run("CRUD Operations", func(t *testing.T) {
64- db := createBookTestDB(t)
65 repo := NewBookRepository(db)
66 ctx := context.Background()
6768 t.Run("Create Book", func(t *testing.T) {
69- book := createSampleBook()
7071 id, err := repo.Create(ctx, book)
72- if err != nil {
73- t.Errorf("Failed to create book: %v", err)
74- }
75-76- if id == 0 {
77- t.Error("Expected non-zero ID")
78- }
79-80- if book.ID != id {
81- t.Errorf("Expected book ID to be set to %d, got %d", id, book.ID)
82- }
83-84- if book.Added.IsZero() {
85- t.Error("Expected Added timestamp to be set")
86- }
87 })
8889 t.Run("Get Book", func(t *testing.T) {
90- original := createSampleBook()
91 id, err := repo.Create(ctx, original)
92- if err != nil {
93- t.Fatalf("Failed to create book: %v", err)
94- }
9596 retrieved, err := repo.Get(ctx, id)
97- if err != nil {
98- t.Errorf("Failed to get book: %v", err)
99- }
100101- if retrieved.Title != original.Title {
102- t.Errorf("Expected title %s, got %s", original.Title, retrieved.Title)
103- }
104- if retrieved.Author != original.Author {
105- t.Errorf("Expected author %s, got %s", original.Author, retrieved.Author)
106- }
107- if retrieved.Status != original.Status {
108- t.Errorf("Expected status %s, got %s", original.Status, retrieved.Status)
109- }
110- if retrieved.Progress != original.Progress {
111- t.Errorf("Expected progress %d, got %d", original.Progress, retrieved.Progress)
112- }
113- if retrieved.Pages != original.Pages {
114- t.Errorf("Expected pages %d, got %d", original.Pages, retrieved.Pages)
115- }
116- if retrieved.Rating != original.Rating {
117- t.Errorf("Expected rating %f, got %f", original.Rating, retrieved.Rating)
118- }
119- if retrieved.Notes != original.Notes {
120- t.Errorf("Expected notes %s, got %s", original.Notes, retrieved.Notes)
121- }
122 })
123124 t.Run("Update Book", func(t *testing.T) {
125- book := createSampleBook()
126 id, err := repo.Create(ctx, book)
127- if err != nil {
128- t.Fatalf("Failed to create book: %v", err)
129- }
130131 book.Title = "Updated Book"
132 book.Status = "reading"
···136 book.Started = &now
137138 err = repo.Update(ctx, book)
139- if err != nil {
140- t.Errorf("Failed to update book: %v", err)
141- }
142143 updated, err := repo.Get(ctx, id)
144- if err != nil {
145- t.Fatalf("Failed to get updated book: %v", err)
146- }
147148- if updated.Title != "Updated Book" {
149- t.Errorf("Expected updated title, got %s", updated.Title)
150- }
151- if updated.Status != "reading" {
152- t.Errorf("Expected status reading, got %s", updated.Status)
153- }
154- if updated.Progress != 50 {
155- t.Errorf("Expected progress 50, got %d", updated.Progress)
156- }
157- if updated.Rating != 5.0 {
158- t.Errorf("Expected rating 5.0, got %f", updated.Rating)
159- }
160- if updated.Started == nil {
161- t.Error("Expected started time to be set")
162- }
163 })
164165 t.Run("Delete Book", func(t *testing.T) {
166- book := createSampleBook()
167 id, err := repo.Create(ctx, book)
168- if err != nil {
169- t.Fatalf("Failed to create book: %v", err)
170- }
171172 err = repo.Delete(ctx, id)
173- if err != nil {
174- t.Errorf("Failed to delete book: %v", err)
175- }
176177 _, err = repo.Get(ctx, id)
178- if err == nil {
179- t.Error("Expected error when getting deleted book")
180- }
181 })
182 })
183184 t.Run("List", func(t *testing.T) {
185- db := createBookTestDB(t)
186 repo := NewBookRepository(db)
187 ctx := context.Background()
188···195196 for _, book := range books {
197 _, err := repo.Create(ctx, book)
198- if err != nil {
199- t.Fatalf("Failed to create book: %v", err)
200- }
201 }
202203 t.Run("List All Books", func(t *testing.T) {
204 results, err := repo.List(ctx, BookListOptions{})
205- if err != nil {
206- t.Errorf("Failed to list books: %v", err)
207- }
208-209- if len(results) != 4 {
210- t.Errorf("Expected 4 books, got %d", len(results))
211- }
212 })
213214 t.Run("List Books with Status Filter", func(t *testing.T) {
215 results, err := repo.List(ctx, BookListOptions{Status: "queued"})
216- if err != nil {
217- t.Errorf("Failed to list books: %v", err)
218- }
219-220- if len(results) != 2 {
221- t.Errorf("Expected 2 queued books, got %d", len(results))
222- }
223224 for _, book := range results {
225- if book.Status != "queued" {
226- t.Errorf("Expected queued status, got %s", book.Status)
227- }
228 }
229 })
230231 t.Run("List Books by Author", func(t *testing.T) {
232 results, err := repo.List(ctx, BookListOptions{Author: "Author A"})
233- if err != nil {
234- t.Errorf("Failed to list books: %v", err)
235- }
236-237- if len(results) != 2 {
238- t.Errorf("Expected 2 books by Author A, got %d", len(results))
239- }
240241 for _, book := range results {
242- if book.Author != "Author A" {
243- t.Errorf("Expected author 'Author A', got %s", book.Author)
244- }
245 }
246 })
247248 t.Run("List Books with Progress Filter", func(t *testing.T) {
249 results, err := repo.List(ctx, BookListOptions{MinProgress: 50})
250- if err != nil {
251- t.Errorf("Failed to list books: %v", err)
252- }
253-254- if len(results) != 2 {
255- t.Errorf("Expected 2 books with progress >= 50, got %d", len(results))
256- }
257258 for _, book := range results {
259- if book.Progress < 50 {
260- t.Errorf("Expected progress >= 50, got %d", book.Progress)
261- }
262 }
263 })
264265 t.Run("List Books with Rating Filter", func(t *testing.T) {
266 results, err := repo.List(ctx, BookListOptions{MinRating: 4.5})
267- if err != nil {
268- t.Errorf("Failed to list books: %v", err)
269- }
270-271- if len(results) != 2 {
272- t.Errorf("Expected 2 books with rating >= 4.5, got %d", len(results))
273- }
274275 for _, book := range results {
276- if book.Rating < 4.5 {
277- t.Errorf("Expected rating >= 4.5, got %f", book.Rating)
278- }
279 }
280 })
281282 t.Run("List Books with Search", func(t *testing.T) {
283 results, err := repo.List(ctx, BookListOptions{Search: "Book 1"})
284- if err != nil {
285- t.Errorf("Failed to list books: %v", err)
286- }
287-288- if len(results) != 1 {
289- t.Errorf("Expected 1 book matching search, got %d", len(results))
290- }
291292- if len(results) > 0 && results[0].Title != "Book 1" {
293- t.Errorf("Expected 'Book 1', got %s", results[0].Title)
294 }
295 })
296297 t.Run("List Books with Limit", func(t *testing.T) {
298 results, err := repo.List(ctx, BookListOptions{Limit: 2})
299- if err != nil {
300- t.Errorf("Failed to list books: %v", err)
301- }
302-303- if len(results) != 2 {
304- t.Errorf("Expected 2 books due to limit, got %d", len(results))
305- }
306 })
307 })
308309 t.Run("Special Methods", func(t *testing.T) {
310- db := createBookTestDB(t)
311 repo := NewBookRepository(db)
312 ctx := context.Background()
313···319 var book1ID int64
320 for _, book := range []*models.Book{book1, book2, book3, book4} {
321 id, err := repo.Create(ctx, book)
322- if err != nil {
323- t.Fatalf("Failed to create book: %v", err)
324- }
325 if book == book1 {
326 book1ID = id
327 }
···329330 t.Run("GetQueued", func(t *testing.T) {
331 results, err := repo.GetQueued(ctx)
332- if err != nil {
333- t.Errorf("Failed to get queued books: %v", err)
334- }
335-336- if len(results) != 2 {
337- t.Errorf("Expected 2 queued books, got %d", len(results))
338- }
339340 for _, book := range results {
341- if book.Status != "queued" {
342- t.Errorf("Expected queued status, got %s", book.Status)
343- }
344 }
345 })
346347 t.Run("GetReading", func(t *testing.T) {
348 results, err := repo.GetReading(ctx)
349- if err != nil {
350- t.Errorf("Failed to get reading books: %v", err)
351- }
352-353- if len(results) != 1 {
354- t.Errorf("Expected 1 reading book, got %d", len(results))
355- }
356357- if len(results) > 0 && results[0].Status != "reading" {
358- t.Errorf("Expected reading status, got %s", results[0].Status)
359 }
360 })
361362 t.Run("GetFinished", func(t *testing.T) {
363 results, err := repo.GetFinished(ctx)
364- if err != nil {
365- t.Errorf("Failed to get finished books: %v", err)
366- }
367-368- if len(results) != 1 {
369- t.Errorf("Expected 1 finished book, got %d", len(results))
370- }
371372- if len(results) > 0 && results[0].Status != "finished" {
373- t.Errorf("Expected finished status, got %s", results[0].Status)
374 }
375 })
376377 t.Run("GetByAuthor", func(t *testing.T) {
378 results, err := repo.GetByAuthor(ctx, "Author A")
379- if err != nil {
380- t.Errorf("Failed to get books by author: %v", err)
381- }
382-383- if len(results) != 2 {
384- t.Errorf("Expected 2 books by Author A, got %d", len(results))
385- }
386387 for _, book := range results {
388- if book.Author != "Author A" {
389- t.Errorf("Expected author 'Author A', got %s", book.Author)
390- }
391 }
392 })
393394 t.Run("StartReading", func(t *testing.T) {
395 err := repo.StartReading(ctx, book1ID)
396- if err != nil {
397- t.Errorf("Failed to start reading book: %v", err)
398- }
399400 updated, err := repo.Get(ctx, book1ID)
401- if err != nil {
402- t.Fatalf("Failed to get updated book: %v", err)
403- }
404405- if updated.Status != "reading" {
406- t.Errorf("Expected status to be reading, got %s", updated.Status)
407- }
408-409- if updated.Started == nil {
410- t.Error("Expected started timestamp to be set")
411- }
412 })
413414 t.Run("FinishReading", func(t *testing.T) {
415 newBook := &models.Book{Title: "New Book", Status: "reading", Progress: 80}
416 id, err := repo.Create(ctx, newBook)
417- if err != nil {
418- t.Fatalf("Failed to create new book: %v", err)
419- }
420421 err = repo.FinishReading(ctx, id)
422- if err != nil {
423- t.Errorf("Failed to finish reading book: %v", err)
424- }
425426 updated, err := repo.Get(ctx, id)
427- if err != nil {
428- t.Fatalf("Failed to get updated book: %v", err)
429- }
430-431- if updated.Status != "finished" {
432- t.Errorf("Expected status to be finished, got %s", updated.Status)
433- }
434435- if updated.Progress != 100 {
436- t.Errorf("Expected progress to be 100, got %d", updated.Progress)
437- }
438-439- if updated.Finished == nil {
440- t.Error("Expected finished timestamp to be set")
441- }
442 })
443444 t.Run("UpdateProgress", func(t *testing.T) {
445 newBook := &models.Book{Title: "Progress Book", Status: "queued", Progress: 0}
446 id, err := repo.Create(ctx, newBook)
447- if err != nil {
448- t.Fatalf("Failed to create new book: %v", err)
449- }
450451 err = repo.UpdateProgress(ctx, id, 25)
452- if err != nil {
453- t.Errorf("Failed to update progress: %v", err)
454- }
455456 updated, err := repo.Get(ctx, id)
457- if err != nil {
458- t.Fatalf("Failed to get updated book: %v", err)
459- }
460-461- if updated.Status != "reading" {
462- t.Errorf("Expected status to be reading when progress > 0, got %s", updated.Status)
463- }
464465- if updated.Progress != 25 {
466- t.Errorf("Expected progress 25, got %d", updated.Progress)
467- }
468-469- if updated.Started == nil {
470- t.Error("Expected started timestamp to be set when progress > 0")
471- }
472473 err = repo.UpdateProgress(ctx, id, 100)
474- if err != nil {
475- t.Errorf("Failed to update progress to 100: %v", err)
476- }
477478 updated, err = repo.Get(ctx, id)
479- if err != nil {
480- t.Fatalf("Failed to get updated book: %v", err)
481- }
482483- if updated.Status != "finished" {
484- t.Errorf("Expected status to be finished when progress = 100, got %s", updated.Status)
485- }
486-487- if updated.Progress != 100 {
488- t.Errorf("Expected progress 100, got %d", updated.Progress)
489- }
490-491- if updated.Finished == nil {
492- t.Error("Expected finished timestamp to be set when progress = 100")
493- }
494 })
495 })
496497 t.Run("Count", func(t *testing.T) {
498- db := createBookTestDB(t)
499 repo := NewBookRepository(db)
500 ctx := context.Background()
501···508509 for _, book := range books {
510 _, err := repo.Create(ctx, book)
511- if err != nil {
512- t.Fatalf("Failed to create book: %v", err)
513- }
514 }
515516 t.Run("Count all books", func(t *testing.T) {
517 count, err := repo.Count(ctx, BookListOptions{})
518- if err != nil {
519- t.Errorf("Failed to count books: %v", err)
520- }
521-522- if count != 4 {
523- t.Errorf("Expected 4 books, got %d", count)
524- }
525 })
526527 t.Run("Count queued books", func(t *testing.T) {
528 count, err := repo.Count(ctx, BookListOptions{Status: "queued"})
529- if err != nil {
530- t.Errorf("Failed to count queued books: %v", err)
531- }
532-533- if count != 2 {
534- t.Errorf("Expected 2 queued books, got %d", count)
535- }
536 })
537538 t.Run("Count books by progress", func(t *testing.T) {
539 count, err := repo.Count(ctx, BookListOptions{MinProgress: 50})
540- if err != nil {
541- t.Errorf("Failed to count books with progress >= 50: %v", err)
542- }
543-544- if count != 2 {
545- t.Errorf("Expected 2 books with progress >= 50, got %d", count)
546- }
547 })
548549 t.Run("Count books by rating", func(t *testing.T) {
550 count, err := repo.Count(ctx, BookListOptions{MinRating: 4.0})
551- if err != nil {
552- t.Errorf("Failed to count high-rated books: %v", err)
553- }
554-555- if count != 3 {
556- t.Errorf("Expected 3 books with rating >= 4.0, got %d", count)
557- }
558 })
559 })
560}
···23import (
4 "context"
05 "testing"
6 "time"
7···9 "github.com/stormlightlabs/noteleaf/internal/models"
10)
11000000000000000000000000000000000000000000000000012func TestBookRepository(t *testing.T) {
13 t.Run("CRUD Operations", func(t *testing.T) {
14+ db := CreateTestDB(t)
15 repo := NewBookRepository(db)
16 ctx := context.Background()
1718 t.Run("Create Book", func(t *testing.T) {
19+ book := CreateSampleBook()
2021 id, err := repo.Create(ctx, book)
22+ AssertNoError(t, err, "Failed to create book")
23+ AssertNotEqual(t, int64(0), id, "Expected non-zero ID")
24+ AssertEqual(t, id, book.ID, "Expected book ID to be set correctly")
25+ AssertFalse(t, book.Added.IsZero(), "Expected Added timestamp to be set")
0000000000026 })
2728 t.Run("Get Book", func(t *testing.T) {
29+ original := CreateSampleBook()
30 id, err := repo.Create(ctx, original)
31+ AssertNoError(t, err, "Failed to create book")
003233 retrieved, err := repo.Get(ctx, id)
34+ AssertNoError(t, err, "Failed to get book")
003536+ AssertEqual(t, original.Title, retrieved.Title, "Title mismatch")
37+ AssertEqual(t, original.Author, retrieved.Author, "Author mismatch")
38+ AssertEqual(t, original.Status, retrieved.Status, "Status mismatch")
39+ AssertEqual(t, original.Progress, retrieved.Progress, "Progress mismatch")
40+ AssertEqual(t, original.Pages, retrieved.Pages, "Pages mismatch")
41+ AssertEqual(t, original.Rating, retrieved.Rating, "Rating mismatch")
42+ AssertEqual(t, original.Notes, retrieved.Notes, "Notes mismatch")
0000000000000043 })
4445 t.Run("Update Book", func(t *testing.T) {
46+ book := CreateSampleBook()
47 id, err := repo.Create(ctx, book)
48+ AssertNoError(t, err, "Failed to create book")
004950 book.Title = "Updated Book"
51 book.Status = "reading"
···55 book.Started = &now
5657 err = repo.Update(ctx, book)
58+ AssertNoError(t, err, "Failed to update book")
005960 updated, err := repo.Get(ctx, id)
61+ AssertNoError(t, err, "Failed to get updated book")
006263+ AssertEqual(t, "Updated Book", updated.Title, "Expected updated title")
64+ AssertEqual(t, "reading", updated.Status, "Expected reading status")
65+ AssertEqual(t, 50, updated.Progress, "Expected progress 50")
66+ AssertEqual(t, 5.0, updated.Rating, "Expected rating 5.0")
67+ AssertTrue(t, updated.Started != nil, "Expected started time to be set")
000000000068 })
6970 t.Run("Delete Book", func(t *testing.T) {
71+ book := CreateSampleBook()
72 id, err := repo.Create(ctx, book)
73+ AssertNoError(t, err, "Failed to create book")
007475 err = repo.Delete(ctx, id)
76+ AssertNoError(t, err, "Failed to delete book")
007778 _, err = repo.Get(ctx, id)
79+ AssertError(t, err, "Expected error when getting deleted book")
0080 })
81 })
8283 t.Run("List", func(t *testing.T) {
84+ db := CreateTestDB(t)
85 repo := NewBookRepository(db)
86 ctx := context.Background()
87···9495 for _, book := range books {
96 _, err := repo.Create(ctx, book)
97+ AssertNoError(t, err, "Failed to create book")
0098 }
99100 t.Run("List All Books", func(t *testing.T) {
101 results, err := repo.List(ctx, BookListOptions{})
102+ AssertNoError(t, err, "Failed to list books")
103+ AssertEqual(t, 4, len(results), "Expected 4 books")
00000104 })
105106 t.Run("List Books with Status Filter", func(t *testing.T) {
107 results, err := repo.List(ctx, BookListOptions{Status: "queued"})
108+ AssertNoError(t, err, "Failed to list books")
109+ AssertEqual(t, 2, len(results), "Expected 2 queued books")
00000110111 for _, book := range results {
112+ AssertEqual(t, "queued", book.Status, "Expected queued status")
00113 }
114 })
115116 t.Run("List Books by Author", func(t *testing.T) {
117 results, err := repo.List(ctx, BookListOptions{Author: "Author A"})
118+ AssertNoError(t, err, "Failed to list books")
119+ AssertEqual(t, 2, len(results), "Expected 2 books by Author A")
00000120121 for _, book := range results {
122+ AssertEqual(t, "Author A", book.Author, "Expected author 'Author A'")
00123 }
124 })
125126 t.Run("List Books with Progress Filter", func(t *testing.T) {
127 results, err := repo.List(ctx, BookListOptions{MinProgress: 50})
128+ AssertNoError(t, err, "Failed to list books")
129+ AssertEqual(t, 2, len(results), "Expected 2 books with progress >= 50")
00000130131 for _, book := range results {
132+ AssertTrue(t, book.Progress >= 50, "Expected progress >= 50")
00133 }
134 })
135136 t.Run("List Books with Rating Filter", func(t *testing.T) {
137 results, err := repo.List(ctx, BookListOptions{MinRating: 4.5})
138+ AssertNoError(t, err, "Failed to list books")
139+ AssertEqual(t, 2, len(results), "Expected 2 books with rating >= 4.5")
00000140141 for _, book := range results {
142+ AssertTrue(t, book.Rating >= 4.5, "Expected rating >= 4.5")
00143 }
144 })
145146 t.Run("List Books with Search", func(t *testing.T) {
147 results, err := repo.List(ctx, BookListOptions{Search: "Book 1"})
148+ AssertNoError(t, err, "Failed to list books")
149+ AssertEqual(t, 1, len(results), "Expected 1 book matching search")
00000150151+ if len(results) > 0 {
152+ AssertEqual(t, "Book 1", results[0].Title, "Expected 'Book 1'")
153 }
154 })
155156 t.Run("List Books with Limit", func(t *testing.T) {
157 results, err := repo.List(ctx, BookListOptions{Limit: 2})
158+ AssertNoError(t, err, "Failed to list books")
159+ AssertEqual(t, 2, len(results), "Expected 2 books due to limit")
00000160 })
161 })
162163 t.Run("Special Methods", func(t *testing.T) {
164+ db := CreateTestDB(t)
165 repo := NewBookRepository(db)
166 ctx := context.Background()
167···173 var book1ID int64
174 for _, book := range []*models.Book{book1, book2, book3, book4} {
175 id, err := repo.Create(ctx, book)
176+ AssertNoError(t, err, "Failed to create book")
00177 if book == book1 {
178 book1ID = id
179 }
···181182 t.Run("GetQueued", func(t *testing.T) {
183 results, err := repo.GetQueued(ctx)
184+ AssertNoError(t, err, "Failed to get queued books")
185+ AssertEqual(t, 2, len(results), "Expected 2 queued books")
00000186187 for _, book := range results {
188+ AssertEqual(t, "queued", book.Status, "Expected queued status")
00189 }
190 })
191192 t.Run("GetReading", func(t *testing.T) {
193 results, err := repo.GetReading(ctx)
194+ AssertNoError(t, err, "Failed to get reading books")
195+ AssertEqual(t, 1, len(results), "Expected 1 reading book")
00000196197+ if len(results) > 0 {
198+ AssertEqual(t, "reading", results[0].Status, "Expected reading status")
199 }
200 })
201202 t.Run("GetFinished", func(t *testing.T) {
203 results, err := repo.GetFinished(ctx)
204+ AssertNoError(t, err, "Failed to get finished books")
205+ AssertEqual(t, 1, len(results), "Expected 1 finished book")
00000206207+ if len(results) > 0 {
208+ AssertEqual(t, "finished", results[0].Status, "Expected finished status")
209 }
210 })
211212 t.Run("GetByAuthor", func(t *testing.T) {
213 results, err := repo.GetByAuthor(ctx, "Author A")
214+ AssertNoError(t, err, "Failed to get books by author")
215+ AssertEqual(t, 2, len(results), "Expected 2 books by Author A")
00000216217 for _, book := range results {
218+ AssertEqual(t, "Author A", book.Author, "Expected author 'Author A'")
00219 }
220 })
221222 t.Run("StartReading", func(t *testing.T) {
223 err := repo.StartReading(ctx, book1ID)
224+ AssertNoError(t, err, "Failed to start reading book")
00225226 updated, err := repo.Get(ctx, book1ID)
227+ AssertNoError(t, err, "Failed to get updated book")
00228229+ AssertEqual(t, "reading", updated.Status, "Expected status to be reading")
230+ AssertTrue(t, updated.Started != nil, "Expected started timestamp to be set")
00000231 })
232233 t.Run("FinishReading", func(t *testing.T) {
234 newBook := &models.Book{Title: "New Book", Status: "reading", Progress: 80}
235 id, err := repo.Create(ctx, newBook)
236+ AssertNoError(t, err, "Failed to create new book")
00237238 err = repo.FinishReading(ctx, id)
239+ AssertNoError(t, err, "Failed to finish reading book")
00240241 updated, err := repo.Get(ctx, id)
242+ AssertNoError(t, err, "Failed to get updated book")
000000243244+ AssertEqual(t, "finished", updated.Status, "Expected status to be finished")
245+ AssertEqual(t, 100, updated.Progress, "Expected progress to be 100")
246+ AssertTrue(t, updated.Finished != nil, "Expected finished timestamp to be set")
0000247 })
248249 t.Run("UpdateProgress", func(t *testing.T) {
250 newBook := &models.Book{Title: "Progress Book", Status: "queued", Progress: 0}
251 id, err := repo.Create(ctx, newBook)
252+ AssertNoError(t, err, "Failed to create new book")
00253254 err = repo.UpdateProgress(ctx, id, 25)
255+ AssertNoError(t, err, "Failed to update progress")
00256257 updated, err := repo.Get(ctx, id)
258+ AssertNoError(t, err, "Failed to get updated book")
000000259260+ AssertEqual(t, "reading", updated.Status, "Expected status to be reading when progress > 0")
261+ AssertEqual(t, 25, updated.Progress, "Expected progress 25")
262+ AssertTrue(t, updated.Started != nil, "Expected started timestamp to be set when progress > 0")
0000263264 err = repo.UpdateProgress(ctx, id, 100)
265+ AssertNoError(t, err, "Failed to update progress to 100")
00266267 updated, err = repo.Get(ctx, id)
268+ AssertNoError(t, err, "Failed to get updated book")
00269270+ AssertEqual(t, "finished", updated.Status, "Expected status to be finished when progress = 100")
271+ AssertEqual(t, 100, updated.Progress, "Expected progress 100")
272+ AssertTrue(t, updated.Finished != nil, "Expected finished timestamp to be set when progress = 100")
00000000273 })
274 })
275276 t.Run("Count", func(t *testing.T) {
277+ db := CreateTestDB(t)
278 repo := NewBookRepository(db)
279 ctx := context.Background()
280···287288 for _, book := range books {
289 _, err := repo.Create(ctx, book)
290+ AssertNoError(t, err, "Failed to create book")
00291 }
292293 t.Run("Count all books", func(t *testing.T) {
294 count, err := repo.Count(ctx, BookListOptions{})
295+ AssertNoError(t, err, "Failed to count books")
296+ AssertEqual(t, int64(4), count, "Expected 4 books")
00000297 })
298299 t.Run("Count queued books", func(t *testing.T) {
300 count, err := repo.Count(ctx, BookListOptions{Status: "queued"})
301+ AssertNoError(t, err, "Failed to count queued books")
302+ AssertEqual(t, int64(2), count, "Expected 2 queued books")
00000303 })
304305 t.Run("Count books by progress", func(t *testing.T) {
306 count, err := repo.Count(ctx, BookListOptions{MinProgress: 50})
307+ AssertNoError(t, err, "Failed to count books with progress >= 50")
308+ AssertEqual(t, int64(2), count, "Expected 2 books with progress >= 50")
00000309 })
310311 t.Run("Count books by rating", func(t *testing.T) {
312 count, err := repo.Count(ctx, BookListOptions{MinRating: 4.0})
313+ AssertNoError(t, err, "Failed to count high-rated books")
314+ AssertEqual(t, int64(3), count, "Expected 3 books with rating >= 4.0")
00000315 })
316 })
317}