package library import ( "context" "os" "path/filepath" "testing" ) func TestScanLifecycle(t *testing.T) { db := testDB(t) ctx := context.Background() root := t.TempDir() writeFile(t, filepath.Join(root, "a.epub"), "aaaaaaaaaa") writeFile(t, filepath.Join(root, "b.epub"), "bbbbbbbbbbbbbbbbbbbb") writeFile(t, filepath.Join(root, "sub", "c.epub"), "cccccccccccccccccccccccccccccc") writeFile(t, filepath.Join(root, "notes.txt"), "not media") writeFile(t, filepath.Join(root, ".hidden.epub"), "hidden") writeFile(t, filepath.Join(root, ".git", "d.epub"), "in a hidden dir") lib, err := Create(ctx, db, "Ebooks", "ebooks", root) if err != nil { t.Fatalf("Create: %v", err) } // First scan: three supported files, nothing else. res, err := Scan(ctx, db, lib, testLogger()) if err != nil { t.Fatalf("Scan: %v", err) } if res.Added != 3 || res.Scanned != 3 { t.Fatalf("first scan added=%d scanned=%d, want 3/3 (%+v)", res.Added, res.Scanned, res) } if n, _ := CountItems(ctx, db, lib.ID); n != 3 { t.Errorf("item count = %d, want 3", n) } // Second scan: nothing changed. res, err = Scan(ctx, db, lib, testLogger()) if err != nil { t.Fatal(err) } if res.Unchanged != 3 || res.Added != 0 || res.Updated != 0 { t.Errorf("second scan unchanged=%d added=%d updated=%d, want 3/0/0", res.Unchanged, res.Added, res.Updated) } // Modify a.epub (different size). writeFile(t, filepath.Join(root, "a.epub"), "aaaaaaaaaaaaaaaaaaaaaaaaaaaa") res, err = Scan(ctx, db, lib, testLogger()) if err != nil { t.Fatal(err) } if res.Updated != 1 || res.Unchanged != 2 { t.Errorf("after modify updated=%d unchanged=%d, want 1/2", res.Updated, res.Unchanged) } // Delete b.epub: it goes missing, it is not deleted. if err := os.Remove(filepath.Join(root, "b.epub")); err != nil { t.Fatal(err) } res, err = Scan(ctx, db, lib, testLogger()) if err != nil { t.Fatal(err) } if res.Missing != 1 { t.Errorf("after delete missing=%d, want 1", res.Missing) } if n, _ := CountItems(ctx, db, lib.ID); n != 2 { t.Errorf("item count (excluding missing) = %d, want 2", n) } // The row still exists, flagged missing. var missing int if err := db.QueryRowContext(ctx, `SELECT missing FROM media_items WHERE library_id = ? AND path = ?`, lib.ID, "b.epub").Scan(&missing); err != nil { t.Fatalf("missing item row was deleted: %v", err) } if missing != 1 { t.Errorf("missing flag = %d, want 1", missing) } // Restore b.epub. writeFile(t, filepath.Join(root, "b.epub"), "bbbbbbbbbbbbbbbbbbbb") res, err = Scan(ctx, db, lib, testLogger()) if err != nil { t.Fatal(err) } if res.Restored != 1 { t.Errorf("after restore restored=%d, want 1 (%+v)", res.Restored, res) } if n, _ := CountItems(ctx, db, lib.ID); n != 3 { t.Errorf("item count after restore = %d, want 3", n) } } func TestScanUnreadableRoot(t *testing.T) { db := testDB(t) ctx := context.Background() root := t.TempDir() writeFile(t, filepath.Join(root, "a.epub"), "x") lib, err := Create(ctx, db, "Ebooks", "ebooks", root) if err != nil { t.Fatal(err) } // Index it once so there is a known file to go missing. if _, err := Scan(ctx, db, lib, testLogger()); err != nil { t.Fatal(err) } // Remove the root out from under the library. if err := os.RemoveAll(root); err != nil { t.Fatal(err) } res, err := Scan(ctx, db, lib, testLogger()) if err != nil { t.Fatalf("scan should not fail on a missing root: %v", err) } if res.Added != 0 { t.Errorf("added=%d, want 0", res.Added) } if res.Missing != 1 { t.Errorf("missing=%d, want 1 (existing item now vanished)", res.Missing) } if len(res.Errors) == 0 { t.Error("expected the walk error to be recorded") } }