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.
324 lines
7.0 KiB
Go
324 lines
7.0 KiB
Go
package sqlite
|
|
|
|
import (
|
|
"database/sql"
|
|
"fmt"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
_ "modernc.org/sqlite"
|
|
|
|
"gitea.k3s.k0.nu/tools/photocli/internal/manifest/types"
|
|
)
|
|
|
|
func TestStoreOpenAppendSQLError(t *testing.T) {
|
|
dir := t.TempDir()
|
|
m, err := Load(dir)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
m.SetOpen(func(driverName, dataSourceName string) (*sql.DB, error) {
|
|
return nil, fmt.Errorf("simulated open error")
|
|
})
|
|
if err := m.OpenAppend(); err == nil {
|
|
t.Error("expected error from sql.Open failure")
|
|
}
|
|
}
|
|
|
|
func TestStoreOpenAppendCreateTableError(t *testing.T) {
|
|
dir := t.TempDir()
|
|
m, err := Load(dir)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
realOpen := m.open
|
|
m.SetOpen(func(driverName, dataSourceName string) (*sql.DB, error) {
|
|
db, err := realOpen(driverName, dataSourceName)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
db.Close()
|
|
return db, nil
|
|
})
|
|
if err := m.OpenAppend(); err == nil {
|
|
t.Error("expected error from closed DB CREATE TABLE")
|
|
}
|
|
}
|
|
|
|
func TestStoreHasAfterClose(t *testing.T) {
|
|
dir := t.TempDir()
|
|
m, err := Load(dir)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := m.OpenAppend(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
m.Add("x1", "photo.jpg", 1024, "local")
|
|
m.Close()
|
|
if m.Has("x1") {
|
|
t.Error("Has should return false after Close")
|
|
}
|
|
}
|
|
|
|
func TestStoreEntriesAfterClose(t *testing.T) {
|
|
dir := t.TempDir()
|
|
m, err := Load(dir)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := m.OpenAppend(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
m.Add("x1", "photo.jpg", 1024, "local")
|
|
m.Close()
|
|
entries := m.Entries()
|
|
if entries != nil {
|
|
t.Errorf("Entries should return nil after Close, got %d entries", len(entries))
|
|
}
|
|
}
|
|
|
|
func TestStoreOpenAppendMkdirAllError(t *testing.T) {
|
|
m, err := Load("/proc/cannot-write")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := m.OpenAppend(); err == nil {
|
|
t.Error("expected error creating dir under /proc")
|
|
m.Close()
|
|
}
|
|
}
|
|
|
|
func TestStoreOpenAppendNilOpener(t *testing.T) {
|
|
dir := t.TempDir()
|
|
m, err := Load(dir)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
m.open = nil
|
|
if err := m.OpenAppend(); err != nil {
|
|
t.Errorf("expected nil opener to use sql.Open fallback, got err: %v", err)
|
|
}
|
|
m.Close()
|
|
}
|
|
|
|
func TestStoreOpenAppendCreateIndexError(t *testing.T) {
|
|
dir := t.TempDir()
|
|
m, err := Load(dir)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
realOpen := m.open
|
|
m.open = func(driverName, dataSourceName string) (*sql.DB, error) {
|
|
db, err := realOpen(driverName, dataSourceName)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
db.Close()
|
|
return db, nil
|
|
}
|
|
if err := m.OpenAppend(); err == nil {
|
|
t.Error("expected error from closed DB")
|
|
}
|
|
}
|
|
|
|
func TestStoreHasQueryError(t *testing.T) {
|
|
dir := t.TempDir()
|
|
m, err := Load(dir)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := m.OpenAppend(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
m.Add("x1", "photo.jpg", 1024, "local")
|
|
closedDB := m.DB()
|
|
closedDB.Close()
|
|
if m.Has("x1") {
|
|
t.Error("Has should return false with broken DB connection")
|
|
}
|
|
}
|
|
|
|
func TestStoreEntriesQueryError(t *testing.T) {
|
|
dir := t.TempDir()
|
|
m, err := Load(dir)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := m.OpenAppend(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
m.Add("x1", "photo.jpg", 1024, "local")
|
|
closedDB := m.DB()
|
|
closedDB.Close()
|
|
entries := m.Entries()
|
|
if entries == nil {
|
|
t.Error("Entries should return non-nil map with broken DB connection")
|
|
}
|
|
if len(entries) != 0 {
|
|
t.Errorf("Entries should be empty with broken DB connection, got %d", len(entries))
|
|
}
|
|
}
|
|
|
|
func TestStoreHasQueryErrorWithOpenDB(t *testing.T) {
|
|
dir := t.TempDir()
|
|
m, err := Load(dir)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := m.OpenAppend(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
m.Add("x1", "photo.jpg", 1024, "local")
|
|
closedDB := m.DB()
|
|
closedDB.Close()
|
|
if m.Has("x1") {
|
|
t.Error("Has should return false with closed DB")
|
|
}
|
|
}
|
|
|
|
func TestStoreEntriesQueryErrorWithOpenDB(t *testing.T) {
|
|
dir := t.TempDir()
|
|
m, err := Load(dir)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := m.OpenAppend(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
m.Add("x1", "photo.jpg", 1024, "local")
|
|
closedDB := m.DB()
|
|
closedDB.Close()
|
|
entries := m.Entries()
|
|
if entries != nil && len(entries) != 0 {
|
|
t.Errorf("Entries should be empty with closed DB, got %d", len(entries))
|
|
}
|
|
}
|
|
|
|
func TestStoreCreateIndexError(t *testing.T) {
|
|
dir := t.TempDir()
|
|
m, err := Load(dir)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
realOpen := m.open
|
|
m.open = func(driverName, dataSourceName string) (*sql.DB, error) {
|
|
db, err := realOpen(driverName, dataSourceName)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
m.SetExecFunc(func(query string, args ...any) (sql.Result, error) {
|
|
if strings.Contains(query, "CREATE INDEX") {
|
|
return nil, fmt.Errorf("injected CREATE INDEX error")
|
|
}
|
|
return db.Exec(query, args...)
|
|
})
|
|
return db, nil
|
|
}
|
|
if err := m.OpenAppend(); err == nil {
|
|
t.Error("expected error from CREATE INDEX")
|
|
m.Close()
|
|
}
|
|
}
|
|
|
|
func TestAddEntryDefaultsPath(t *testing.T) {
|
|
dir := t.TempDir()
|
|
m, err := Load(dir)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := m.OpenAppend(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
m.AddEntry(types.Entry{ID: "x1", Filename: "file.jpg", Size: 3, Cloud: "local", Checksum: "sha256:def"})
|
|
if _, err := m.DB().Exec(`UPDATE downloads SET path = '' WHERE id = 'x1'`); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
sloaded := m.Entries()["x1"]
|
|
if got := sloaded.Path; got != "file.jpg" {
|
|
t.Fatalf("sqlite path = %q", got)
|
|
}
|
|
if sloaded.Checksum != "sha256:def" {
|
|
t.Fatalf("sqlite checksum = %q", sloaded.Checksum)
|
|
}
|
|
m.Close()
|
|
}
|
|
|
|
func TestNewLogWriter(t *testing.T) {
|
|
dir := t.TempDir()
|
|
dbPath := filepath.Join(dir, "test.db")
|
|
db, err := sql.Open("sqlite", dbPath)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer db.Close()
|
|
|
|
lw, err := NewLogWriter(db)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
lw.Log(types.LogEntry{
|
|
Timestamp: 1700000000,
|
|
Level: "info",
|
|
Event: "export_done",
|
|
AssetID: "asset-1",
|
|
Album: "Favorites",
|
|
Filename: "photo.jpg",
|
|
Size: 1024,
|
|
Cloud: "local",
|
|
DurationMs: 500,
|
|
})
|
|
lw.Log(types.LogEntry{
|
|
Timestamp: 1700000001,
|
|
Level: "error",
|
|
Event: "export_fail",
|
|
AssetID: "asset-2",
|
|
Filename: "bad.jpg",
|
|
Message: "timeout",
|
|
})
|
|
lw.Close()
|
|
|
|
var count int
|
|
err = db.QueryRow(`SELECT COUNT(*) FROM logs`).Scan(&count)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if count != 2 {
|
|
t.Errorf("expected 2 log entries, got %d", count)
|
|
}
|
|
|
|
var event, level, assetID string
|
|
err = db.QueryRow(`SELECT event, level, asset_id FROM logs WHERE asset_id = 'asset-1'`).Scan(&event, &level, &assetID)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if event != "export_done" || level != "info" || assetID != "asset-1" {
|
|
t.Errorf("unexpected row: event=%s level=%s asset_id=%s", event, level, assetID)
|
|
}
|
|
}
|
|
|
|
func TestLogWriterNilDB(t *testing.T) {
|
|
w := &LogWriter{db: nil}
|
|
w.Log(types.LogEntry{Event: "test"})
|
|
w.Close()
|
|
}
|
|
|
|
func TestNewLogWriterError(t *testing.T) {
|
|
db, err := sql.Open("sqlite", filepath.Join(t.TempDir(), "test.db"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := db.Close(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := NewLogWriter(db); err == nil {
|
|
t.Error("expected error for closed db")
|
|
}
|
|
}
|
|
|
|
func TestLogWriterCloseConcrete(t *testing.T) {
|
|
(&LogWriter{}).Close()
|
|
}
|