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