c9ac014473
- 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.
280 lines
8.4 KiB
Go
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") }
|