package manifest import ( "database/sql" "os" "path/filepath" "testing" _ "modernc.org/sqlite" ) func TestNoopLogWriter(t *testing.T) { lw := NoopLogWriter lw.Log(LogEntry{Event: "test"}) lw.Close() noopLogWriter{}.Log(LogEntry{Event: "test"}) noopLogWriter{}.Close() } func TestNewSQLiteLogWriter(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 := NewSQLiteLogWriter(db) if err != nil { t.Fatal(err) } lw.Log(LogEntry{ Timestamp: 1700000000, Level: "info", Event: "export_done", AssetID: "asset-1", Album: "Favorites", Filename: "photo.jpg", Size: 1024, Cloud: "local", DurationMs: 500, Message: "", }) lw.Log(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 TestSQLiteLogWriterNilDB(t *testing.T) { w := &sqliteLogWriter{db: nil} w.Log(LogEntry{Event: "test"}) w.Close() } func TestNewSQLiteLogWriterError(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 := NewSQLiteLogWriter(db); err == nil { t.Error("expected error for closed db") } } func TestSQLiteLogWriterCloseConcrete(t *testing.T) { (&sqliteLogWriter{}).Close() } func TestNewFileLogWriter(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "export.log") lw, err := NewFileLogWriter(path) if err != nil { t.Fatal(err) } lw.Log(LogEntry{ Timestamp: 1700000000, Level: "info", Event: "export_done", Filename: "photo.jpg", Size: 2048, }) lw.Close() data, err := os.ReadFile(path) if err != nil { t.Fatal(err) } if len(data) == 0 { t.Error("expected log data") } if data[len(data)-1] != '\n' { t.Error("expected trailing newline") } } func TestFileLogWriterClosed(t *testing.T) { w := &fileLogWriter{f: nil} w.Log(LogEntry{Event: "test"}) w.Close() } func TestNewFileLogWriterError(t *testing.T) { _, err := NewFileLogWriter("/nonexistent/dir/export.log") if err == nil { t.Error("expected error for bad path") } } func TestFileLogWriterDoubleClose(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "export.log") lw, err := NewFileLogWriter(path) if err != nil { t.Fatal(err) } lw.Close() lw.Close() } func TestLogPath(t *testing.T) { p := LogPath("/tmp/out") if p != "/tmp/out/export.log" { t.Errorf("expected /tmp/out/export.log, got %s", p) } } func TestOpenLogWriterSQLite(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() lw, err := OpenLogWriter(m, dir) if err != nil { t.Fatal(err) } lw.Log(LogEntry{Event: "test", Level: "info"}) lw.Close() } func TestOpenLogWriterJSONL(t *testing.T) { dir := t.TempDir() m := LoadJSONL(dir) if err := m.OpenAppend(); err != nil { t.Fatal(err) } defer m.Close() lw, err := OpenLogWriter(m, dir) if err != nil { t.Fatal(err) } lw.Log(LogEntry{Event: "test", Level: "info"}) lw.Close() if _, err := os.Stat(LogPath(dir)); os.IsNotExist(err) { t.Error("expected export.log to exist") } } func TestOpenLogWriterNilManifest(t *testing.T) { dir := t.TempDir() lw, err := OpenLogWriter(nil, dir) if err != nil { t.Fatal(err) } lw.Log(LogEntry{Event: "test", Level: "info"}) lw.Close() if _, err := os.Stat(LogPath(dir)); os.IsNotExist(err) { t.Error("expected export.log to exist for nil manifest") } }