package storage import ( "path/filepath" "testing" "time" "donniemarko/internal/note" ) func newSQLiteStorage(t *testing.T) *SQLiteStorage { t.Helper() dbPath := filepath.Join(t.TempDir(), "notes.db") st, err := NewSQLiteStorage(dbPath) if err != nil { t.Fatalf("new sqlite storage: %v", err) } t.Cleanup(func() { _ = st.Close() }) return st } func sampleNote(id, path, title, content string, tstamp time.Time) *note.Note { return ¬e.Note{ ID: id, Path: path, Title: title, Content: content, UpdatedAt: tstamp.Add(2 * time.Hour), Size: int64(len(content)), Published: true, } } func TestSQLiteStorage_CreateGet(t *testing.T) { st := newSQLiteStorage(t) ts := time.Date(2026, 2, 3, 12, 0, 0, 0, time.UTC) n := sampleNote("n1", "notes/alpha.md", "Alpha", "# Alpha", ts) if err := st.Create(n); err != nil { t.Fatalf("create note: %v", err) } got, err := st.Get("n1") if err != nil { t.Fatalf("get note: %v", err) } if got.Title != n.Title || got.Path != n.Path || got.Content != n.Content { t.Fatalf("unexpected note fields: %+v", got) } if !got.UpdatedAt.Equal(n.UpdatedAt) || got.Size != n.Size { t.Fatalf("unexpected time fields: %+v", got) } if !got.Published { t.Fatalf("expected published to be true") } } func TestSQLiteStorage_GetAll(t *testing.T) { st := newSQLiteStorage(t) ts := time.Date(2026, 2, 3, 12, 0, 0, 0, time.UTC) if err := st.Create(sampleNote("n1", "notes/alpha.md", "Alpha", "one", ts)); err != nil { t.Fatalf("create note: %v", err) } if err := st.Create(sampleNote("n2", "notes/beta.md", "Beta", "two", ts)); err != nil { t.Fatalf("create note: %v", err) } all := st.GetAll() if len(all) != 2 { t.Fatalf("expected 2 notes, got %d", len(all)) } } func TestSQLiteStorage_Update(t *testing.T) { st := newSQLiteStorage(t) ts := time.Date(2026, 2, 3, 12, 0, 0, 0, time.UTC) n := sampleNote("n1", "notes/alpha.md", "Alpha", "one", ts) if err := st.Create(n); err != nil { t.Fatalf("create note: %v", err) } updated := sampleNote("n1", "notes/alpha.md", "Alpha Updated", "two", ts.Add(24*time.Hour)) st.Update("n1", updated) got, err := st.Get("n1") if err != nil { t.Fatalf("get note: %v", err) } if got.Title != "Alpha Updated" || got.Content != "two" { t.Fatalf("update did not persist: %+v", got) } } func TestSQLiteStorage_Delete(t *testing.T) { st := newSQLiteStorage(t) ts := time.Date(2026, 2, 3, 12, 0, 0, 0, time.UTC) if err := st.Create(sampleNote("n1", "notes/alpha.md", "Alpha", "one", ts)); err != nil { t.Fatalf("create note: %v", err) } st.Delete("n1") if st.Count() != 0 { t.Fatalf("expected count 0 after delete") } if _, err := st.Get("n1"); err == nil { t.Fatalf("expected error for missing note") } } func TestSQLiteStorage_DeleteByPath(t *testing.T) { st := newSQLiteStorage(t) ts := time.Date(2026, 2, 3, 12, 0, 0, 0, time.UTC) n := sampleNote("n1", "notes/alpha.md", "Alpha", "one", ts) if err := st.Create(n); err != nil { t.Fatalf("create note: %v", err) } st.DeleteByPath("notes/alpha.md") if st.Count() != 0 { t.Fatalf("expected count 0 after delete by path") } } func TestSQLiteStorage_Search(t *testing.T) { st := newSQLiteStorage(t) ts := time.Date(2026, 2, 3, 12, 0, 0, 0, time.UTC) if err := st.Create(sampleNote("n1", "notes/alpha.md", "Alpha", "Rust tips", ts)); err != nil { t.Fatalf("create note: %v", err) } if err := st.Create(sampleNote("n2", "notes/beta.md", "Beta", "Golang tutorial", ts)); err != nil { t.Fatalf("create note: %v", err) } results := st.Search("rust") if len(results) != 1 { t.Fatalf("expected 1 result, got %d", len(results)) } if results[0].ID != "n1" { t.Fatalf("expected rust match to be n1") } } func TestSQLiteStorage_Count(t *testing.T) { st := newSQLiteStorage(t) if st.Count() != 0 { t.Fatalf("expected empty count to be 0") } ts := time.Date(2026, 2, 3, 12, 0, 0, 0, time.UTC) if err := st.Create(sampleNote("n1", "notes/alpha.md", "Alpha", "one", ts)); err != nil { t.Fatalf("create note: %v", err) } if err := st.Create(sampleNote("n2", "notes/beta.md", "Beta", "two", ts)); err != nil { t.Fatalf("create note: %v", err) } if st.Count() != 2 { t.Fatalf("expected count 2") } } func TestSQLiteStorage_Tags(t *testing.T) { st := newSQLiteStorage(t) ts := time.Date(2026, 2, 3, 12, 0, 0, 0, time.UTC) if err := st.Create(sampleNote("n1", "notes/alpha.md", "Alpha", "one", ts)); err != nil { t.Fatalf("create note: %v", err) } if err := st.AddTag("n1", "go"); err != nil { t.Fatalf("add tag: %v", err) } if err := st.AddTag("n1", "rust"); err != nil { t.Fatalf("add tag: %v", err) } tags := st.GetTags("n1") if len(tags) != 2 { t.Fatalf("expected 2 tags, got %d", len(tags)) } if err := st.RemoveTag("n1", "go"); err != nil { t.Fatalf("remove tag: %v", err) } tags = st.GetTags("n1") if len(tags) != 1 || tags[0] != "rust" { t.Fatalf("expected remaining tag rust, got %+v", tags) } } func TestSQLiteStorage_SearchByTag(t *testing.T) { st := newSQLiteStorage(t) ts := time.Date(2026, 2, 3, 12, 0, 0, 0, time.UTC) if err := st.Create(sampleNote("n1", "notes/alpha.md", "Alpha", "no match", ts)); err != nil { t.Fatalf("create note: %v", err) } if err := st.Create(sampleNote("n2", "notes/beta.md", "Beta", "content", ts)); err != nil { t.Fatalf("create note: %v", err) } if err := st.AddTag("n2", "Go"); err != nil { t.Fatalf("add tag: %v", err) } results := st.Search("go") if len(results) != 1 { t.Fatalf("expected 1 result, got %d", len(results)) } if results[0].ID != "n2" { t.Fatalf("expected tag match to be n2") } } func TestSQLiteStorage_Create_Upsert(t *testing.T) { st := newSQLiteStorage(t) ts := time.Date(2026, 2, 3, 12, 0, 0, 0, time.UTC) n := sampleNote("n1", "notes/alpha.md", "Alpha", "one", ts) if err := st.Create(n); err != nil { t.Fatalf("create note: %v", err) } n.Content = "updated" n.Title = "Alpha Updated" n.UpdatedAt = ts.Add(4 * time.Hour) if err := st.Create(n); err != nil { t.Fatalf("upsert note: %v", err) } got, err := st.Get("n1") if err != nil { t.Fatalf("get note: %v", err) } if got.Title != "Alpha Updated" || got.Content != "updated" { t.Fatalf("expected note to be updated, got %+v", got) } } func TestSQLiteStorage_Create_UpsertSkipsOlder(t *testing.T) { st := newSQLiteStorage(t) ts := time.Date(2026, 2, 3, 12, 0, 0, 0, time.UTC) n := sampleNote("n1", "notes/alpha.md", "Alpha", "one", ts) if err := st.Create(n); err != nil { t.Fatalf("create note: %v", err) } n.Content = "older" n.UpdatedAt = ts.Add(-2 * time.Hour) if err := st.Create(n); err != nil { t.Fatalf("upsert note: %v", err) } got, err := st.Get("n1") if err != nil { t.Fatalf("get note: %v", err) } if got.Content != "one" { t.Fatalf("expected existing content to remain, got %q", got.Content) } }