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.
This commit is contained in:
@@ -0,0 +1,279 @@
|
||||
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") }
|
||||
Reference in New Issue
Block a user