Files
Ein Anderssono c9ac014473
pipeline / test (push) Has been cancelled
pipeline / build (push) Has been cancelled
v0.10.0: ports and adapters refactor
- Extract shared manifest types into internal/manifest/types leaf package.
- Extract SQLite adapter into internal/manifest/sqlite.
- Extract JSONL adapter into internal/manifest/jsonl.
- Isolate modernc.org/sqlite import to sqlite/adapter.go.
- Add adapter-backed registry with manifest.Default.
- Adapter-agnostic ConvertManifest in types/.
- MemoryAdapter for in-memory manifest testing.
- CLI uses manifest.Default registry directly.
- SQLite LogWriter type assertion moved into SQLiteAdapter.
- Manifest interface includes Entries(); EntryReader removed.
- No behavior changes. 100% coverage across all 6 packages.
2026-06-15 08:27:38 +02:00

280 lines
8.4 KiB
Go

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") }