package main // store_test.go — persistence: events replay, sessions, settings, containers. import ( "path/filepath" "testing" ) func openTestStore(t *testing.T) *Store { t.Helper() store, err := OpenStore(filepath.Join(t.TempDir(), "lvmh.db")) if err != nil { t.Fatalf("open store: %v", err) } t.Cleanup(func() { _ = store.Close() }) return store } func TestStoreEventPersistAndReplayAfterSeq(t *testing.T) { store := openTestStore(t) events := []Event{ {SessionID: "s1", Seq: 1, TS: 1000, Type: evMessageStart, Payload: []byte(`{"message":{"role":"user","id":"m1"}}`)}, {SessionID: "s1", Seq: 2, TS: 1001, Type: evMessageEnd, Payload: []byte(`{"message":{"role":"user","id":"m1"}}`)}, {SessionID: "s1", Seq: 3, TS: 1002, Type: evAgentSettled, Payload: []byte(`{}`)}, } for _, e := range events { if err := store.AppendEvent(e); err != nil { t.Fatalf("append seq %d: %v", e.Seq, err) } } if last, _ := store.LastSeq("s1"); last != 3 { t.Fatalf("lastSeq = %d, want 3", last) } all, err := store.EventsAfter("s1", 0, 100) if err != nil { t.Fatalf("events after 0: %v", err) } if len(all) != 3 { t.Fatalf("after=0 returned %d events, want 3", len(all)) } for i, e := range all { if e.Seq != int64(i+1) { t.Fatalf("event %d has seq %d, want ascending from 1", i, e.Seq) } } tail, err := store.EventsAfter("s1", 2, 100) if err != nil { t.Fatalf("events after 2: %v", err) } if len(tail) != 1 || tail[0].Seq != 3 { t.Fatalf("after=2 returned %v, want only seq 3", tail) } limited, _ := store.EventsAfter("s1", 0, 2) if len(limited) != 2 { t.Fatalf("limit=2 returned %d events", len(limited)) } // Duplicate (sessionId, seq) is ignored — replay idempotence. if err := store.AppendEvent(events[0]); err != nil { t.Fatalf("duplicate append: %v", err) } dup, _ := store.EventsAfter("s1", 0, 100) if len(dup) != 3 { t.Fatalf("duplicate append created extra row: %d", len(dup)) } } func TestStoreSessionsOnlineAndTouch(t *testing.T) { store := openTestStore(t) name := "my session" info := SessionInfo{ID: "s1", Name: &name, Cwd: "/w", Model: "glm-5.3", Provider: "zai-renaud", Agent: true, Repo: nil, StartedAt: 42} if err := store.UpsertSession(info); err != nil { t.Fatalf("upsert: %v", err) } if err := store.TouchSession("s1", 7, 4242); err != nil { t.Fatalf("touch: %v", err) } if err := store.TouchSession("s1", 3, 9999); err != nil { // lastSeq monotonic t.Fatalf("touch lower: %v", err) } if err := store.SetOnline("s1", true); err != nil { t.Fatalf("online: %v", err) } rows, err := store.Sessions() if err != nil || len(rows) != 1 { t.Fatalf("sessions: %v %d", err, len(rows)) } row := rows[0] if row.LastSeq != 7 || row.LastEventAt != 9999 || !row.OnlineDB { t.Fatalf("row = %+v, want lastSeq 7, lastEventAt 9999, online", row) } if row.Info.Name == nil || *row.Info.Name != name { t.Fatalf("name not roundtripped: %+v", row.Info) } if last, _ := store.LastSeq("nope"); last != 0 { t.Fatalf("missing session lastSeq = %d, want 0", last) } } func TestStoreSettingsAndContainers(t *testing.T) { store := openTestStore(t) if _, ok, _ := store.GetSetting(settingGitLabToken); ok { t.Fatal("unset setting should not exist") } if err := store.SetSetting(settingGitLabToken, "pat"); err != nil { t.Fatalf("set: %v", err) } if v, ok, _ := store.GetSetting(settingGitLabToken); !ok || v != "pat" { t.Fatalf("get = %q %v", v, ok) } if err := store.DeleteSetting(settingGitLabToken); err != nil { t.Fatalf("delete: %v", err) } if err := store.UpsertContainer("s1", "cid1", "group/proj"); err != nil { t.Fatalf("upsert container: %v", err) } row, ok, err := store.GetContainer("s1") if err != nil || !ok || row.ContainerID != "cid1" || row.Repo != "group/proj" { t.Fatalf("get container = %+v %v %v", row, ok, err) } if err := store.DeleteContainer("s1"); err != nil { t.Fatalf("delete container: %v", err) } if _, ok, _ := store.GetContainer("s1"); ok { t.Fatal("container row should be gone") } }