cli + tui to publish to leaflet (wip) & manage tasks, notes & watch/read lists ๐Ÿƒ
charm leaflet readability golang

refactor: repository test utilities build: apply test utilities

+1334 -1272
+139 -3
internal/models/models_test.go
··· 1024 t.Errorf("Expected 6 models, got %d", len(models)) 1025 } 1026 1027 - // 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{} 1105 1106 - // 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 } ··· 1126 1127 if note.IsArchived() { 1128 t.Error("Zero value note should not be archived") 1129 } 1130 }) 1131 })
··· 1024 t.Errorf("Expected 6 models, got %d", len(models)) 1025 } 1026 1027 for i, model := range models { 1028 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{} 1103 1104 if task.IsCompleted() || task.IsPending() || task.IsDeleted() { 1105 t.Error("Zero value task should have false status methods") 1106 } ··· 1123 1124 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
··· 2 3 import ( 4 "context" 5 - "database/sql" 6 "testing" 7 "time" 8 ··· 10 "github.com/stormlightlabs/noteleaf/internal/models" 11 ) 12 13 - 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 - 62 func 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() 67 68 t.Run("Create Book", func(t *testing.T) { 69 - book := createSampleBook() 70 71 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 }) 88 89 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 - } 95 96 retrieved, err := repo.Get(ctx, id) 97 - if err != nil { 98 - t.Errorf("Failed to get book: %v", err) 99 - } 100 101 - 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 }) 123 124 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 - } 130 131 book.Title = "Updated Book" 132 book.Status = "reading" ··· 136 book.Started = &now 137 138 err = repo.Update(ctx, book) 139 - if err != nil { 140 - t.Errorf("Failed to update book: %v", err) 141 - } 142 143 updated, err := repo.Get(ctx, id) 144 - if err != nil { 145 - t.Fatalf("Failed to get updated book: %v", err) 146 - } 147 148 - 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 }) 164 165 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 - } 171 172 err = repo.Delete(ctx, id) 173 - if err != nil { 174 - t.Errorf("Failed to delete book: %v", err) 175 - } 176 177 _, err = repo.Get(ctx, id) 178 - if err == nil { 179 - t.Error("Expected error when getting deleted book") 180 - } 181 }) 182 }) 183 184 t.Run("List", func(t *testing.T) { 185 - db := createBookTestDB(t) 186 repo := NewBookRepository(db) 187 ctx := context.Background() 188 ··· 195 196 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 } 202 203 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 }) 213 214 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 - } 223 224 for _, book := range results { 225 - if book.Status != "queued" { 226 - t.Errorf("Expected queued status, got %s", book.Status) 227 - } 228 } 229 }) 230 231 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 - } 240 241 for _, book := range results { 242 - if book.Author != "Author A" { 243 - t.Errorf("Expected author 'Author A', got %s", book.Author) 244 - } 245 } 246 }) 247 248 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 - } 257 258 for _, book := range results { 259 - if book.Progress < 50 { 260 - t.Errorf("Expected progress >= 50, got %d", book.Progress) 261 - } 262 } 263 }) 264 265 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 - } 274 275 for _, book := range results { 276 - if book.Rating < 4.5 { 277 - t.Errorf("Expected rating >= 4.5, got %f", book.Rating) 278 - } 279 } 280 }) 281 282 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 - } 291 292 - if len(results) > 0 && results[0].Title != "Book 1" { 293 - t.Errorf("Expected 'Book 1', got %s", results[0].Title) 294 } 295 }) 296 297 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 }) 308 309 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 } ··· 329 330 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 - } 339 340 for _, book := range results { 341 - if book.Status != "queued" { 342 - t.Errorf("Expected queued status, got %s", book.Status) 343 - } 344 } 345 }) 346 347 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 - } 356 357 - if len(results) > 0 && results[0].Status != "reading" { 358 - t.Errorf("Expected reading status, got %s", results[0].Status) 359 } 360 }) 361 362 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 - } 371 372 - if len(results) > 0 && results[0].Status != "finished" { 373 - t.Errorf("Expected finished status, got %s", results[0].Status) 374 } 375 }) 376 377 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 - } 386 387 for _, book := range results { 388 - if book.Author != "Author A" { 389 - t.Errorf("Expected author 'Author A', got %s", book.Author) 390 - } 391 } 392 }) 393 394 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 - } 399 400 updated, err := repo.Get(ctx, book1ID) 401 - if err != nil { 402 - t.Fatalf("Failed to get updated book: %v", err) 403 - } 404 405 - 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 }) 413 414 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 - } 420 421 err = repo.FinishReading(ctx, id) 422 - if err != nil { 423 - t.Errorf("Failed to finish reading book: %v", err) 424 - } 425 426 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 - } 434 435 - 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 }) 443 444 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 - } 450 451 err = repo.UpdateProgress(ctx, id, 25) 452 - if err != nil { 453 - t.Errorf("Failed to update progress: %v", err) 454 - } 455 456 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 - } 464 465 - 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 - } 472 473 err = repo.UpdateProgress(ctx, id, 100) 474 - if err != nil { 475 - t.Errorf("Failed to update progress to 100: %v", err) 476 - } 477 478 updated, err = repo.Get(ctx, id) 479 - if err != nil { 480 - t.Fatalf("Failed to get updated book: %v", err) 481 - } 482 483 - 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 }) 496 497 t.Run("Count", func(t *testing.T) { 498 - db := createBookTestDB(t) 499 repo := NewBookRepository(db) 500 ctx := context.Background() 501 ··· 508 509 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 } 515 516 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 }) 526 527 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 }) 537 538 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 }) 548 549 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 }
··· 2 3 import ( 4 "context" 5 "testing" 6 "time" 7 ··· 9 "github.com/stormlightlabs/noteleaf/internal/models" 10 ) 11 12 func 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() 17 18 t.Run("Create Book", func(t *testing.T) { 19 + book := CreateSampleBook() 20 21 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") 26 }) 27 28 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") 32 33 retrieved, err := repo.Get(ctx, id) 34 + AssertNoError(t, err, "Failed to get book") 35 36 + 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") 43 }) 44 45 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") 49 50 book.Title = "Updated Book" 51 book.Status = "reading" ··· 55 book.Started = &now 56 57 err = repo.Update(ctx, book) 58 + AssertNoError(t, err, "Failed to update book") 59 60 updated, err := repo.Get(ctx, id) 61 + AssertNoError(t, err, "Failed to get updated book") 62 63 + 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") 68 }) 69 70 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") 74 75 err = repo.Delete(ctx, id) 76 + AssertNoError(t, err, "Failed to delete book") 77 78 _, err = repo.Get(ctx, id) 79 + AssertError(t, err, "Expected error when getting deleted book") 80 }) 81 }) 82 83 t.Run("List", func(t *testing.T) { 84 + db := CreateTestDB(t) 85 repo := NewBookRepository(db) 86 ctx := context.Background() 87 ··· 94 95 for _, book := range books { 96 _, err := repo.Create(ctx, book) 97 + AssertNoError(t, err, "Failed to create book") 98 } 99 100 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") 104 }) 105 106 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") 110 111 for _, book := range results { 112 + AssertEqual(t, "queued", book.Status, "Expected queued status") 113 } 114 }) 115 116 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") 120 121 for _, book := range results { 122 + AssertEqual(t, "Author A", book.Author, "Expected author 'Author A'") 123 } 124 }) 125 126 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") 130 131 for _, book := range results { 132 + AssertTrue(t, book.Progress >= 50, "Expected progress >= 50") 133 } 134 }) 135 136 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") 140 141 for _, book := range results { 142 + AssertTrue(t, book.Rating >= 4.5, "Expected rating >= 4.5") 143 } 144 }) 145 146 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") 150 151 + if len(results) > 0 { 152 + AssertEqual(t, "Book 1", results[0].Title, "Expected 'Book 1'") 153 } 154 }) 155 156 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") 160 }) 161 }) 162 163 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") 177 if book == book1 { 178 book1ID = id 179 } ··· 181 182 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") 186 187 for _, book := range results { 188 + AssertEqual(t, "queued", book.Status, "Expected queued status") 189 } 190 }) 191 192 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") 196 197 + if len(results) > 0 { 198 + AssertEqual(t, "reading", results[0].Status, "Expected reading status") 199 } 200 }) 201 202 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") 206 207 + if len(results) > 0 { 208 + AssertEqual(t, "finished", results[0].Status, "Expected finished status") 209 } 210 }) 211 212 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") 216 217 for _, book := range results { 218 + AssertEqual(t, "Author A", book.Author, "Expected author 'Author A'") 219 } 220 }) 221 222 t.Run("StartReading", func(t *testing.T) { 223 err := repo.StartReading(ctx, book1ID) 224 + AssertNoError(t, err, "Failed to start reading book") 225 226 updated, err := repo.Get(ctx, book1ID) 227 + AssertNoError(t, err, "Failed to get updated book") 228 229 + AssertEqual(t, "reading", updated.Status, "Expected status to be reading") 230 + AssertTrue(t, updated.Started != nil, "Expected started timestamp to be set") 231 }) 232 233 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") 237 238 err = repo.FinishReading(ctx, id) 239 + AssertNoError(t, err, "Failed to finish reading book") 240 241 updated, err := repo.Get(ctx, id) 242 + AssertNoError(t, err, "Failed to get updated book") 243 244 + 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") 247 }) 248 249 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") 253 254 err = repo.UpdateProgress(ctx, id, 25) 255 + AssertNoError(t, err, "Failed to update progress") 256 257 updated, err := repo.Get(ctx, id) 258 + AssertNoError(t, err, "Failed to get updated book") 259 260 + 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") 263 264 err = repo.UpdateProgress(ctx, id, 100) 265 + AssertNoError(t, err, "Failed to update progress to 100") 266 267 updated, err = repo.Get(ctx, id) 268 + AssertNoError(t, err, "Failed to get updated book") 269 270 + 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") 273 }) 274 }) 275 276 t.Run("Count", func(t *testing.T) { 277 + db := CreateTestDB(t) 278 repo := NewBookRepository(db) 279 ctx := context.Background() 280 ··· 287 288 for _, book := range books { 289 _, err := repo.Create(ctx, book) 290 + AssertNoError(t, err, "Failed to create book") 291 } 292 293 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") 297 }) 298 299 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") 303 }) 304 305 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") 309 }) 310 311 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") 315 }) 316 }) 317 }
+300
internal/repo/find_methods_test.go
···
··· 1 + package repo 2 + 3 + import ( 4 + "context" 5 + "testing" 6 + ) 7 + 8 + func TestFindMethods(t *testing.T) { 9 + db := CreateTestDB(t) 10 + repos := SetupTestData(t, db) 11 + ctx := context.Background() 12 + 13 + t.Run("TaskRepository Find", func(t *testing.T) { 14 + t.Run("finds tasks by status", func(t *testing.T) { 15 + options := TaskListOptions{ 16 + Status: "pending", 17 + } 18 + tasks, err := repos.Tasks.Find(ctx, options) 19 + AssertNoError(t, err, "Find should succeed") 20 + AssertTrue(t, len(tasks) >= 1, "Should find at least one pending task") 21 + for _, task := range tasks { 22 + AssertEqual(t, "pending", task.Status, "All returned tasks should be pending") 23 + } 24 + }) 25 + 26 + t.Run("finds tasks by priority", func(t *testing.T) { 27 + options := TaskListOptions{ 28 + Priority: "high", 29 + } 30 + tasks, err := repos.Tasks.Find(ctx, options) 31 + AssertNoError(t, err, "Find should succeed") 32 + AssertTrue(t, len(tasks) >= 1, "Should find at least one high priority task") 33 + for _, task := range tasks { 34 + AssertEqual(t, "high", task.Priority, "All returned tasks should be high priority") 35 + } 36 + }) 37 + 38 + t.Run("finds tasks by project", func(t *testing.T) { 39 + options := TaskListOptions{ 40 + Project: "test-project", 41 + } 42 + tasks, err := repos.Tasks.Find(ctx, options) 43 + AssertNoError(t, err, "Find should succeed") 44 + AssertTrue(t, len(tasks) >= 1, "Should find tasks in test-project") 45 + for _, task := range tasks { 46 + AssertEqual(t, "test-project", task.Project, "All returned tasks should be in test-project") 47 + } 48 + }) 49 + 50 + t.Run("finds tasks by context", func(t *testing.T) { 51 + options := TaskListOptions{ 52 + Context: "test-context", 53 + } 54 + tasks, err := repos.Tasks.Find(ctx, options) 55 + AssertNoError(t, err, "Find should succeed") 56 + AssertTrue(t, len(tasks) >= 1, "Should find tasks in test-context") 57 + for _, task := range tasks { 58 + AssertEqual(t, "test-context", task.Context, "All returned tasks should be in test-context") 59 + } 60 + }) 61 + 62 + t.Run("finds tasks by multiple criteria", func(t *testing.T) { 63 + options := TaskListOptions{ 64 + Status: "pending", 65 + Priority: "high", 66 + Project: "test-project", 67 + } 68 + tasks, err := repos.Tasks.Find(ctx, options) 69 + AssertNoError(t, err, "Find should succeed") 70 + for _, task := range tasks { 71 + AssertEqual(t, "pending", task.Status, "Task should be pending") 72 + AssertEqual(t, "high", task.Priority, "Task should be high priority") 73 + AssertEqual(t, "test-project", task.Project, "Task should be in test-project") 74 + } 75 + }) 76 + 77 + t.Run("returns empty for non-matching criteria", func(t *testing.T) { 78 + options := TaskListOptions{ 79 + Status: "non-existent-status", 80 + } 81 + tasks, err := repos.Tasks.Find(ctx, options) 82 + AssertNoError(t, err, "Find should succeed even with no results") 83 + AssertEqual(t, 0, len(tasks), "Should find no tasks") 84 + }) 85 + 86 + t.Run("returns all tasks with empty options", func(t *testing.T) { 87 + options := TaskListOptions{} 88 + tasks, err := repos.Tasks.Find(ctx, options) 89 + AssertNoError(t, err, "Find should succeed with empty options") 90 + AssertTrue(t, len(tasks) >= 2, "Should return all tasks for empty options") 91 + }) 92 + }) 93 + 94 + t.Run("BookRepository Find", func(t *testing.T) { 95 + t.Run("finds books by status", func(t *testing.T) { 96 + options := BookListOptions{ 97 + Status: "reading", 98 + } 99 + books, err := repos.Books.Find(ctx, options) 100 + AssertNoError(t, err, "Find should succeed") 101 + AssertTrue(t, len(books) >= 1, "Should find at least one book being read") 102 + for _, book := range books { 103 + AssertEqual(t, "reading", book.Status, "All returned books should be reading") 104 + } 105 + }) 106 + 107 + t.Run("finds books by author", func(t *testing.T) { 108 + options := BookListOptions{ 109 + Author: "Test Author", 110 + } 111 + books, err := repos.Books.Find(ctx, options) 112 + AssertNoError(t, err, "Find should succeed") 113 + AssertTrue(t, len(books) >= 1, "Should find at least one book by Test Author") 114 + for _, book := range books { 115 + AssertEqual(t, "Test Author", book.Author, "All returned books should be by Test Author") 116 + } 117 + }) 118 + 119 + t.Run("finds books by minimum progress", func(t *testing.T) { 120 + options := BookListOptions{ 121 + MinProgress: 0, 122 + } 123 + books, err := repos.Books.Find(ctx, options) 124 + AssertNoError(t, err, "Find should succeed") 125 + AssertTrue(t, len(books) >= 1, "Should find books with progress >= 0") 126 + for _, book := range books { 127 + AssertTrue(t, book.Progress >= 0, "All returned books should have progress >= 0") 128 + } 129 + }) 130 + 131 + t.Run("finds books by multiple criteria", func(t *testing.T) { 132 + options := BookListOptions{ 133 + Status: "reading", 134 + Author: "Test Author", 135 + MinProgress: 0, 136 + } 137 + books, err := repos.Books.Find(ctx, options) 138 + AssertNoError(t, err, "Find should succeed") 139 + for _, book := range books { 140 + AssertEqual(t, "reading", book.Status, "Book should be reading") 141 + AssertEqual(t, "Test Author", book.Author, "Book should be by Test Author") 142 + AssertTrue(t, book.Progress >= 0, "Book should have progress >= 0") 143 + } 144 + }) 145 + 146 + t.Run("returns empty for non-matching criteria", func(t *testing.T) { 147 + options := BookListOptions{ 148 + Status: "non-existent-status", 149 + } 150 + books, err := repos.Books.Find(ctx, options) 151 + AssertNoError(t, err, "Find should succeed even with no results") 152 + AssertEqual(t, 0, len(books), "Should find no books") 153 + }) 154 + 155 + t.Run("returns all books with empty options", func(t *testing.T) { 156 + options := BookListOptions{} 157 + books, err := repos.Books.Find(ctx, options) 158 + AssertNoError(t, err, "Find should succeed with empty options") 159 + AssertTrue(t, len(books) >= 2, "Should return all books for empty options") 160 + }) 161 + }) 162 + 163 + t.Run("MovieRepository Find", func(t *testing.T) { 164 + t.Run("finds movies by status", func(t *testing.T) { 165 + options := MovieListOptions{ 166 + Status: "watched", 167 + } 168 + movies, err := repos.Movies.Find(ctx, options) 169 + AssertNoError(t, err, "Find should succeed") 170 + AssertTrue(t, len(movies) >= 1, "Should find at least one watched movie") 171 + for _, movie := range movies { 172 + AssertEqual(t, "watched", movie.Status, "All returned movies should be watched") 173 + } 174 + }) 175 + 176 + t.Run("finds movies by year", func(t *testing.T) { 177 + options := MovieListOptions{ 178 + Year: 2023, 179 + } 180 + movies, err := repos.Movies.Find(ctx, options) 181 + AssertNoError(t, err, "Find should succeed") 182 + AssertTrue(t, len(movies) >= 1, "Should find movies from 2023") 183 + for _, movie := range movies { 184 + AssertEqual(t, 2023, movie.Year, "Movie should be from 2023") 185 + } 186 + }) 187 + 188 + t.Run("finds movies by minimum rating", func(t *testing.T) { 189 + options := MovieListOptions{ 190 + MinRating: 0.0, 191 + } 192 + movies, err := repos.Movies.Find(ctx, options) 193 + AssertNoError(t, err, "Find should succeed") 194 + AssertTrue(t, len(movies) >= 1, "Should find movies with rating >= 0") 195 + for _, movie := range movies { 196 + AssertTrue(t, movie.Rating >= 0.0, "Movie rating should be >= 0") 197 + } 198 + }) 199 + 200 + t.Run("finds movies by multiple criteria", func(t *testing.T) { 201 + options := MovieListOptions{ 202 + Status: "watched", 203 + Year: 2023, 204 + MinRating: 0.0, 205 + } 206 + movies, err := repos.Movies.Find(ctx, options) 207 + AssertNoError(t, err, "Find should succeed") 208 + for _, movie := range movies { 209 + AssertEqual(t, "watched", movie.Status, "Movie should be watched") 210 + AssertEqual(t, 2023, movie.Year, "Movie should be from 2023") 211 + AssertTrue(t, movie.Rating >= 0.0, "Movie rating should be >= 0") 212 + } 213 + }) 214 + 215 + t.Run("returns empty for non-matching criteria", func(t *testing.T) { 216 + options := MovieListOptions{ 217 + Status: "non-existent-status", 218 + } 219 + movies, err := repos.Movies.Find(ctx, options) 220 + AssertNoError(t, err, "Find should succeed even with no results") 221 + AssertEqual(t, 0, len(movies), "Should find no movies") 222 + }) 223 + 224 + t.Run("returns all movies with empty options", func(t *testing.T) { 225 + options := MovieListOptions{} 226 + movies, err := repos.Movies.Find(ctx, options) 227 + AssertNoError(t, err, "Find should succeed with empty options") 228 + AssertTrue(t, len(movies) >= 2, "Should return all movies for empty options") 229 + }) 230 + }) 231 + 232 + t.Run("TVRepository Find", func(t *testing.T) { 233 + t.Run("finds TV shows by status", func(t *testing.T) { 234 + options := TVListOptions{ 235 + Status: "watching", 236 + } 237 + shows, err := repos.TV.Find(ctx, options) 238 + AssertNoError(t, err, "Find should succeed") 239 + AssertTrue(t, len(shows) >= 1, "Should find at least one TV show being watched") 240 + for _, show := range shows { 241 + AssertEqual(t, "watching", show.Status, "All returned shows should be watching") 242 + } 243 + }) 244 + 245 + t.Run("finds TV shows by season", func(t *testing.T) { 246 + options := TVListOptions{ 247 + Season: 1, 248 + } 249 + shows, err := repos.TV.Find(ctx, options) 250 + AssertNoError(t, err, "Find should succeed") 251 + AssertTrue(t, len(shows) >= 1, "Should find TV shows with season 1") 252 + for _, show := range shows { 253 + AssertEqual(t, 1, show.Season, "All returned shows should be season 1") 254 + } 255 + }) 256 + 257 + t.Run("finds TV shows by minimum rating", func(t *testing.T) { 258 + options := TVListOptions{ 259 + MinRating: 0.0, 260 + } 261 + shows, err := repos.TV.Find(ctx, options) 262 + AssertNoError(t, err, "Find should succeed") 263 + AssertTrue(t, len(shows) >= 1, "Should find TV shows with rating >= 0") 264 + for _, show := range shows { 265 + AssertTrue(t, show.Rating >= 0.0, "Show rating should be >= 0") 266 + } 267 + }) 268 + 269 + t.Run("finds TV shows by multiple criteria", func(t *testing.T) { 270 + options := TVListOptions{ 271 + Status: "watching", 272 + Season: 1, 273 + MinRating: 0.0, 274 + } 275 + shows, err := repos.TV.Find(ctx, options) 276 + AssertNoError(t, err, "Find should succeed") 277 + for _, show := range shows { 278 + AssertEqual(t, "watching", show.Status, "Show should be watching") 279 + AssertEqual(t, 1, show.Season, "Show should be season 1") 280 + AssertTrue(t, show.Rating >= 0.0, "Show rating should be >= 0") 281 + } 282 + }) 283 + 284 + t.Run("returns empty for non-matching criteria", func(t *testing.T) { 285 + options := TVListOptions{ 286 + Status: "non-existent-status", 287 + } 288 + shows, err := repos.TV.Find(ctx, options) 289 + AssertNoError(t, err, "Find should succeed even with no results") 290 + AssertEqual(t, 0, len(shows), "Should find no TV shows") 291 + }) 292 + 293 + t.Run("returns all TV shows with empty options", func(t *testing.T) { 294 + options := TVListOptions{} 295 + shows, err := repos.TV.Find(ctx, options) 296 + AssertNoError(t, err, "Find should succeed with empty options") 297 + AssertTrue(t, len(shows) >= 2, "Should return all TV shows for empty options") 298 + }) 299 + }) 300 + }
+67 -233
internal/repo/movie_repository_test.go
··· 2 3 import ( 4 "context" 5 - "database/sql" 6 "testing" 7 "time" 8 ··· 10 "github.com/stormlightlabs/noteleaf/internal/models" 11 ) 12 13 - func createMovieTestDB(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 movies ( 25 - id INTEGER PRIMARY KEY AUTOINCREMENT, 26 - title TEXT NOT NULL, 27 - year INTEGER, 28 - status TEXT DEFAULT 'queued', 29 - rating REAL, 30 - notes TEXT, 31 - added DATETIME DEFAULT CURRENT_TIMESTAMP, 32 - watched DATETIME 33 - ); 34 - ` 35 - 36 - if _, err := db.Exec(schema); err != nil { 37 - t.Fatalf("Failed to create schema: %v", err) 38 - } 39 - 40 - t.Cleanup(func() { 41 - db.Close() 42 - }) 43 - 44 - return db 45 - } 46 - 47 - func createSampleMovie() *models.Movie { 48 - return &models.Movie{ 49 - Title: "Test Movie", 50 - Year: 2023, 51 - Status: "queued", 52 - Rating: 8.5, 53 - Notes: "Great movie to watch", 54 - } 55 - } 56 - 57 func TestMovieRepository(t *testing.T) { 58 t.Run("CRUD Operations", func(t *testing.T) { 59 - db := createMovieTestDB(t) 60 repo := NewMovieRepository(db) 61 ctx := context.Background() 62 63 t.Run("Create Movie", func(t *testing.T) { 64 - movie := createSampleMovie() 65 66 id, err := repo.Create(ctx, movie) 67 - if err != nil { 68 - t.Errorf("Failed to create movie: %v", err) 69 - } 70 - 71 - if id == 0 { 72 - t.Error("Expected non-zero ID") 73 - } 74 - 75 - if movie.ID != id { 76 - t.Errorf("Expected movie ID to be set to %d, got %d", id, movie.ID) 77 - } 78 - 79 - if movie.Added.IsZero() { 80 - t.Error("Expected Added timestamp to be set") 81 - } 82 }) 83 84 t.Run("Get Movie", func(t *testing.T) { 85 - original := createSampleMovie() 86 id, err := repo.Create(ctx, original) 87 - if err != nil { 88 - t.Fatalf("Failed to create movie: %v", err) 89 - } 90 91 retrieved, err := repo.Get(ctx, id) 92 - if err != nil { 93 - t.Errorf("Failed to get movie: %v", err) 94 - } 95 96 - if retrieved.Title != original.Title { 97 - t.Errorf("Expected title %s, got %s", original.Title, retrieved.Title) 98 - } 99 - if retrieved.Year != original.Year { 100 - t.Errorf("Expected year %d, got %d", original.Year, retrieved.Year) 101 - } 102 - if retrieved.Status != original.Status { 103 - t.Errorf("Expected status %s, got %s", original.Status, retrieved.Status) 104 - } 105 - if retrieved.Rating != original.Rating { 106 - t.Errorf("Expected rating %f, got %f", original.Rating, retrieved.Rating) 107 - } 108 - if retrieved.Notes != original.Notes { 109 - t.Errorf("Expected notes %s, got %s", original.Notes, retrieved.Notes) 110 - } 111 }) 112 113 t.Run("Update Movie", func(t *testing.T) { 114 - movie := createSampleMovie() 115 id, err := repo.Create(ctx, movie) 116 - if err != nil { 117 - t.Fatalf("Failed to create movie: %v", err) 118 - } 119 120 movie.Title = "Updated Movie" 121 movie.Status = "watched" ··· 124 movie.Watched = &now 125 126 err = repo.Update(ctx, movie) 127 - if err != nil { 128 - t.Errorf("Failed to update movie: %v", err) 129 - } 130 131 updated, err := repo.Get(ctx, id) 132 - if err != nil { 133 - t.Fatalf("Failed to get updated movie: %v", err) 134 - } 135 136 - if updated.Title != "Updated Movie" { 137 - t.Errorf("Expected updated title, got %s", updated.Title) 138 - } 139 - if updated.Status != "watched" { 140 - t.Errorf("Expected status watched, got %s", updated.Status) 141 - } 142 - if updated.Rating != 9.0 { 143 - t.Errorf("Expected rating 9.0, got %f", updated.Rating) 144 - } 145 - if updated.Watched == nil { 146 - t.Error("Expected watched time to be set") 147 - } 148 }) 149 150 t.Run("Delete Movie", func(t *testing.T) { 151 - movie := createSampleMovie() 152 id, err := repo.Create(ctx, movie) 153 - if err != nil { 154 - t.Fatalf("Failed to create movie: %v", err) 155 - } 156 157 err = repo.Delete(ctx, id) 158 - if err != nil { 159 - t.Errorf("Failed to delete movie: %v", err) 160 - } 161 162 _, err = repo.Get(ctx, id) 163 - if err == nil { 164 - t.Error("Expected error when getting deleted movie") 165 - } 166 }) 167 }) 168 169 t.Run("List", func(t *testing.T) { 170 - db := createMovieTestDB(t) 171 repo := NewMovieRepository(db) 172 ctx := context.Background() 173 ··· 179 180 for _, movie := range movies { 181 _, err := repo.Create(ctx, movie) 182 - if err != nil { 183 - t.Fatalf("Failed to create movie: %v", err) 184 - } 185 } 186 187 t.Run("List All Movies", func(t *testing.T) { 188 results, err := repo.List(ctx, MovieListOptions{}) 189 - if err != nil { 190 - t.Errorf("Failed to list movies: %v", err) 191 - } 192 - 193 - if len(results) != 3 { 194 - t.Errorf("Expected 3 movies, got %d", len(results)) 195 - } 196 }) 197 198 t.Run("List Movies with Status Filter", func(t *testing.T) { 199 results, err := repo.List(ctx, MovieListOptions{Status: "queued"}) 200 - if err != nil { 201 - t.Errorf("Failed to list movies: %v", err) 202 - } 203 - 204 - if len(results) != 2 { 205 - t.Errorf("Expected 2 queued movies, got %d", len(results)) 206 - } 207 208 for _, movie := range results { 209 - if movie.Status != "queued" { 210 - t.Errorf("Expected queued status, got %s", movie.Status) 211 - } 212 } 213 }) 214 215 t.Run("List Movies with Year Filter", func(t *testing.T) { 216 results, err := repo.List(ctx, MovieListOptions{Year: 2021}) 217 - if err != nil { 218 - t.Errorf("Failed to list movies: %v", err) 219 - } 220 221 - if len(results) != 1 { 222 - t.Errorf("Expected 1 movie from 2021, got %d", len(results)) 223 - } 224 - 225 - if len(results) > 0 && results[0].Year != 2021 { 226 - t.Errorf("Expected year 2021, got %d", results[0].Year) 227 } 228 }) 229 230 t.Run("List Movies with Rating Filter", func(t *testing.T) { 231 results, err := repo.List(ctx, MovieListOptions{MinRating: 8.0}) 232 - if err != nil { 233 - t.Errorf("Failed to list movies: %v", err) 234 - } 235 - 236 - if len(results) != 2 { 237 - t.Errorf("Expected 2 movies with rating >= 8.0, got %d", len(results)) 238 - } 239 240 for _, movie := range results { 241 - if movie.Rating < 8.0 { 242 - t.Errorf("Expected rating >= 8.0, got %f", movie.Rating) 243 - } 244 } 245 }) 246 247 t.Run("List Movies with Search", func(t *testing.T) { 248 results, err := repo.List(ctx, MovieListOptions{Search: "Movie 1"}) 249 - if err != nil { 250 - t.Errorf("Failed to list movies: %v", err) 251 - } 252 253 - if len(results) != 1 { 254 - t.Errorf("Expected 1 movie matching search, got %d", len(results)) 255 - } 256 - 257 - if len(results) > 0 && results[0].Title != "Movie 1" { 258 - t.Errorf("Expected 'Movie 1', got %s", results[0].Title) 259 } 260 }) 261 262 t.Run("List Movies with Limit", func(t *testing.T) { 263 results, err := repo.List(ctx, MovieListOptions{Limit: 2}) 264 - if err != nil { 265 - t.Errorf("Failed to list movies: %v", err) 266 - } 267 - 268 - if len(results) != 2 { 269 - t.Errorf("Expected 2 movies due to limit, got %d", len(results)) 270 - } 271 }) 272 }) 273 274 t.Run("Special Methods", func(t *testing.T) { 275 - db := createMovieTestDB(t) 276 repo := NewMovieRepository(db) 277 ctx := context.Background() 278 ··· 283 var movie1ID int64 284 for _, movie := range []*models.Movie{movie1, movie2, movie3} { 285 id, err := repo.Create(ctx, movie) 286 - if err != nil { 287 - t.Fatalf("Failed to create movie: %v", err) 288 - } 289 if movie == movie1 { 290 movie1ID = id 291 } ··· 293 294 t.Run("GetQueued", func(t *testing.T) { 295 results, err := repo.GetQueued(ctx) 296 - if err != nil { 297 - t.Errorf("Failed to get queued movies: %v", err) 298 - } 299 - 300 - if len(results) != 2 { 301 - t.Errorf("Expected 2 queued movies, got %d", len(results)) 302 - } 303 304 for _, movie := range results { 305 - if movie.Status != "queued" { 306 - t.Errorf("Expected queued status, got %s", movie.Status) 307 - } 308 } 309 }) 310 311 t.Run("GetWatched", func(t *testing.T) { 312 results, err := repo.GetWatched(ctx) 313 - if err != nil { 314 - t.Errorf("Failed to get watched movies: %v", err) 315 - } 316 - 317 - if len(results) != 1 { 318 - t.Errorf("Expected 1 watched movie, got %d", len(results)) 319 - } 320 321 - if len(results) > 0 && results[0].Status != "watched" { 322 - t.Errorf("Expected watched status, got %s", results[0].Status) 323 } 324 }) 325 326 t.Run("MarkWatched", func(t *testing.T) { 327 err := repo.MarkWatched(ctx, movie1ID) 328 - if err != nil { 329 - t.Errorf("Failed to mark movie as watched: %v", err) 330 - } 331 332 updated, err := repo.Get(ctx, movie1ID) 333 - if err != nil { 334 - t.Fatalf("Failed to get updated movie: %v", err) 335 - } 336 337 - if updated.Status != "watched" { 338 - t.Errorf("Expected status to be watched, got %s", updated.Status) 339 - } 340 - 341 - if updated.Watched == nil { 342 - t.Error("Expected watched timestamp to be set") 343 - } 344 }) 345 }) 346 347 t.Run("Count", func(t *testing.T) { 348 - db := createMovieTestDB(t) 349 repo := NewMovieRepository(db) 350 ctx := context.Background() 351 ··· 357 358 for _, movie := range movies { 359 _, err := repo.Create(ctx, movie) 360 - if err != nil { 361 - t.Fatalf("Failed to create movie: %v", err) 362 - } 363 } 364 365 t.Run("Count all movies", func(t *testing.T) { 366 count, err := repo.Count(ctx, MovieListOptions{}) 367 - if err != nil { 368 - t.Errorf("Failed to count movies: %v", err) 369 - } 370 - 371 - if count != 3 { 372 - t.Errorf("Expected 3 movies, got %d", count) 373 - } 374 }) 375 376 t.Run("Count queued movies", func(t *testing.T) { 377 count, err := repo.Count(ctx, MovieListOptions{Status: "queued"}) 378 - if err != nil { 379 - t.Errorf("Failed to count queued movies: %v", err) 380 - } 381 - 382 - if count != 2 { 383 - t.Errorf("Expected 2 queued movies, got %d", count) 384 - } 385 }) 386 387 t.Run("Count movies by rating", func(t *testing.T) { 388 count, err := repo.Count(ctx, MovieListOptions{MinRating: 8.0}) 389 - if err != nil { 390 - t.Errorf("Failed to count high-rated movies: %v", err) 391 - } 392 - 393 - if count != 2 { 394 - t.Errorf("Expected 2 movies with rating >= 8.0, got %d", count) 395 - } 396 }) 397 }) 398 }
··· 2 3 import ( 4 "context" 5 "testing" 6 "time" 7 ··· 9 "github.com/stormlightlabs/noteleaf/internal/models" 10 ) 11 12 func TestMovieRepository(t *testing.T) { 13 t.Run("CRUD Operations", func(t *testing.T) { 14 + db := CreateTestDB(t) 15 repo := NewMovieRepository(db) 16 ctx := context.Background() 17 18 t.Run("Create Movie", func(t *testing.T) { 19 + movie := CreateSampleMovie() 20 21 id, err := repo.Create(ctx, movie) 22 + AssertNoError(t, err, "Failed to create movie") 23 + AssertNotEqual(t, int64(0), id, "Expected non-zero ID") 24 + AssertEqual(t, id, movie.ID, "Expected movie ID to be set correctly") 25 + AssertFalse(t, movie.Added.IsZero(), "Expected Added timestamp to be set") 26 }) 27 28 t.Run("Get Movie", func(t *testing.T) { 29 + original := CreateSampleMovie() 30 id, err := repo.Create(ctx, original) 31 + AssertNoError(t, err, "Failed to create movie") 32 33 retrieved, err := repo.Get(ctx, id) 34 + AssertNoError(t, err, "Failed to get movie") 35 36 + AssertEqual(t, original.Title, retrieved.Title, "Title mismatch") 37 + AssertEqual(t, original.Year, retrieved.Year, "Year mismatch") 38 + AssertEqual(t, original.Status, retrieved.Status, "Status mismatch") 39 + AssertEqual(t, original.Rating, retrieved.Rating, "Rating mismatch") 40 + AssertEqual(t, original.Notes, retrieved.Notes, "Notes mismatch") 41 }) 42 43 t.Run("Update Movie", func(t *testing.T) { 44 + movie := CreateSampleMovie() 45 id, err := repo.Create(ctx, movie) 46 + AssertNoError(t, err, "Failed to create movie") 47 48 movie.Title = "Updated Movie" 49 movie.Status = "watched" ··· 52 movie.Watched = &now 53 54 err = repo.Update(ctx, movie) 55 + AssertNoError(t, err, "Failed to update movie") 56 57 updated, err := repo.Get(ctx, id) 58 + AssertNoError(t, err, "Failed to get updated movie") 59 60 + AssertEqual(t, "Updated Movie", updated.Title, "Expected updated title") 61 + AssertEqual(t, "watched", updated.Status, "Expected watched status") 62 + AssertEqual(t, 9.0, updated.Rating, "Expected rating 9.0") 63 + AssertTrue(t, updated.Watched != nil, "Expected watched time to be set") 64 }) 65 66 t.Run("Delete Movie", func(t *testing.T) { 67 + movie := CreateSampleMovie() 68 id, err := repo.Create(ctx, movie) 69 + AssertNoError(t, err, "Failed to create movie") 70 71 err = repo.Delete(ctx, id) 72 + AssertNoError(t, err, "Failed to delete movie") 73 74 _, err = repo.Get(ctx, id) 75 + AssertError(t, err, "Expected error when getting deleted movie") 76 }) 77 }) 78 79 t.Run("List", func(t *testing.T) { 80 + db := CreateTestDB(t) 81 repo := NewMovieRepository(db) 82 ctx := context.Background() 83 ··· 89 90 for _, movie := range movies { 91 _, err := repo.Create(ctx, movie) 92 + AssertNoError(t, err, "Failed to create movie") 93 } 94 95 t.Run("List All Movies", func(t *testing.T) { 96 results, err := repo.List(ctx, MovieListOptions{}) 97 + AssertNoError(t, err, "Failed to list movies") 98 + AssertEqual(t, 3, len(results), "Expected 3 movies") 99 }) 100 101 t.Run("List Movies with Status Filter", func(t *testing.T) { 102 results, err := repo.List(ctx, MovieListOptions{Status: "queued"}) 103 + AssertNoError(t, err, "Failed to list movies") 104 + AssertEqual(t, 2, len(results), "Expected 2 queued movies") 105 106 for _, movie := range results { 107 + AssertEqual(t, "queued", movie.Status, "Expected queued status") 108 } 109 }) 110 111 t.Run("List Movies with Year Filter", func(t *testing.T) { 112 results, err := repo.List(ctx, MovieListOptions{Year: 2021}) 113 + AssertNoError(t, err, "Failed to list movies") 114 + AssertEqual(t, 1, len(results), "Expected 1 movie from 2021") 115 116 + if len(results) > 0 { 117 + AssertEqual(t, 2021, results[0].Year, "Expected year 2021") 118 } 119 }) 120 121 t.Run("List Movies with Rating Filter", func(t *testing.T) { 122 results, err := repo.List(ctx, MovieListOptions{MinRating: 8.0}) 123 + AssertNoError(t, err, "Failed to list movies") 124 + AssertEqual(t, 2, len(results), "Expected 2 movies with rating >= 8.0") 125 126 for _, movie := range results { 127 + AssertTrue(t, movie.Rating >= 8.0, "Expected rating >= 8.0") 128 } 129 }) 130 131 t.Run("List Movies with Search", func(t *testing.T) { 132 results, err := repo.List(ctx, MovieListOptions{Search: "Movie 1"}) 133 + AssertNoError(t, err, "Failed to list movies") 134 + AssertEqual(t, 1, len(results), "Expected 1 movie matching search") 135 136 + if len(results) > 0 { 137 + AssertEqual(t, "Movie 1", results[0].Title, "Expected 'Movie 1'") 138 } 139 }) 140 141 t.Run("List Movies with Limit", func(t *testing.T) { 142 results, err := repo.List(ctx, MovieListOptions{Limit: 2}) 143 + AssertNoError(t, err, "Failed to list movies") 144 + AssertEqual(t, 2, len(results), "Expected 2 movies due to limit") 145 }) 146 }) 147 148 t.Run("Special Methods", func(t *testing.T) { 149 + db := CreateTestDB(t) 150 repo := NewMovieRepository(db) 151 ctx := context.Background() 152 ··· 157 var movie1ID int64 158 for _, movie := range []*models.Movie{movie1, movie2, movie3} { 159 id, err := repo.Create(ctx, movie) 160 + AssertNoError(t, err, "Failed to create movie") 161 if movie == movie1 { 162 movie1ID = id 163 } ··· 165 166 t.Run("GetQueued", func(t *testing.T) { 167 results, err := repo.GetQueued(ctx) 168 + AssertNoError(t, err, "Failed to get queued movies") 169 + AssertEqual(t, 2, len(results), "Expected 2 queued movies") 170 171 for _, movie := range results { 172 + AssertEqual(t, "queued", movie.Status, "Expected queued status") 173 } 174 }) 175 176 t.Run("GetWatched", func(t *testing.T) { 177 results, err := repo.GetWatched(ctx) 178 + AssertNoError(t, err, "Failed to get watched movies") 179 + AssertEqual(t, 1, len(results), "Expected 1 watched movie") 180 181 + if len(results) > 0 { 182 + AssertEqual(t, "watched", results[0].Status, "Expected watched status") 183 } 184 }) 185 186 t.Run("MarkWatched", func(t *testing.T) { 187 err := repo.MarkWatched(ctx, movie1ID) 188 + AssertNoError(t, err, "Failed to mark movie as watched") 189 190 updated, err := repo.Get(ctx, movie1ID) 191 + AssertNoError(t, err, "Failed to get updated movie") 192 193 + AssertEqual(t, "watched", updated.Status, "Expected status to be watched") 194 + AssertTrue(t, updated.Watched != nil, "Expected watched timestamp to be set") 195 }) 196 }) 197 198 t.Run("Count", func(t *testing.T) { 199 + db := CreateTestDB(t) 200 repo := NewMovieRepository(db) 201 ctx := context.Background() 202 ··· 208 209 for _, movie := range movies { 210 _, err := repo.Create(ctx, movie) 211 + AssertNoError(t, err, "Failed to create movie") 212 } 213 214 t.Run("Count all movies", func(t *testing.T) { 215 count, err := repo.Count(ctx, MovieListOptions{}) 216 + AssertNoError(t, err, "Failed to count movies") 217 + AssertEqual(t, int64(3), count, "Expected 3 movies") 218 }) 219 220 t.Run("Count queued movies", func(t *testing.T) { 221 count, err := repo.Count(ctx, MovieListOptions{Status: "queued"}) 222 + AssertNoError(t, err, "Failed to count queued movies") 223 + AssertEqual(t, int64(2), count, "Expected 2 queued movies") 224 }) 225 226 t.Run("Count movies by rating", func(t *testing.T) { 227 count, err := repo.Count(ctx, MovieListOptions{MinRating: 8.0}) 228 + AssertNoError(t, err, "Failed to count high-rated movies") 229 + AssertEqual(t, int64(2), count, "Expected 2 movies with rating >= 8.0") 230 }) 231 }) 232 }
+381 -569
internal/repo/note_repository_test.go
··· 2 3 import ( 4 "context" 5 - "database/sql" 6 "testing" 7 8 _ "github.com/mattn/go-sqlite3" 9 "github.com/stormlightlabs/noteleaf/internal/models" 10 ) 11 12 - func createNoteTestDB(t *testing.T) *sql.DB { 13 - db, err := sql.Open("sqlite3", ":memory:") 14 - if err != nil { 15 - t.Fatalf("Failed to create in-memory database: %v", err) 16 - } 17 18 - if _, err := db.Exec("PRAGMA foreign_keys = ON"); err != nil { 19 - t.Fatalf("Failed to enable foreign keys: %v", err) 20 - } 21 22 - schema := ` 23 - CREATE TABLE IF NOT EXISTS notes ( 24 - id INTEGER PRIMARY KEY AUTOINCREMENT, 25 - title TEXT NOT NULL, 26 - content TEXT NOT NULL, 27 - tags TEXT, 28 - archived BOOLEAN DEFAULT FALSE, 29 - created DATETIME DEFAULT CURRENT_TIMESTAMP, 30 - modified DATETIME DEFAULT CURRENT_TIMESTAMP, 31 - file_path TEXT 32 - ); 33 - ` 34 35 - if _, err := db.Exec(schema); err != nil { 36 - t.Fatalf("Failed to create schema: %v", err) 37 - } 38 39 - t.Cleanup(func() { 40 - db.Close() 41 - }) 42 43 - return db 44 - } 45 46 - func createSampleNote() *models.Note { 47 - return &models.Note{ 48 - Title: "Test Note", 49 - Content: "This is test content with **markdown**", 50 - Tags: []string{"personal", "work"}, 51 - Archived: false, 52 - FilePath: "/path/to/note.md", 53 - } 54 - } 55 56 - func TestNoteRepository_CRUD(t *testing.T) { 57 - db := createNoteTestDB(t) 58 - repo := NewNoteRepository(db) 59 - ctx := context.Background() 60 61 - t.Run("Create Note", func(t *testing.T) { 62 - note := createSampleNote() 63 64 - id, err := repo.Create(ctx, note) 65 - if err != nil { 66 - t.Errorf("Failed to create note: %v", err) 67 - } 68 69 - if id == 0 { 70 - t.Error("Expected non-zero ID") 71 - } 72 73 - if note.ID != id { 74 - t.Errorf("Expected note ID to be set to %d, got %d", id, note.ID) 75 - } 76 77 - if note.Created.IsZero() { 78 - t.Error("Expected Created timestamp to be set") 79 - } 80 - if note.Modified.IsZero() { 81 - t.Error("Expected Modified timestamp to be set") 82 - } 83 - }) 84 - 85 - t.Run("Get Note", func(t *testing.T) { 86 - original := createSampleNote() 87 - id, err := repo.Create(ctx, original) 88 - if err != nil { 89 - t.Fatalf("Failed to create note: %v", err) 90 - } 91 92 - retrieved, err := repo.Get(ctx, id) 93 - if err != nil { 94 - t.Fatalf("Failed to get note: %v", err) 95 - } 96 97 - if retrieved.ID != original.ID { 98 - t.Errorf("Expected ID %d, got %d", original.ID, retrieved.ID) 99 - } 100 - if retrieved.Title != original.Title { 101 - t.Errorf("Expected title %s, got %s", original.Title, retrieved.Title) 102 - } 103 - if retrieved.Content != original.Content { 104 - t.Errorf("Expected content %s, got %s", original.Content, retrieved.Content) 105 - } 106 - if len(retrieved.Tags) != len(original.Tags) { 107 - t.Errorf("Expected %d tags, got %d", len(original.Tags), len(retrieved.Tags)) 108 - } 109 - if retrieved.Archived != original.Archived { 110 - t.Errorf("Expected archived %v, got %v", original.Archived, retrieved.Archived) 111 - } 112 - if retrieved.FilePath != original.FilePath { 113 - t.Errorf("Expected file path %s, got %s", original.FilePath, retrieved.FilePath) 114 - } 115 }) 116 117 - t.Run("Update Note", func(t *testing.T) { 118 - note := createSampleNote() 119 - id, err := repo.Create(ctx, note) 120 - if err != nil { 121 - t.Fatalf("Failed to create note: %v", err) 122 - } 123 - 124 - originalModified := note.Modified 125 - 126 - note.Title = "Updated Title" 127 - note.Content = "Updated content" 128 - note.Tags = []string{"updated", "test"} 129 - note.Archived = true 130 - note.FilePath = "/new/path/note.md" 131 132 - err = repo.Update(ctx, note) 133 - if err != nil { 134 - t.Errorf("Failed to update note: %v", err) 135 } 136 137 - retrieved, err := repo.Get(ctx, id) 138 - if err != nil { 139 - t.Fatalf("Failed to get updated note: %v", err) 140 } 141 142 - if retrieved.Title != "Updated Title" { 143 - t.Errorf("Expected updated title, got %s", retrieved.Title) 144 - } 145 - if retrieved.Content != "Updated content" { 146 - t.Errorf("Expected updated content, got %s", retrieved.Content) 147 - } 148 - if len(retrieved.Tags) != 2 || retrieved.Tags[0] != "updated" || retrieved.Tags[1] != "test" { 149 - t.Errorf("Expected updated tags, got %v", retrieved.Tags) 150 - } 151 - if !retrieved.Archived { 152 - t.Error("Expected note to be archived") 153 - } 154 - if retrieved.FilePath != "/new/path/note.md" { 155 - t.Errorf("Expected updated file path, got %s", retrieved.FilePath) 156 - } 157 - if !retrieved.Modified.After(originalModified) { 158 - t.Error("Expected Modified timestamp to be updated") 159 - } 160 - }) 161 162 - t.Run("Delete Note", func(t *testing.T) { 163 - note := createSampleNote() 164 - id, err := repo.Create(ctx, note) 165 - if err != nil { 166 - t.Fatalf("Failed to create note: %v", err) 167 - } 168 169 - err = repo.Delete(ctx, id) 170 - if err != nil { 171 - t.Errorf("Failed to delete note: %v", err) 172 - } 173 174 - _, err = repo.Get(ctx, id) 175 - if err == nil { 176 - t.Error("Expected error when getting deleted note") 177 - } 178 - }) 179 - } 180 - 181 - func TestNoteRepository_List(t *testing.T) { 182 - db := createNoteTestDB(t) 183 - repo := NewNoteRepository(db) 184 - ctx := context.Background() 185 - 186 - notes := []*models.Note{ 187 - {Title: "First Note", Content: "Content 1", Tags: []string{"work"}, Archived: false}, 188 - {Title: "Second Note", Content: "Content 2", Tags: []string{"personal"}, Archived: true}, 189 - {Title: "Third Note", Content: "Important content", Tags: []string{"work", "important"}, Archived: false}, 190 - } 191 192 - for _, note := range notes { 193 - _, err := repo.Create(ctx, note) 194 - if err != nil { 195 - t.Fatalf("Failed to create test note: %v", err) 196 - } 197 - } 198 199 - t.Run("List All Notes", func(t *testing.T) { 200 - results, err := repo.List(ctx, NoteListOptions{}) 201 - if err != nil { 202 - t.Fatalf("Failed to list notes: %v", err) 203 - } 204 205 - if len(results) != 3 { 206 - t.Errorf("Expected 3 notes, got %d", len(results)) 207 - } 208 }) 209 210 - t.Run("List Archived Notes Only", func(t *testing.T) { 211 - archived := true 212 - results, err := repo.List(ctx, NoteListOptions{Archived: &archived}) 213 - if err != nil { 214 - t.Fatalf("Failed to list archived notes: %v", err) 215 - } 216 217 - if len(results) != 1 { 218 - t.Errorf("Expected 1 archived note, got %d", len(results)) 219 } 220 - if !results[0].Archived { 221 - t.Error("Retrieved note should be archived") 222 - } 223 - }) 224 225 - t.Run("List Active Notes Only", func(t *testing.T) { 226 - archived := false 227 - results, err := repo.List(ctx, NoteListOptions{Archived: &archived}) 228 - if err != nil { 229 - t.Fatalf("Failed to list active notes: %v", err) 230 } 231 232 - if len(results) != 2 { 233 - t.Errorf("Expected 2 active notes, got %d", len(results)) 234 - } 235 - for _, note := range results { 236 - if note.Archived { 237 - t.Error("Retrieved note should not be archived") 238 } 239 - } 240 - }) 241 242 - t.Run("Search by Title", func(t *testing.T) { 243 - results, err := repo.List(ctx, NoteListOptions{Title: "First"}) 244 - if err != nil { 245 - t.Fatalf("Failed to search by title: %v", err) 246 - } 247 248 - if len(results) != 1 { 249 - t.Errorf("Expected 1 note, got %d", len(results)) 250 - } 251 - if results[0].Title != "First Note" { 252 - t.Errorf("Expected 'First Note', got %s", results[0].Title) 253 - } 254 - }) 255 256 - t.Run("Search by Content", func(t *testing.T) { 257 - results, err := repo.List(ctx, NoteListOptions{Content: "Important"}) 258 - if err != nil { 259 - t.Fatalf("Failed to search by content: %v", err) 260 - } 261 262 - if len(results) != 1 { 263 - t.Errorf("Expected 1 note, got %d", len(results)) 264 - } 265 - if results[0].Title != "Third Note" { 266 - t.Errorf("Expected 'Third Note', got %s", results[0].Title) 267 - } 268 - }) 269 270 - t.Run("Limit and Offset", func(t *testing.T) { 271 - results, err := repo.List(ctx, NoteListOptions{Limit: 2}) 272 - if err != nil { 273 - t.Fatalf("Failed to list with limit: %v", err) 274 - } 275 - 276 - if len(results) != 2 { 277 - t.Errorf("Expected 2 notes, got %d", len(results)) 278 - } 279 - 280 - results, err = repo.List(ctx, NoteListOptions{Limit: 2, Offset: 1}) 281 - if err != nil { 282 - t.Fatalf("Failed to list with limit and offset: %v", err) 283 - } 284 - 285 - if len(results) != 2 { 286 - t.Errorf("Expected 2 notes with offset, got %d", len(results)) 287 - } 288 - }) 289 - } 290 - 291 - func TestNoteRepository_SpecializedMethods(t *testing.T) { 292 - db := createNoteTestDB(t) 293 - repo := NewNoteRepository(db) 294 - ctx := context.Background() 295 - 296 - notes := []*models.Note{ 297 - {Title: "Work Note", Content: "Work content", Tags: []string{"work"}, Archived: false}, 298 - {Title: "Personal Note", Content: "Personal content", Tags: []string{"personal"}, Archived: true}, 299 - {Title: "Important Note", Content: "Important content", Tags: []string{"work", "important"}, Archived: false}, 300 - } 301 - 302 - for _, note := range notes { 303 - _, err := repo.Create(ctx, note) 304 - if err != nil { 305 - t.Fatalf("Failed to create test note: %v", err) 306 - } 307 - } 308 309 - t.Run("GetByTitle", func(t *testing.T) { 310 - results, err := repo.GetByTitle(ctx, "Work") 311 - if err != nil { 312 - t.Fatalf("Failed to get by title: %v", err) 313 - } 314 315 - if len(results) != 1 { 316 - t.Errorf("Expected 1 note, got %d", len(results)) 317 - } 318 - if results[0].Title != "Work Note" { 319 - t.Errorf("Expected 'Work Note', got %s", results[0].Title) 320 - } 321 - }) 322 323 - t.Run("GetArchived", func(t *testing.T) { 324 - results, err := repo.GetArchived(ctx) 325 - if err != nil { 326 - t.Fatalf("Failed to get archived notes: %v", err) 327 - } 328 329 - if len(results) != 1 { 330 - t.Errorf("Expected 1 archived note, got %d", len(results)) 331 - } 332 - if !results[0].Archived { 333 - t.Error("Retrieved note should be archived") 334 - } 335 }) 336 337 - t.Run("GetActive", func(t *testing.T) { 338 - results, err := repo.GetActive(ctx) 339 - if err != nil { 340 - t.Fatalf("Failed to get active notes: %v", err) 341 - } 342 - 343 - if len(results) != 2 { 344 - t.Errorf("Expected 2 active notes, got %d", len(results)) 345 - } 346 - for _, note := range results { 347 - if note.Archived { 348 - t.Error("Retrieved note should not be archived") 349 - } 350 - } 351 - }) 352 353 - t.Run("Archive and Unarchive", func(t *testing.T) { 354 note := &models.Note{ 355 - Title: "Test Archive", 356 - Content: "Archive test", 357 - Archived: false, 358 } 359 id, err := repo.Create(ctx, note) 360 if err != nil { 361 t.Fatalf("Failed to create note: %v", err) 362 } 363 364 - err = repo.Archive(ctx, id) 365 - if err != nil { 366 - t.Fatalf("Failed to archive note: %v", err) 367 - } 368 369 - retrieved, err := repo.Get(ctx, id) 370 - if err != nil { 371 - t.Fatalf("Failed to get note: %v", err) 372 - } 373 - if !retrieved.Archived { 374 - t.Error("Note should be archived") 375 - } 376 377 - err = repo.Unarchive(ctx, id) 378 - if err != nil { 379 - t.Fatalf("Failed to unarchive note: %v", err) 380 - } 381 382 - retrieved, err = repo.Get(ctx, id) 383 - if err != nil { 384 - t.Fatalf("Failed to get note: %v", err) 385 - } 386 - if retrieved.Archived { 387 - t.Error("Note should not be archived") 388 - } 389 - }) 390 391 - t.Run("SearchContent", func(t *testing.T) { 392 - results, err := repo.SearchContent(ctx, "Important") 393 - if err != nil { 394 - t.Fatalf("Failed to search content: %v", err) 395 - } 396 397 - if len(results) != 1 { 398 - t.Errorf("Expected 1 note, got %d", len(results)) 399 - } 400 - if results[0].Title != "Important Note" { 401 - t.Errorf("Expected 'Important Note', got %s", results[0].Title) 402 - } 403 - }) 404 405 - t.Run("GetRecent", func(t *testing.T) { 406 - results, err := repo.GetRecent(ctx, 2) 407 - if err != nil { 408 - t.Fatalf("Failed to get recent notes: %v", err) 409 - } 410 411 - if len(results) != 2 { 412 - t.Errorf("Expected 2 notes, got %d", len(results)) 413 - } 414 - }) 415 - } 416 417 - func TestNoteRepository_TagMethods(t *testing.T) { 418 - db := createNoteTestDB(t) 419 - repo := NewNoteRepository(db) 420 - ctx := context.Background() 421 422 - note := &models.Note{ 423 - Title: "Tag Test Note", 424 - Content: "Testing tags", 425 - Tags: []string{"initial"}, 426 - } 427 - id, err := repo.Create(ctx, note) 428 - if err != nil { 429 - t.Fatalf("Failed to create note: %v", err) 430 - } 431 - 432 - t.Run("AddTag", func(t *testing.T) { 433 - err := repo.AddTag(ctx, id, "new-tag") 434 - if err != nil { 435 - t.Fatalf("Failed to add tag: %v", err) 436 - } 437 - 438 - retrieved, err := repo.Get(ctx, id) 439 - if err != nil { 440 - t.Fatalf("Failed to get note: %v", err) 441 - } 442 443 - if len(retrieved.Tags) != 2 { 444 - t.Errorf("Expected 2 tags, got %d", len(retrieved.Tags)) 445 - } 446 447 - found := false 448 - for _, tag := range retrieved.Tags { 449 - if tag == "new-tag" { 450 - found = true 451 - break 452 } 453 - } 454 - if !found { 455 - t.Error("New tag not found in note") 456 - } 457 - }) 458 459 - t.Run("AddTag Duplicate", func(t *testing.T) { 460 - err := repo.AddTag(ctx, id, "new-tag") 461 - if err != nil { 462 - t.Fatalf("Failed to add duplicate tag: %v", err) 463 - } 464 465 - retrieved, err := repo.Get(ctx, id) 466 - if err != nil { 467 - t.Fatalf("Failed to get note: %v", err) 468 - } 469 470 - if len(retrieved.Tags) != 2 { 471 - t.Errorf("Expected 2 tags (no duplicate), got %d", len(retrieved.Tags)) 472 - } 473 - }) 474 475 - t.Run("RemoveTag", func(t *testing.T) { 476 - err := repo.RemoveTag(ctx, id, "initial") 477 - if err != nil { 478 - t.Fatalf("Failed to remove tag: %v", err) 479 - } 480 481 - retrieved, err := repo.Get(ctx, id) 482 - if err != nil { 483 - t.Fatalf("Failed to get note: %v", err) 484 - } 485 486 - if len(retrieved.Tags) != 1 { 487 - t.Errorf("Expected 1 tag after removal, got %d", len(retrieved.Tags)) 488 - } 489 490 - for _, tag := range retrieved.Tags { 491 - if tag == "initial" { 492 - t.Error("Removed tag still found in note") 493 } 494 - } 495 }) 496 497 - t.Run("GetByTags", func(t *testing.T) { 498 - note1 := &models.Note{ 499 - Title: "Note 1", 500 - Content: "Content 1", 501 - Tags: []string{"work", "urgent"}, 502 - } 503 - note2 := &models.Note{ 504 - Title: "Note 2", 505 - Content: "Content 2", 506 - Tags: []string{"personal", "ideas"}, 507 - } 508 - note3 := &models.Note{ 509 - Title: "Note 3", 510 - Content: "Content 3", 511 - Tags: []string{"work", "planning"}, 512 - } 513 514 - _, err := repo.Create(ctx, note1) 515 - if err != nil { 516 - t.Fatalf("Failed to create note1: %v", err) 517 - } 518 - _, err = repo.Create(ctx, note2) 519 - if err != nil { 520 - t.Fatalf("Failed to create note2: %v", err) 521 - } 522 - _, err = repo.Create(ctx, note3) 523 - if err != nil { 524 - t.Fatalf("Failed to create note3: %v", err) 525 - } 526 527 - results, err := repo.GetByTags(ctx, []string{"work"}) 528 - if err != nil { 529 - t.Fatalf("Failed to get notes by tag: %v", err) 530 - } 531 532 - if len(results) < 2 { 533 - t.Errorf("Expected at least 2 notes with 'work' tag, got %d", len(results)) 534 - } 535 536 - results, err = repo.GetByTags(ctx, []string{"nonexistent"}) 537 - if err != nil { 538 - t.Fatalf("Failed to get notes by nonexistent tag: %v", err) 539 - } 540 541 - if len(results) != 0 { 542 - t.Errorf("Expected 0 notes with nonexistent tag, got %d", len(results)) 543 - } 544 545 - results, err = repo.GetByTags(ctx, []string{}) 546 - if err != nil { 547 - t.Fatalf("Failed to get notes with empty tags: %v", err) 548 - } 549 - 550 - if len(results) != 0 { 551 - t.Errorf("Expected 0 notes with empty tag list, got %d", len(results)) 552 - } 553 }) 554 - } 555 556 - func TestNoteRepository_ErrorCases(t *testing.T) { 557 - db := createNoteTestDB(t) 558 - repo := NewNoteRepository(db) 559 - ctx := context.Background() 560 561 - t.Run("Get Nonexistent Note", func(t *testing.T) { 562 - _, err := repo.Get(ctx, 999) 563 - if err == nil { 564 - t.Error("Expected error when getting nonexistent note") 565 - } 566 - }) 567 568 - t.Run("Update Nonexistent Note", func(t *testing.T) { 569 - note := &models.Note{ 570 - ID: 999, 571 - Title: "Nonexistent", 572 - Content: "Should fail", 573 - } 574 575 - err := repo.Update(ctx, note) 576 - if err == nil { 577 - t.Error("Expected error when updating nonexistent note") 578 - } 579 - }) 580 581 - t.Run("Delete Nonexistent Note", func(t *testing.T) { 582 - err := repo.Delete(ctx, 999) 583 - if err == nil { 584 - t.Error("Expected error when deleting nonexistent note") 585 - } 586 - }) 587 588 - t.Run("Archive Nonexistent Note", func(t *testing.T) { 589 - err := repo.Archive(ctx, 999) 590 - if err == nil { 591 - t.Error("Expected error when archiving nonexistent note") 592 - } 593 - }) 594 - 595 - t.Run("AddTag to Nonexistent Note", func(t *testing.T) { 596 - err := repo.AddTag(ctx, 999, "tag") 597 - if err == nil { 598 - t.Error("Expected error when adding tag to nonexistent note") 599 - } 600 - }) 601 - } 602 - 603 - func TestNoteRepository_EdgeCases(t *testing.T) { 604 - db := createNoteTestDB(t) 605 - repo := NewNoteRepository(db) 606 - ctx := context.Background() 607 608 - t.Run("Note with Empty Tags", func(t *testing.T) { 609 - note := &models.Note{ 610 - Title: "No Tags Note", 611 - Content: "This note has no tags", 612 - Tags: []string{}, 613 - } 614 - 615 - id, err := repo.Create(ctx, note) 616 - if err != nil { 617 - t.Fatalf("Failed to create note with empty tags: %v", err) 618 - } 619 - 620 - retrieved, err := repo.Get(ctx, id) 621 - if err != nil { 622 - t.Fatalf("Failed to get note: %v", err) 623 - } 624 - 625 - if len(retrieved.Tags) != 0 { 626 - t.Errorf("Expected empty tags slice, got %d tags", len(retrieved.Tags)) 627 - } 628 - }) 629 - 630 - t.Run("Note with Nil Tags", func(t *testing.T) { 631 - note := &models.Note{ 632 - Title: "Nil Tags Note", 633 - Content: "This note has nil tags", 634 - Tags: nil, 635 - } 636 - 637 - id, err := repo.Create(ctx, note) 638 - if err != nil { 639 - t.Fatalf("Failed to create note with nil tags: %v", err) 640 - } 641 642 - retrieved, err := repo.Get(ctx, id) 643 - if err != nil { 644 - t.Fatalf("Failed to get note: %v", err) 645 - } 646 647 - if retrieved.Tags != nil { 648 - t.Errorf("Expected nil tags, got %v", retrieved.Tags) 649 - } 650 - }) 651 652 - t.Run("Note with Long Content", func(t *testing.T) { 653 - longContent := "" 654 - for i := 0; i < 1000; i++ { 655 - longContent += "This is a very long content string. " 656 - } 657 658 - note := &models.Note{ 659 - Title: "Long Content Note", 660 - Content: longContent, 661 - } 662 663 - id, err := repo.Create(ctx, note) 664 - if err != nil { 665 - t.Fatalf("Failed to create note with long content: %v", err) 666 - } 667 668 - retrieved, err := repo.Get(ctx, id) 669 - if err != nil { 670 - t.Fatalf("Failed to get note: %v", err) 671 - } 672 673 - if retrieved.Content != longContent { 674 - t.Error("Long content was not stored/retrieved correctly") 675 - } 676 }) 677 - }
··· 2 3 import ( 4 "context" 5 "testing" 6 7 _ "github.com/mattn/go-sqlite3" 8 "github.com/stormlightlabs/noteleaf/internal/models" 9 ) 10 11 + func TestNoteRepository(t *testing.T) { 12 + t.Run("CRUD", func(t *testing.T) { 13 + db := CreateTestDB(t) 14 + repo := NewNoteRepository(db) 15 + ctx := context.Background() 16 17 + t.Run("Create Note", func(t *testing.T) { 18 + note := CreateSampleNote() 19 20 + id, err := repo.Create(ctx, note) 21 + AssertNoError(t, err, "Failed to create note") 22 + AssertNotEqual(t, int64(0), id, "Expected non-zero ID") 23 + AssertEqual(t, id, note.ID, "Expected note ID to be set correctly") 24 + AssertFalse(t, note.Created.IsZero(), "Expected Created timestamp to be set") 25 + AssertFalse(t, note.Modified.IsZero(), "Expected Modified timestamp to be set") 26 + }) 27 28 + t.Run("Get Note", func(t *testing.T) { 29 + original := CreateSampleNote() 30 + id, err := repo.Create(ctx, original) 31 + AssertNoError(t, err, "Failed to create note") 32 33 + retrieved, err := repo.Get(ctx, id) 34 + AssertNoError(t, err, "Failed to get note") 35 36 + AssertEqual(t, original.ID, retrieved.ID, "ID mismatch") 37 + AssertEqual(t, original.Title, retrieved.Title, "Title mismatch") 38 + AssertEqual(t, original.Content, retrieved.Content, "Content mismatch") 39 + AssertEqual(t, len(original.Tags), len(retrieved.Tags), "Tags length mismatch") 40 + AssertEqual(t, original.Archived, retrieved.Archived, "Archived mismatch") 41 + AssertEqual(t, original.FilePath, retrieved.FilePath, "FilePath mismatch") 42 + }) 43 44 + t.Run("Update Note", func(t *testing.T) { 45 + note := CreateSampleNote() 46 + id, err := repo.Create(ctx, note) 47 + AssertNoError(t, err, "Failed to create note") 48 49 + originalModified := note.Modified 50 51 + note.Title = "Updated Title" 52 + note.Content = "Updated content" 53 + note.Tags = []string{"updated", "test"} 54 + note.Archived = true 55 + note.FilePath = "/new/path/note.md" 56 57 + err = repo.Update(ctx, note) 58 + AssertNoError(t, err, "Failed to update note") 59 60 + retrieved, err := repo.Get(ctx, id) 61 + AssertNoError(t, err, "Failed to get updated note") 62 63 + AssertEqual(t, "Updated Title", retrieved.Title, "Expected updated title") 64 + AssertEqual(t, "Updated content", retrieved.Content, "Expected updated content") 65 + AssertEqual(t, 2, len(retrieved.Tags), "Expected 2 tags") 66 + if len(retrieved.Tags) >= 2 { 67 + AssertEqual(t, "updated", retrieved.Tags[0], "Expected first tag to be 'updated'") 68 + AssertEqual(t, "test", retrieved.Tags[1], "Expected second tag to be 'test'") 69 + } 70 + AssertTrue(t, retrieved.Archived, "Expected note to be archived") 71 + AssertEqual(t, "/new/path/note.md", retrieved.FilePath, "Expected updated file path") 72 + AssertTrue(t, retrieved.Modified.After(originalModified), "Expected Modified timestamp to be updated") 73 + }) 74 75 + t.Run("Delete Note", func(t *testing.T) { 76 + note := CreateSampleNote() 77 + id, err := repo.Create(ctx, note) 78 + AssertNoError(t, err, "Failed to create note") 79 80 + err = repo.Delete(ctx, id) 81 + AssertNoError(t, err, "Failed to delete note") 82 83 + _, err = repo.Get(ctx, id) 84 + AssertError(t, err, "Expected error when getting deleted note") 85 + }) 86 }) 87 88 + t.Run("List", func(t *testing.T) { 89 + db := CreateTestDB(t) 90 + repo := NewNoteRepository(db) 91 + ctx := context.Background() 92 93 + notes := []*models.Note{ 94 + {Title: "First Note", Content: "Content 1", Tags: []string{"work"}, Archived: false}, 95 + {Title: "Second Note", Content: "Content 2", Tags: []string{"personal"}, Archived: true}, 96 + {Title: "Third Note", Content: "Important content", Tags: []string{"work", "important"}, Archived: false}, 97 } 98 99 + for _, note := range notes { 100 + _, err := repo.Create(ctx, note) 101 + AssertNoError(t, err, "Failed to create test note") 102 } 103 104 + t.Run("List All Notes", func(t *testing.T) { 105 + results, err := repo.List(ctx, NoteListOptions{}) 106 + AssertNoError(t, err, "Failed to list notes") 107 + AssertEqual(t, 3, len(results), "Expected 3 notes") 108 + }) 109 110 + t.Run("List Archived Notes Only", func(t *testing.T) { 111 + archived := true 112 + results, err := repo.List(ctx, NoteListOptions{Archived: &archived}) 113 + AssertNoError(t, err, "Failed to list archived notes") 114 + AssertEqual(t, 1, len(results), "Expected 1 archived note") 115 + if len(results) > 0 { 116 + AssertTrue(t, results[0].Archived, "Retrieved note should be archived") 117 + } 118 + }) 119 120 + t.Run("List Active Notes Only", func(t *testing.T) { 121 + archived := false 122 + results, err := repo.List(ctx, NoteListOptions{Archived: &archived}) 123 + AssertNoError(t, err, "Failed to list active notes") 124 + AssertEqual(t, 2, len(results), "Expected 2 active notes") 125 + for _, note := range results { 126 + AssertFalse(t, note.Archived, "Retrieved note should not be archived") 127 + } 128 + }) 129 130 + t.Run("Search by Title", func(t *testing.T) { 131 + results, err := repo.List(ctx, NoteListOptions{Title: "First"}) 132 + AssertNoError(t, err, "Failed to search by title") 133 + AssertEqual(t, 1, len(results), "Expected 1 note") 134 + if len(results) > 0 { 135 + AssertEqual(t, "First Note", results[0].Title, "Expected 'First Note'") 136 + } 137 + }) 138 139 + t.Run("Search by Content", func(t *testing.T) { 140 + results, err := repo.List(ctx, NoteListOptions{Content: "Important"}) 141 + AssertNoError(t, err, "Failed to search by content") 142 + AssertEqual(t, 1, len(results), "Expected 1 note") 143 + if len(results) > 0 { 144 + AssertEqual(t, "Third Note", results[0].Title, "Expected 'Third Note'") 145 + } 146 + }) 147 148 + t.Run("Limit and Offset", func(t *testing.T) { 149 + results, err := repo.List(ctx, NoteListOptions{Limit: 2}) 150 + AssertNoError(t, err, "Failed to list with limit") 151 + AssertEqual(t, 2, len(results), "Expected 2 notes") 152 153 + results, err = repo.List(ctx, NoteListOptions{Limit: 2, Offset: 1}) 154 + AssertNoError(t, err, "Failed to list with limit and offset") 155 + AssertEqual(t, 2, len(results), "Expected 2 notes with offset") 156 + }) 157 }) 158 159 + t.Run("Specialized Methods", func(t *testing.T) { 160 + db := CreateTestDB(t) 161 + repo := NewNoteRepository(db) 162 + ctx := context.Background() 163 164 + notes := []*models.Note{ 165 + {Title: "Work Note", Content: "Work content", Tags: []string{"work"}, Archived: false}, 166 + {Title: "Personal Note", Content: "Personal content", Tags: []string{"personal"}, Archived: true}, 167 + {Title: "Important Note", Content: "Important content", Tags: []string{"work", "important"}, Archived: false}, 168 } 169 170 + for _, note := range notes { 171 + _, err := repo.Create(ctx, note) 172 + AssertNoError(t, err, "Failed to create test note") 173 } 174 175 + t.Run("GetByTitle", func(t *testing.T) { 176 + results, err := repo.GetByTitle(ctx, "Work") 177 + AssertNoError(t, err, "Failed to get by title") 178 + AssertEqual(t, 1, len(results), "Expected 1 note") 179 + if len(results) > 0 { 180 + AssertEqual(t, "Work Note", results[0].Title, "Expected 'Work Note'") 181 } 182 + }) 183 184 + t.Run("GetArchived", func(t *testing.T) { 185 + results, err := repo.GetArchived(ctx) 186 + AssertNoError(t, err, "Failed to get archived notes") 187 + AssertEqual(t, 1, len(results), "Expected 1 archived note") 188 + if len(results) > 0 { 189 + AssertTrue(t, results[0].Archived, "Retrieved note should be archived") 190 + } 191 + }) 192 193 + t.Run("GetActive", func(t *testing.T) { 194 + results, err := repo.GetActive(ctx) 195 + AssertNoError(t, err, "Failed to get active notes") 196 + AssertEqual(t, 2, len(results), "Expected 2 active notes") 197 + for _, note := range results { 198 + AssertFalse(t, note.Archived, "Retrieved note should not be archived") 199 + } 200 + }) 201 202 + t.Run("Archive and Unarchive", func(t *testing.T) { 203 + note := &models.Note{ 204 + Title: "Test Archive", 205 + Content: "Archive test", 206 + Archived: false, 207 + } 208 + id, err := repo.Create(ctx, note) 209 + AssertNoError(t, err, "Failed to create note") 210 211 + err = repo.Archive(ctx, id) 212 + AssertNoError(t, err, "Failed to archive note") 213 214 + retrieved, err := repo.Get(ctx, id) 215 + AssertNoError(t, err, "Failed to get note") 216 + AssertTrue(t, retrieved.Archived, "Note should be archived") 217 218 + err = repo.Unarchive(ctx, id) 219 + AssertNoError(t, err, "Failed to unarchive note") 220 221 + retrieved, err = repo.Get(ctx, id) 222 + AssertNoError(t, err, "Failed to get note") 223 + AssertFalse(t, retrieved.Archived, "Note should not be archived") 224 + }) 225 226 + t.Run("SearchContent", func(t *testing.T) { 227 + results, err := repo.SearchContent(ctx, "Important") 228 + AssertNoError(t, err, "Failed to search content") 229 + AssertEqual(t, 1, len(results), "Expected 1 note") 230 + if len(results) > 0 { 231 + AssertEqual(t, "Important Note", results[0].Title, "Expected 'Important Note'") 232 + } 233 + }) 234 235 + t.Run("GetRecent", func(t *testing.T) { 236 + results, err := repo.GetRecent(ctx, 2) 237 + AssertNoError(t, err, "Failed to get recent notes") 238 + AssertEqual(t, 2, len(results), "Expected 2 notes") 239 + }) 240 }) 241 242 + t.Run("Tag Methods", func(t *testing.T) { 243 + db := CreateTestDB(t) 244 + repo := NewNoteRepository(db) 245 + ctx := context.Background() 246 247 note := &models.Note{ 248 + Title: "Tag Test Note", 249 + Content: "Testing tags", 250 + Tags: []string{"initial"}, 251 } 252 id, err := repo.Create(ctx, note) 253 if err != nil { 254 t.Fatalf("Failed to create note: %v", err) 255 } 256 257 + t.Run("AddTag", func(t *testing.T) { 258 + err := repo.AddTag(ctx, id, "new-tag") 259 + if err != nil { 260 + t.Fatalf("Failed to add tag: %v", err) 261 + } 262 263 + retrieved, err := repo.Get(ctx, id) 264 + if err != nil { 265 + t.Fatalf("Failed to get note: %v", err) 266 + } 267 268 + if len(retrieved.Tags) != 2 { 269 + t.Errorf("Expected 2 tags, got %d", len(retrieved.Tags)) 270 + } 271 272 + found := false 273 + for _, tag := range retrieved.Tags { 274 + if tag == "new-tag" { 275 + found = true 276 + break 277 + } 278 + } 279 + if !found { 280 + t.Error("New tag not found in note") 281 + } 282 + }) 283 284 + t.Run("AddTag Duplicate", func(t *testing.T) { 285 + err := repo.AddTag(ctx, id, "new-tag") 286 + AssertNoError(t, err, "Failed to add duplicate tag") 287 288 + retrieved, err := repo.Get(ctx, id) 289 + AssertNoError(t, err, "Failed to get note") 290 291 + AssertEqual(t, 2, len(retrieved.Tags), "Expected 2 tags (no duplicate)") 292 + }) 293 294 + t.Run("RemoveTag", func(t *testing.T) { 295 + err := repo.RemoveTag(ctx, id, "initial") 296 + AssertNoError(t, err, "Failed to remove tag") 297 298 + retrieved, err := repo.Get(ctx, id) 299 + AssertNoError(t, err, "Failed to get note") 300 301 + AssertEqual(t, 1, len(retrieved.Tags), "Expected 1 tag after removal") 302 303 + for _, tag := range retrieved.Tags { 304 + AssertNotEqual(t, "initial", tag, "Removed tag still found in note") 305 + } 306 + }) 307 308 + t.Run("GetByTags", func(t *testing.T) { 309 + note1 := &models.Note{ 310 + Title: "Note 1", 311 + Content: "Content 1", 312 + Tags: []string{"work", "urgent"}, 313 } 314 + note2 := &models.Note{ 315 + Title: "Note 2", 316 + Content: "Content 2", 317 + Tags: []string{"personal", "ideas"}, 318 + } 319 + note3 := &models.Note{ 320 + Title: "Note 3", 321 + Content: "Content 3", 322 + Tags: []string{"work", "planning"}, 323 + } 324 325 + _, err := repo.Create(ctx, note1) 326 + if err != nil { 327 + t.Fatalf("Failed to create note1: %v", err) 328 + } 329 + _, err = repo.Create(ctx, note2) 330 + if err != nil { 331 + t.Fatalf("Failed to create note2: %v", err) 332 + } 333 + _, err = repo.Create(ctx, note3) 334 + if err != nil { 335 + t.Fatalf("Failed to create note3: %v", err) 336 + } 337 338 + results, err := repo.GetByTags(ctx, []string{"work"}) 339 + if err != nil { 340 + t.Fatalf("Failed to get notes by tag: %v", err) 341 + } 342 343 + if len(results) < 2 { 344 + t.Errorf("Expected at least 2 notes with 'work' tag, got %d", len(results)) 345 + } 346 347 + results, err = repo.GetByTags(ctx, []string{"nonexistent"}) 348 + if err != nil { 349 + t.Fatalf("Failed to get notes by nonexistent tag: %v", err) 350 + } 351 352 + if len(results) != 0 { 353 + t.Errorf("Expected 0 notes with nonexistent tag, got %d", len(results)) 354 + } 355 356 + results, err = repo.GetByTags(ctx, []string{}) 357 + if err != nil { 358 + t.Fatalf("Failed to get notes with empty tags: %v", err) 359 + } 360 361 + if len(results) != 0 { 362 + t.Errorf("Expected 0 notes with empty tag list, got %d", len(results)) 363 } 364 + }) 365 }) 366 367 + t.Run("Error Cases", func(t *testing.T) { 368 + db := CreateTestDB(t) 369 + repo := NewNoteRepository(db) 370 + ctx := context.Background() 371 372 + t.Run("Get Nonexistent Note", func(t *testing.T) { 373 + _, err := repo.Get(ctx, 999) 374 + if err == nil { 375 + t.Error("Expected error when getting nonexistent note") 376 + } 377 + }) 378 379 + t.Run("Update Nonexistent Note", func(t *testing.T) { 380 + note := &models.Note{ 381 + ID: 999, 382 + Title: "Nonexistent", 383 + Content: "Should fail", 384 + } 385 386 + err := repo.Update(ctx, note) 387 + if err == nil { 388 + t.Error("Expected error when updating nonexistent note") 389 + } 390 + }) 391 392 + t.Run("Delete Nonexistent Note", func(t *testing.T) { 393 + err := repo.Delete(ctx, 999) 394 + if err == nil { 395 + t.Error("Expected error when deleting nonexistent note") 396 + } 397 + }) 398 399 + t.Run("Archive Nonexistent Note", func(t *testing.T) { 400 + err := repo.Archive(ctx, 999) 401 + if err == nil { 402 + t.Error("Expected error when archiving nonexistent note") 403 + } 404 + }) 405 406 + t.Run("AddTag to Nonexistent Note", func(t *testing.T) { 407 + err := repo.AddTag(ctx, 999, "tag") 408 + if err == nil { 409 + t.Error("Expected error when adding tag to nonexistent note") 410 + } 411 + }) 412 }) 413 414 + t.Run("Edge Cases", func(t *testing.T) { 415 + db := CreateTestDB(t) 416 + repo := NewNoteRepository(db) 417 + ctx := context.Background() 418 419 + t.Run("Note with Empty Tags", func(t *testing.T) { 420 + note := &models.Note{ 421 + Title: "No Tags Note", 422 + Content: "This note has no tags", 423 + Tags: []string{}, 424 + } 425 426 + id, err := repo.Create(ctx, note) 427 + if err != nil { 428 + t.Fatalf("Failed to create note with empty tags: %v", err) 429 + } 430 431 + retrieved, err := repo.Get(ctx, id) 432 + if err != nil { 433 + t.Fatalf("Failed to get note: %v", err) 434 + } 435 436 + if len(retrieved.Tags) != 0 { 437 + t.Errorf("Expected empty tags slice, got %d tags", len(retrieved.Tags)) 438 + } 439 + }) 440 441 + t.Run("Note with Nil Tags", func(t *testing.T) { 442 + note := &models.Note{ 443 + Title: "Nil Tags Note", 444 + Content: "This note has nil tags", 445 + Tags: nil, 446 + } 447 448 + id, err := repo.Create(ctx, note) 449 + if err != nil { 450 + t.Fatalf("Failed to create note with nil tags: %v", err) 451 + } 452 453 + retrieved, err := repo.Get(ctx, id) 454 + if err != nil { 455 + t.Fatalf("Failed to get note: %v", err) 456 + } 457 458 + if retrieved.Tags != nil { 459 + t.Errorf("Expected nil tags, got %v", retrieved.Tags) 460 + } 461 + }) 462 463 + t.Run("Note with Long Content", func(t *testing.T) { 464 + longContent := "" 465 + for i := 0; i < 1000; i++ { 466 + longContent += "This is a very long content string. " 467 + } 468 469 + note := &models.Note{ 470 + Title: "Long Content Note", 471 + Content: longContent, 472 + } 473 474 + id, err := repo.Create(ctx, note) 475 + if err != nil { 476 + t.Fatalf("Failed to create note with long content: %v", err) 477 + } 478 479 + retrieved, err := repo.Get(ctx, id) 480 + if err != nil { 481 + t.Fatalf("Failed to get note: %v", err) 482 + } 483 484 + if retrieved.Content != longContent { 485 + t.Error("Long content was not stored/retrieved correctly") 486 + } 487 + }) 488 }) 489 + }
+15 -71
internal/repo/task_repository_test.go
··· 2 3 import ( 4 "context" 5 - "database/sql" 6 "slices" 7 "testing" 8 "time" ··· 12 "github.com/stormlightlabs/noteleaf/internal/models" 13 ) 14 15 - func createTaskTestDB(t *testing.T) *sql.DB { 16 - db, err := sql.Open("sqlite3", ":memory:") 17 - if err != nil { 18 - t.Fatalf("Failed to create in-memory database: %v", err) 19 - } 20 - 21 - if _, err := db.Exec("PRAGMA foreign_keys = ON"); err != nil { 22 - t.Fatalf("Failed to enable foreign keys: %v", err) 23 - } 24 - 25 - schema := ` 26 - CREATE TABLE IF NOT EXISTS tasks ( 27 - id INTEGER PRIMARY KEY AUTOINCREMENT, 28 - uuid TEXT UNIQUE NOT NULL, 29 - description TEXT NOT NULL, 30 - status TEXT DEFAULT 'pending', 31 - priority TEXT, 32 - project TEXT, 33 - context TEXT, 34 - tags TEXT, 35 - due DATETIME, 36 - entry DATETIME DEFAULT CURRENT_TIMESTAMP, 37 - modified DATETIME DEFAULT CURRENT_TIMESTAMP, 38 - end DATETIME, 39 - start DATETIME, 40 - annotations TEXT 41 - ); 42 - ` 43 - 44 - if _, err := db.Exec(schema); err != nil { 45 - t.Fatalf("Failed to create schema: %v", err) 46 - } 47 - 48 - t.Cleanup(func() { 49 - db.Close() 50 - }) 51 - 52 - return db 53 - } 54 - 55 - func createSampleTask() *models.Task { 56 - return &models.Task{ 57 - UUID: newUUID(), 58 - Description: "Test task", 59 - Status: "pending", 60 - Priority: "H", 61 - Project: "test-project", 62 - Context: "test-context", 63 - Tags: []string{"test", "important"}, 64 - Annotations: []string{"This is a test", "Another annotation"}, 65 - } 66 - } 67 - 68 func newUUID() string { 69 return uuid.New().String() 70 } 71 72 func TestTaskRepository(t *testing.T) { 73 - db := createTaskTestDB(t) 74 repo := NewTaskRepository(db) 75 ctx := context.Background() 76 77 t.Run("Create Task", func(t *testing.T) { 78 - task := createSampleTask() 79 80 id, err := repo.Create(ctx, task) 81 if err != nil { ··· 99 }) 100 101 t.Run("Get Task", func(t *testing.T) { 102 - original := createSampleTask() 103 id, err := repo.Create(ctx, original) 104 if err != nil { 105 t.Fatalf("Failed to create task: %v", err) ··· 144 }) 145 146 t.Run("Update Task", func(t *testing.T) { 147 - task := createSampleTask() 148 id, err := repo.Create(ctx, task) 149 if err != nil { 150 t.Fatalf("Failed to create task: %v", err) ··· 181 }) 182 183 t.Run("Delete Task", func(t *testing.T) { 184 - task := createSampleTask() 185 id, err := repo.Create(ctx, task) 186 if err != nil { 187 t.Fatalf("Failed to create task: %v", err) ··· 681 }) 682 683 t.Run("GetByPriority", func(t *testing.T) { 684 - // Test numeric priority 685 results, err := repo.GetByPriority(ctx, "5") 686 if err != nil { 687 t.Errorf("Failed to get tasks by priority 5: %v", err) ··· 713 } 714 } 715 716 - // Test empty priority - create a specific task with no priority for this test 717 noPriorityTask := &models.Task{ 718 UUID: newUUID(), 719 Description: "No priority task for test", ··· 828 } 829 830 func TestTaskRepository_GetContexts(t *testing.T) { 831 - db := createTaskTestDB(t) 832 repo := NewTaskRepository(db) 833 ctx := context.Background() 834 835 // Create tasks with different contexts 836 - task1 := createSampleTask() 837 task1.Context = "work" 838 _, err := repo.Create(ctx, task1) 839 if err != nil { 840 t.Fatalf("Failed to create task1: %v", err) 841 } 842 843 - task2 := createSampleTask() 844 task2.Context = "home" 845 _, err = repo.Create(ctx, task2) 846 if err != nil { 847 t.Fatalf("Failed to create task2: %v", err) 848 } 849 850 - task3 := createSampleTask() 851 task3.Context = "work" 852 _, err = repo.Create(ctx, task3) 853 if err != nil { ··· 855 } 856 857 // Task with empty context should not be included 858 - task4 := createSampleTask() 859 task4.Context = "" 860 _, err = repo.Create(ctx, task4) 861 if err != nil { ··· 888 } 889 890 func TestTaskRepository_GetByContext(t *testing.T) { 891 - db := createTaskTestDB(t) 892 repo := NewTaskRepository(db) 893 ctx := context.Background() 894 895 // Create tasks with different contexts 896 - task1 := createSampleTask() 897 task1.Context = "work" 898 task1.Description = "Work task 1" 899 _, err := repo.Create(ctx, task1) ··· 901 t.Fatalf("Failed to create task1: %v", err) 902 } 903 904 - task2 := createSampleTask() 905 task2.Context = "home" 906 task2.Description = "Home task 1" 907 _, err = repo.Create(ctx, task2) ··· 909 t.Fatalf("Failed to create task2: %v", err) 910 } 911 912 - task3 := createSampleTask() 913 task3.Context = "work" 914 task3.Description = "Work task 2" 915 _, err = repo.Create(ctx, task3) ··· 933 } 934 } 935 936 - // Get tasks by home context 937 homeTasks, err := repo.GetByContext(ctx, "home") 938 if err != nil { 939 t.Fatalf("Failed to get tasks by context: %v", err)
··· 2 3 import ( 4 "context" 5 "slices" 6 "testing" 7 "time" ··· 11 "github.com/stormlightlabs/noteleaf/internal/models" 12 ) 13 14 func newUUID() string { 15 return uuid.New().String() 16 } 17 18 func TestTaskRepository(t *testing.T) { 19 + db := CreateTestDB(t) 20 repo := NewTaskRepository(db) 21 ctx := context.Background() 22 23 t.Run("Create Task", func(t *testing.T) { 24 + task := CreateSampleTask() 25 26 id, err := repo.Create(ctx, task) 27 if err != nil { ··· 45 }) 46 47 t.Run("Get Task", func(t *testing.T) { 48 + original := CreateSampleTask() 49 id, err := repo.Create(ctx, original) 50 if err != nil { 51 t.Fatalf("Failed to create task: %v", err) ··· 90 }) 91 92 t.Run("Update Task", func(t *testing.T) { 93 + task := CreateSampleTask() 94 id, err := repo.Create(ctx, task) 95 if err != nil { 96 t.Fatalf("Failed to create task: %v", err) ··· 127 }) 128 129 t.Run("Delete Task", func(t *testing.T) { 130 + task := CreateSampleTask() 131 id, err := repo.Create(ctx, task) 132 if err != nil { 133 t.Fatalf("Failed to create task: %v", err) ··· 627 }) 628 629 t.Run("GetByPriority", func(t *testing.T) { 630 results, err := repo.GetByPriority(ctx, "5") 631 if err != nil { 632 t.Errorf("Failed to get tasks by priority 5: %v", err) ··· 658 } 659 } 660 661 noPriorityTask := &models.Task{ 662 UUID: newUUID(), 663 Description: "No priority task for test", ··· 772 } 773 774 func TestTaskRepository_GetContexts(t *testing.T) { 775 + db := CreateTestDB(t) 776 repo := NewTaskRepository(db) 777 ctx := context.Background() 778 779 // Create tasks with different contexts 780 + task1 := CreateSampleTask() 781 task1.Context = "work" 782 _, err := repo.Create(ctx, task1) 783 if err != nil { 784 t.Fatalf("Failed to create task1: %v", err) 785 } 786 787 + task2 := CreateSampleTask() 788 task2.Context = "home" 789 _, err = repo.Create(ctx, task2) 790 if err != nil { 791 t.Fatalf("Failed to create task2: %v", err) 792 } 793 794 + task3 := CreateSampleTask() 795 task3.Context = "work" 796 _, err = repo.Create(ctx, task3) 797 if err != nil { ··· 799 } 800 801 // Task with empty context should not be included 802 + task4 := CreateSampleTask() 803 task4.Context = "" 804 _, err = repo.Create(ctx, task4) 805 if err != nil { ··· 832 } 833 834 func TestTaskRepository_GetByContext(t *testing.T) { 835 + db := CreateTestDB(t) 836 repo := NewTaskRepository(db) 837 ctx := context.Background() 838 839 // Create tasks with different contexts 840 + task1 := CreateSampleTask() 841 task1.Context = "work" 842 task1.Description = "Work task 1" 843 _, err := repo.Create(ctx, task1) ··· 845 t.Fatalf("Failed to create task1: %v", err) 846 } 847 848 + task2 := CreateSampleTask() 849 task2.Context = "home" 850 task2.Description = "Home task 1" 851 _, err = repo.Create(ctx, task2) ··· 853 t.Fatalf("Failed to create task2: %v", err) 854 } 855 856 + task3 := CreateSampleTask() 857 task3.Context = "work" 858 task3.Description = "Work task 2" 859 _, err = repo.Create(ctx, task3) ··· 877 } 878 } 879 880 + // Get tasks by home context 881 homeTasks, err := repo.GetByContext(ctx, "home") 882 if err != nil { 883 t.Fatalf("Failed to get tasks by context: %v", err)
+326
internal/repo/test_utilities.go
···
··· 1 + package repo 2 + 3 + import ( 4 + "context" 5 + "database/sql" 6 + "testing" 7 + "time" 8 + 9 + "github.com/google/uuid" 10 + _ "github.com/mattn/go-sqlite3" 11 + "github.com/stormlightlabs/noteleaf/internal/models" 12 + ) 13 + 14 + // CreateTestDB creates an in-memory SQLite database with the full schema for testing 15 + func CreateTestDB(t *testing.T) *sql.DB { 16 + db, err := sql.Open("sqlite3", ":memory:") 17 + if err != nil { 18 + t.Fatalf("Failed to create in-memory database: %v", err) 19 + } 20 + 21 + if _, err := db.Exec("PRAGMA foreign_keys = ON"); err != nil { 22 + t.Fatalf("Failed to enable foreign keys: %v", err) 23 + } 24 + 25 + // Full schema for all tables 26 + schema := ` 27 + CREATE TABLE IF NOT EXISTS tasks ( 28 + id INTEGER PRIMARY KEY AUTOINCREMENT, 29 + uuid TEXT UNIQUE NOT NULL, 30 + description TEXT NOT NULL, 31 + status TEXT DEFAULT 'pending', 32 + priority TEXT, 33 + project TEXT, 34 + context TEXT, 35 + tags TEXT, 36 + due DATETIME, 37 + entry DATETIME DEFAULT CURRENT_TIMESTAMP, 38 + modified DATETIME DEFAULT CURRENT_TIMESTAMP, 39 + end DATETIME, 40 + start DATETIME, 41 + annotations TEXT 42 + ); 43 + 44 + CREATE TABLE IF NOT EXISTS books ( 45 + id INTEGER PRIMARY KEY AUTOINCREMENT, 46 + title TEXT NOT NULL, 47 + author TEXT, 48 + status TEXT DEFAULT 'queued', 49 + progress INTEGER DEFAULT 0, 50 + pages INTEGER, 51 + rating REAL, 52 + notes TEXT, 53 + added DATETIME DEFAULT CURRENT_TIMESTAMP, 54 + started DATETIME, 55 + finished DATETIME 56 + ); 57 + 58 + CREATE TABLE IF NOT EXISTS movies ( 59 + id INTEGER PRIMARY KEY AUTOINCREMENT, 60 + title TEXT NOT NULL, 61 + year INTEGER, 62 + status TEXT DEFAULT 'queued', 63 + rating REAL, 64 + notes TEXT, 65 + added DATETIME DEFAULT CURRENT_TIMESTAMP, 66 + watched DATETIME 67 + ); 68 + 69 + CREATE TABLE IF NOT EXISTS tv_shows ( 70 + id INTEGER PRIMARY KEY AUTOINCREMENT, 71 + title TEXT NOT NULL, 72 + season INTEGER, 73 + episode INTEGER, 74 + status TEXT DEFAULT 'queued', 75 + rating REAL, 76 + notes TEXT, 77 + added DATETIME DEFAULT CURRENT_TIMESTAMP, 78 + last_watched DATETIME 79 + ); 80 + 81 + CREATE TABLE IF NOT EXISTS notes ( 82 + id INTEGER PRIMARY KEY AUTOINCREMENT, 83 + title TEXT NOT NULL, 84 + content TEXT, 85 + tags TEXT, 86 + archived BOOLEAN DEFAULT FALSE, 87 + created DATETIME DEFAULT CURRENT_TIMESTAMP, 88 + modified DATETIME DEFAULT CURRENT_TIMESTAMP, 89 + file_path TEXT 90 + ); 91 + 92 + CREATE TABLE IF NOT EXISTS time_entries ( 93 + id INTEGER PRIMARY KEY AUTOINCREMENT, 94 + task_id INTEGER NOT NULL, 95 + start_time DATETIME NOT NULL, 96 + end_time DATETIME, 97 + duration_seconds INTEGER, 98 + created DATETIME DEFAULT CURRENT_TIMESTAMP, 99 + modified DATETIME DEFAULT CURRENT_TIMESTAMP, 100 + FOREIGN KEY (task_id) REFERENCES tasks(id) ON DELETE CASCADE 101 + ); 102 + ` 103 + 104 + if _, err := db.Exec(schema); err != nil { 105 + t.Fatalf("Failed to create schema: %v", err) 106 + } 107 + 108 + t.Cleanup(func() { 109 + db.Close() 110 + }) 111 + 112 + return db 113 + } 114 + 115 + // Sample data creators 116 + func CreateSampleTask() *models.Task { 117 + return &models.Task{ 118 + UUID: uuid.New().String(), 119 + Description: "Test Task", 120 + Status: "pending", 121 + Priority: "medium", 122 + Project: "test-project", 123 + Context: "test-context", 124 + Tags: []string{"test", "sample"}, 125 + Entry: time.Now(), 126 + Modified: time.Now(), 127 + } 128 + } 129 + 130 + func CreateSampleBook() *models.Book { 131 + return &models.Book{ 132 + Title: "Test Book", 133 + Author: "Test Author", 134 + Status: "queued", 135 + Progress: 0, 136 + Pages: 300, 137 + Rating: 4.5, 138 + Notes: "Great book!", 139 + Added: time.Now(), 140 + } 141 + } 142 + 143 + func CreateSampleMovie() *models.Movie { 144 + return &models.Movie{ 145 + Title: "Test Movie", 146 + Year: 2023, 147 + Status: "queued", 148 + Rating: 8.5, 149 + Notes: "Excellent film", 150 + Added: time.Now(), 151 + } 152 + } 153 + 154 + func CreateSampleTVShow() *models.TVShow { 155 + return &models.TVShow{ 156 + Title: "Test TV Show", 157 + Season: 1, 158 + Episode: 1, 159 + Status: "queued", 160 + Rating: 9.0, 161 + Notes: "Amazing series", 162 + Added: time.Now(), 163 + } 164 + } 165 + 166 + func CreateSampleNote() *models.Note { 167 + return &models.Note{ 168 + Title: "Test Note", 169 + Content: "This is a test note content", 170 + Tags: []string{"test", "sample"}, 171 + Archived: false, 172 + Created: time.Now(), 173 + Modified: time.Now(), 174 + } 175 + } 176 + 177 + func CreateSampleTimeEntry(taskID int64) *models.TimeEntry { 178 + startTime := time.Now().Add(-time.Hour) 179 + return &models.TimeEntry{ 180 + TaskID: taskID, 181 + StartTime: startTime, 182 + EndTime: nil, 183 + DurationSeconds: 0, 184 + Created: startTime, 185 + Modified: startTime, 186 + } 187 + } 188 + 189 + // Test helpers for common operations 190 + func AssertNoError(t *testing.T, err error, msg string) { 191 + t.Helper() 192 + if err != nil { 193 + t.Fatalf("%s: %v", msg, err) 194 + } 195 + } 196 + 197 + func AssertError(t *testing.T, err error, msg string) { 198 + t.Helper() 199 + if err == nil { 200 + t.Fatalf("%s: expected error but got none", msg) 201 + } 202 + } 203 + 204 + func AssertEqual[T comparable](t *testing.T, expected, actual T, msg string) { 205 + t.Helper() 206 + if expected != actual { 207 + t.Fatalf("%s: expected %v, got %v", msg, expected, actual) 208 + } 209 + } 210 + 211 + func AssertNotEqual[T comparable](t *testing.T, notExpected, actual T, msg string) { 212 + t.Helper() 213 + if notExpected == actual { 214 + t.Fatalf("%s: expected value to not equal %v", msg, notExpected) 215 + } 216 + } 217 + 218 + func AssertTrue(t *testing.T, condition bool, msg string) { 219 + t.Helper() 220 + if !condition { 221 + t.Fatalf("%s: expected true", msg) 222 + } 223 + } 224 + 225 + func AssertFalse(t *testing.T, condition bool, msg string) { 226 + t.Helper() 227 + if condition { 228 + t.Fatalf("%s: expected false", msg) 229 + } 230 + } 231 + 232 + // SetupTestData creates sample data in the database and returns the repositories 233 + func SetupTestData(t *testing.T, db *sql.DB) *Repositories { 234 + ctx := context.Background() 235 + repos := NewRepositories(db) 236 + 237 + // Create sample tasks 238 + task1 := CreateSampleTask() 239 + task1.Description = "Sample Task 1" 240 + task1.Status = "pending" 241 + task1.Priority = "high" 242 + 243 + task2 := CreateSampleTask() 244 + task2.Description = "Sample Task 2" 245 + task2.Status = "completed" 246 + task2.Priority = "low" 247 + 248 + id1, err := repos.Tasks.Create(ctx, task1) 249 + AssertNoError(t, err, "Failed to create sample task 1") 250 + task1.ID = id1 251 + 252 + id2, err := repos.Tasks.Create(ctx, task2) 253 + AssertNoError(t, err, "Failed to create sample task 2") 254 + task2.ID = id2 255 + 256 + // Create sample books 257 + book1 := CreateSampleBook() 258 + book1.Title = "Sample Book 1" 259 + book1.Status = "reading" 260 + 261 + book2 := CreateSampleBook() 262 + book2.Title = "Sample Book 2" 263 + book2.Status = "finished" 264 + 265 + bookID1, err := repos.Books.Create(ctx, book1) 266 + AssertNoError(t, err, "Failed to create sample book 1") 267 + book1.ID = bookID1 268 + 269 + bookID2, err := repos.Books.Create(ctx, book2) 270 + AssertNoError(t, err, "Failed to create sample book 2") 271 + book2.ID = bookID2 272 + 273 + // Create sample movies 274 + movie1 := CreateSampleMovie() 275 + movie1.Title = "Sample Movie 1" 276 + movie1.Status = "queued" 277 + 278 + movie2 := CreateSampleMovie() 279 + movie2.Title = "Sample Movie 2" 280 + movie2.Status = "watched" 281 + 282 + movieID1, err := repos.Movies.Create(ctx, movie1) 283 + AssertNoError(t, err, "Failed to create sample movie 1") 284 + movie1.ID = movieID1 285 + 286 + movieID2, err := repos.Movies.Create(ctx, movie2) 287 + AssertNoError(t, err, "Failed to create sample movie 2") 288 + movie2.ID = movieID2 289 + 290 + // Create sample TV shows 291 + tv1 := CreateSampleTVShow() 292 + tv1.Title = "Sample TV Show 1" 293 + tv1.Status = "queued" 294 + 295 + tv2 := CreateSampleTVShow() 296 + tv2.Title = "Sample TV Show 2" 297 + tv2.Status = "watching" 298 + 299 + tvID1, err := repos.TV.Create(ctx, tv1) 300 + AssertNoError(t, err, "Failed to create sample TV show 1") 301 + tv1.ID = tvID1 302 + 303 + tvID2, err := repos.TV.Create(ctx, tv2) 304 + AssertNoError(t, err, "Failed to create sample TV show 2") 305 + tv2.ID = tvID2 306 + 307 + // Create sample notes 308 + note1 := CreateSampleNote() 309 + note1.Title = "Sample Note 1" 310 + note1.Content = "Content for note 1" 311 + 312 + note2 := CreateSampleNote() 313 + note2.Title = "Sample Note 2" 314 + note2.Content = "Content for note 2" 315 + note2.Archived = true 316 + 317 + noteID1, err := repos.Notes.Create(ctx, note1) 318 + AssertNoError(t, err, "Failed to create sample note 1") 319 + note1.ID = noteID1 320 + 321 + noteID2, err := repos.Notes.Create(ctx, note2) 322 + AssertNoError(t, err, "Failed to create sample note 2") 323 + note2.ID = noteID2 324 + 325 + return repos 326 + }
+8 -55
internal/repo/tv_repository_test.go
··· 2 3 import ( 4 "context" 5 - "database/sql" 6 "testing" 7 "time" 8 ··· 10 "github.com/stormlightlabs/noteleaf/internal/models" 11 ) 12 13 - func createTVTestDB(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 tv_shows ( 25 - id INTEGER PRIMARY KEY AUTOINCREMENT, 26 - title TEXT NOT NULL, 27 - season INTEGER, 28 - episode INTEGER, 29 - status TEXT DEFAULT 'queued', 30 - rating REAL, 31 - notes TEXT, 32 - added DATETIME DEFAULT CURRENT_TIMESTAMP, 33 - last_watched DATETIME 34 - ); 35 - ` 36 - 37 - if _, err := db.Exec(schema); err != nil { 38 - t.Fatalf("Failed to create schema: %v", err) 39 - } 40 - 41 - t.Cleanup(func() { 42 - db.Close() 43 - }) 44 - 45 - return db 46 - } 47 - 48 - func createSampleTVShow() *models.TVShow { 49 - return &models.TVShow{ 50 - Title: "Test Show", 51 - Season: 1, 52 - Episode: 1, 53 - Status: "queued", 54 - Rating: 9.0, 55 - Notes: "Excellent series", 56 - } 57 - } 58 - 59 func TestTVRepository(t *testing.T) { 60 t.Run("CRUD Operations", func(t *testing.T) { 61 - db := createTVTestDB(t) 62 repo := NewTVRepository(db) 63 ctx := context.Background() 64 65 t.Run("Create TV Show", func(t *testing.T) { 66 - tvShow := createSampleTVShow() 67 68 id, err := repo.Create(ctx, tvShow) 69 if err != nil { ··· 84 }) 85 86 t.Run("Get TV Show", func(t *testing.T) { 87 - original := createSampleTVShow() 88 id, err := repo.Create(ctx, original) 89 if err != nil { 90 t.Fatalf("Failed to create TV show: %v", err) ··· 116 }) 117 118 t.Run("Update TV Show", func(t *testing.T) { 119 - tvShow := createSampleTVShow() 120 id, err := repo.Create(ctx, tvShow) 121 if err != nil { 122 t.Fatalf("Failed to create TV show: %v", err) ··· 161 }) 162 163 t.Run("Delete TV Show", func(t *testing.T) { 164 - tvShow := createSampleTVShow() 165 id, err := repo.Create(ctx, tvShow) 166 if err != nil { 167 t.Fatalf("Failed to create TV show: %v", err) ··· 180 }) 181 182 t.Run("List", func(t *testing.T) { 183 - db := createTVTestDB(t) 184 repo := NewTVRepository(db) 185 ctx := context.Background() 186 ··· 307 }) 308 309 t.Run("Special Methods", func(t *testing.T) { 310 - db := createTVTestDB(t) 311 repo := NewTVRepository(db) 312 ctx := context.Background() 313 ··· 458 }) 459 460 t.Run("Count", func(t *testing.T) { 461 - db := createTVTestDB(t) 462 repo := NewTVRepository(db) 463 ctx := context.Background() 464
··· 2 3 import ( 4 "context" 5 "testing" 6 "time" 7 ··· 9 "github.com/stormlightlabs/noteleaf/internal/models" 10 ) 11 12 func TestTVRepository(t *testing.T) { 13 t.Run("CRUD Operations", func(t *testing.T) { 14 + db := CreateTestDB(t) 15 repo := NewTVRepository(db) 16 ctx := context.Background() 17 18 t.Run("Create TV Show", func(t *testing.T) { 19 + tvShow := CreateSampleTVShow() 20 21 id, err := repo.Create(ctx, tvShow) 22 if err != nil { ··· 37 }) 38 39 t.Run("Get TV Show", func(t *testing.T) { 40 + original := CreateSampleTVShow() 41 id, err := repo.Create(ctx, original) 42 if err != nil { 43 t.Fatalf("Failed to create TV show: %v", err) ··· 69 }) 70 71 t.Run("Update TV Show", func(t *testing.T) { 72 + tvShow := CreateSampleTVShow() 73 id, err := repo.Create(ctx, tvShow) 74 if err != nil { 75 t.Fatalf("Failed to create TV show: %v", err) ··· 114 }) 115 116 t.Run("Delete TV Show", func(t *testing.T) { 117 + tvShow := CreateSampleTVShow() 118 id, err := repo.Create(ctx, tvShow) 119 if err != nil { 120 t.Fatalf("Failed to create TV show: %v", err) ··· 133 }) 134 135 t.Run("List", func(t *testing.T) { 136 + db := CreateTestDB(t) 137 repo := NewTVRepository(db) 138 ctx := context.Background() 139 ··· 260 }) 261 262 t.Run("Special Methods", func(t *testing.T) { 263 + db := CreateTestDB(t) 264 repo := NewTVRepository(db) 265 ctx := context.Background() 266 ··· 411 }) 412 413 t.Run("Count", func(t *testing.T) { 414 + db := CreateTestDB(t) 415 repo := NewTVRepository(db) 416 ctx := context.Background() 417