package manifest import ( "fmt" "testing" jsonladapter "gitea.k3s.k0.nu/tools/photocli/internal/manifest/jsonl" sqliteadapter "gitea.k3s.k0.nu/tools/photocli/internal/manifest/sqlite" "gitea.k3s.k0.nu/tools/photocli/internal/manifest/types" ) func TestRegistryParseFormatAliases(t *testing.T) { r := Default cases := map[string]Format{ "jsonl": FormatJSONL, "json": FormatJSONL, "sqlite": FormatSQLite, "db": FormatSQLite, "sqlite3": FormatSQLite, } for input, want := range cases { got, err := r.ParseFormat(input) if err != nil { t.Fatalf("ParseFormat(%q): %v", input, err) } if got != want { t.Fatalf("ParseFormat(%q) = %q, want %q", input, got, want) } } } func TestRegistryParseFormatUnknown(t *testing.T) { if _, err := Default.ParseFormat("bad"); err == nil { t.Fatal("expected unknown format error") } } func TestRegistryOpenConvertsExistingManifest(t *testing.T) { dir := t.TempDir() m := LoadJSONL(dir) if err := m.OpenAppend(); err != nil { t.Fatal(err) } m.Add("x1", "photo.jpg", 12, "local") if err := m.Save(); err != nil { t.Fatal(err) } m.Close() converted, err := Default.Open(dir, FormatSQLite) if err != nil { t.Fatal(err) } defer converted.Close() if !converted.Has("x1") { t.Fatal("expected converted sqlite manifest to contain entry") } if FileExists(JSONLPath(dir)) { t.Fatal("expected source jsonl manifest to be removed") } if !FileExists(SQLitePath(dir)) { t.Fatal("expected sqlite manifest to exist") } } func TestMemoryAdapterStore(t *testing.T) { s := newMemoryStore() if s.Has("x") { t.Fatal("expected empty") } s.Add("x", "photo.jpg", 42, "local") if !s.Has("x") { t.Fatal("expected has after add") } s.AddEntry(Entry{ID: "y", Filename: "file.jpg"}) if e := s.Entries()["y"]; e.Path != "file.jpg" { t.Fatal("expected default path") } if err := s.Save(); err != nil { t.Fatal(err) } s.Close() if err := s.OpenAppend(); err != nil { t.Fatal(err) } adapter := MemoryAdapter{} if adapter.Format() != FormatJSONL { t.Fatal("expected JSONL format") } if len(adapter.Aliases()) != 0 { t.Fatal("expected no aliases") } if adapter.Path("") != "" { t.Fatal("expected empty path") } if adapter.Exists("") { t.Fatal("expected not to exist") } m, err := adapter.Open(t.TempDir()) if err != nil { t.Fatal(err) } m.Close() w, err := adapter.OpenLogWriter(nil, t.TempDir()) if err != nil { t.Fatal(err) } w.Close() } func TestRegistryOpenLogWriterUsesAdapter(t *testing.T) { dir := t.TempDir() m, err := Default.Open(dir, FormatJSONL) if err != nil { t.Fatal(err) } defer m.Close() w, err := Default.OpenLogWriter(m, dir, FormatJSONL) if err != nil { t.Fatal(err) } w.Close() if !FileExists(LogPath(dir)) { t.Fatal("expected jsonl adapter to create file log") } } func TestRegistryOpenCreatesRequestedAdapter(t *testing.T) { dir := t.TempDir() m, err := Default.Open(dir, FormatJSONL) if err != nil { t.Fatal(err) } defer m.Close() if _, ok := m.(*jsonladapter.Store); !ok { t.Fatalf("expected jsonl store, got %T", m) } } func TestRegistryUnknownAdapterErrors(t *testing.T) { r := NewRegistry(JSONLAdapter) if _, err := r.Open(t.TempDir(), FormatSQLite); err == nil { t.Fatal("expected open error for unregistered format") } if _, err := r.OpenLogWriter(LoadJSONL(t.TempDir()), t.TempDir(), FormatSQLite); err == nil { t.Fatal("expected log writer error for unregistered format") } } func TestSQLiteAdapterOpenLogWriterUsesSQLite(t *testing.T) { dir := t.TempDir() m, err := LoadSQLite(dir) if err != nil { t.Fatal(err) } if err := m.OpenAppend(); err != nil { t.Fatal(err) } defer m.Close() w, err := SQLiteAdapter.OpenLogWriter(m, dir) if err != nil { t.Fatal(err) } defer w.Close() if _, ok := w.(*sqliteadapter.LogWriter); !ok { t.Fatalf("expected sqlite log writer, got %T", w) } } func TestOpenLogWriterUsesManifestFormat(t *testing.T) { dir := t.TempDir() m, err := LoadSQLite(dir) if err != nil { t.Fatal(err) } if err := m.OpenAppend(); err != nil { t.Fatal(err) } defer m.Close() w, err := OpenLogWriter(m, dir) if err != nil { t.Fatal(err) } defer w.Close() if _, ok := w.(*sqliteadapter.LogWriter); !ok { t.Fatalf("expected sqlite log writer, got %T", w) } } func TestSQLiteAdapterOpenLogWriterFallsBackToFile(t *testing.T) { dir := t.TempDir() w, err := SQLiteAdapter.OpenLogWriter(LoadJSONL(dir), dir) if err != nil { t.Fatal(err) } w.Close() if !FileExists(LogPath(dir)) { t.Fatal("expected sqlite adapter fallback to create file log") } } func TestSetJSONLSaveHookWrapper(t *testing.T) { old := SetJSONLSaveHook(func() error { return nil }) SetJSONLSaveHook(old) } func TestConvertManifestErrorBranches(t *testing.T) { dir := t.TempDir() if _, err := ConvertManifest(dir, failingAdapter{format: FormatJSONL, openErr: fmt.Errorf("boom")}, JSONLAdapter); err == nil { t.Fatal("expected source open error") } if _, err := ConvertManifest(dir, staticAdapter{format: FormatJSONL, store: badOpenManifest{}}, JSONLAdapter); err == nil { t.Fatal("expected source OpenAppend error") } if _, err := ConvertManifest(dir, staticAdapter{format: FormatJSONL, store: noReadManifest{}}, JSONLAdapter); err == nil { t.Fatal("expected entry reader error") } if _, err := ConvertManifest(dir, staticAdapter{format: FormatJSONL, store: readableManifest{}}, failingAdapter{format: FormatSQLite, openErr: fmt.Errorf("boom")}); err == nil { t.Fatal("expected destination open error") } if _, err := ConvertManifest(dir, staticAdapter{format: FormatJSONL, store: readableManifest{}}, staticAdapter{format: FormatSQLite, store: badOpenManifest{}}); err == nil { t.Fatal("expected destination OpenAppend error") } oldRemove := types.RemoveFunc() types.SetRemoveFunc(func(string) error { return fmt.Errorf("remove failed") }) defer func() { types.SetRemoveFunc(oldRemove) }() if _, err := ConvertManifest(dir, staticAdapter{format: FormatJSONL, store: readableManifest{}}, staticAdapter{format: FormatSQLite, store: readableManifest{}}); err == nil { t.Fatal("expected remove error") } } type staticAdapter struct { format Format store Manifest } func (a staticAdapter) Format() Format { return a.format } func (a staticAdapter) Aliases() []string { return nil } func (a staticAdapter) Path(string) string { return "manifest.file" } func (a staticAdapter) Exists(string) bool { return true } func (a staticAdapter) Open(string) (Manifest, error) { return a.store, nil } func (a staticAdapter) OpenLogWriter(Manifest, string) (LogWriter, error) { return NoopLogWriter, nil } type failingAdapter struct { format Format openErr error } func (a failingAdapter) Format() Format { return a.format } func (a failingAdapter) Aliases() []string { return nil } func (a failingAdapter) Path(string) string { return "manifest.file" } func (a failingAdapter) Exists(string) bool { return true } func (a failingAdapter) Open(string) (Manifest, error) { return nil, a.openErr } func (a failingAdapter) OpenLogWriter(Manifest, string) (LogWriter, error) { return nil, a.openErr } type readableManifest struct{} func (readableManifest) Has(string) bool { return false } func (readableManifest) Add(string, string, int64, string) {} func (readableManifest) AddEntry(Entry) {} func (readableManifest) Save() error { return nil } func (readableManifest) Close() {} func (readableManifest) OpenAppend() error { return nil } func (readableManifest) Entries() map[string]Entry { return map[string]Entry{"x": {Filename: "x.jpg"}} } type noReadManifest struct{} func (noReadManifest) Has(string) bool { return false } func (noReadManifest) Add(string, string, int64, string) {} func (noReadManifest) AddEntry(Entry) {} func (noReadManifest) Save() error { return nil } func (noReadManifest) Close() {} func (noReadManifest) OpenAppend() error { return nil } func (noReadManifest) Entries() map[string]Entry { return nil } type badOpenManifest struct{ readableManifest } func (badOpenManifest) OpenAppend() error { return fmt.Errorf("open failed") }