ci / go (push) Waiting to run
ci / go-db (agent) (push) Waiting to run
ci / go-db (config) (push) Waiting to run
ci / go-db (db) (push) Waiting to run
ci / go-db (evidence) (push) Waiting to run
ci / go-db (llmrec) (push) Waiting to run
ci / go-db (server) (push) Waiting to run
web / web (push) Waiting to run
docs / links (push) Canceled after 0s
detections / detections (push) Canceled after 0s
179 lines
5.3 KiB
Go
179 lines
5.3 KiB
Go
package db
|
|
|
|
import "testing"
|
|
|
|
// TestActivityPageSessions covers the reverse-paginated, per-session history added
|
|
// for the SSE remediation: Main/Plan/Worker filtering, before-cursor paging without
|
|
// gaps/overlap, hasMore, and the task-level snapshot cursor. Mirrors docs §11.1.
|
|
func TestActivityPageSessions(t *testing.T) {
|
|
d, err := Open(testDSN(t))
|
|
if err != nil {
|
|
t.Skipf("postgres unavailable (%v) — skipping", err)
|
|
}
|
|
defer d.Close()
|
|
|
|
expID, err := d.CreateExploration("test", "分页历史")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer d.Exec(`DELETE FROM explorations WHERE id=$1`, expID)
|
|
es := d.Exploration(expID)
|
|
|
|
intentA, err := es.AddIntent(map[string]any{"summary": "intent A"}, 5, nil, "planner")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
intentB, err := es.AddIntent(map[string]any{"summary": "intent B"}, 5, nil, "planner")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Interleave a mix of agents so a session filter must actually discriminate.
|
|
// 25 main, 25 planner (Goal+Planner share worker=planner), 30 workerA, 5 workerB.
|
|
appendN := func(n int, a Activity) {
|
|
for range n {
|
|
if _, err := es.AppendActivity(a); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
}
|
|
// Interleaving order matters: emit round-robin-ish so ids of one session are
|
|
// scattered, proving the WHERE filter (not a contiguous range) is what selects.
|
|
for range 25 {
|
|
appendN(1, Activity{Worker: "mainagent", Kind: "text", Summary: "m"})
|
|
appendN(1, Activity{Worker: "planner", Kind: "text", Summary: "p"})
|
|
appendN(1, Activity{NodeID: &intentA, Worker: "work#1", Kind: "text", Summary: "a"})
|
|
}
|
|
appendN(5, Activity{NodeID: &intentA, Worker: "work#1", Kind: "text", Summary: "a2"}) // workerA → 30 total
|
|
appendN(5, Activity{NodeID: &intentB, Worker: "work#2", Kind: "text", Summary: "b"})
|
|
|
|
// snapshot cursor = max id across the whole task.
|
|
snap, err := es.ActivityMaxID()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Helper: page through a whole session backward and assert coverage.
|
|
collect := func(f ActivitySessionFilter, pageSize int) []Activity {
|
|
var all []Activity
|
|
before := int64(0)
|
|
seen := map[int64]bool{}
|
|
for {
|
|
items, hasMore, err := es.ActivityPage(f, before, pageSize)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// ascending order within a page
|
|
for i := 1; i < len(items); i++ {
|
|
if items[i-1].ID >= items[i].ID {
|
|
t.Fatalf("page not ascending: %d >= %d", items[i-1].ID, items[i].ID)
|
|
}
|
|
}
|
|
// no overlap across pages
|
|
for _, a := range items {
|
|
if seen[a.ID] {
|
|
t.Fatalf("duplicate id %d across pages", a.ID)
|
|
}
|
|
seen[a.ID] = true
|
|
}
|
|
all = append([]Activity{}, append(items, all...)...) // prepend older page
|
|
if !hasMore || len(items) == 0 {
|
|
break
|
|
}
|
|
before = items[0].ID
|
|
}
|
|
return all
|
|
}
|
|
|
|
main := collect(ActivitySessionFilter{Worker: "mainagent"}, 10)
|
|
if len(main) != 25 {
|
|
t.Fatalf("main count = %d, want 25", len(main))
|
|
}
|
|
plan := collect(ActivitySessionFilter{Worker: "planner"}, 7)
|
|
if len(plan) != 25 {
|
|
t.Fatalf("plan count = %d, want 25", len(plan))
|
|
}
|
|
wa := collect(ActivitySessionFilter{NodeID: &intentA}, 8)
|
|
if len(wa) != 30 {
|
|
t.Fatalf("workerA count = %d, want 30", len(wa))
|
|
}
|
|
wb := collect(ActivitySessionFilter{NodeID: &intentB}, 8)
|
|
if len(wb) != 5 {
|
|
t.Fatalf("workerB count = %d, want 5", len(wb))
|
|
}
|
|
// full ascending order across the reconstructed session
|
|
for i := 1; i < len(wa); i++ {
|
|
if wa[i-1].ID >= wa[i].ID {
|
|
t.Fatalf("reconstructed session not ascending at %d", i)
|
|
}
|
|
}
|
|
// latest page (before=0) must include the session's newest record.
|
|
latest, hasMore, err := es.ActivityPage(ActivitySessionFilter{NodeID: &intentA}, 0, 10)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !hasMore {
|
|
t.Fatalf("workerA should have more than one page")
|
|
}
|
|
if latest[len(latest)-1].ID != wa[len(wa)-1].ID {
|
|
t.Fatalf("latest page missing newest record")
|
|
}
|
|
// snapshot cursor is the whole-task max, ≥ any session's max.
|
|
if snap < wa[len(wa)-1].ID {
|
|
t.Fatalf("snapshot %d < workerA max %d", snap, wa[len(wa)-1].ID)
|
|
}
|
|
}
|
|
|
|
// TestListByKindPage covers the paged worker(intent) list that lets the session list
|
|
// reach past the old fixed 300 cap (docs §8 / §11.1 item 10).
|
|
func TestListByKindPage(t *testing.T) {
|
|
d, err := Open(testDSN(t))
|
|
if err != nil {
|
|
t.Skipf("postgres unavailable (%v) — skipping", err)
|
|
}
|
|
defer d.Close()
|
|
|
|
expID, err := d.CreateExploration("test", "意图分页")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer d.Exec(`DELETE FROM explorations WHERE id=$1`, expID)
|
|
es := d.Exploration(expID)
|
|
|
|
const total = 25
|
|
for range total {
|
|
if _, err := es.AddIntent(map[string]any{"summary": "i"}, 1, nil, "planner"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
// Page backward in chunks of 10; expect 10,10,5 and hasMore false on last.
|
|
seen := map[int64]bool{}
|
|
before := int64(0)
|
|
pages := 0
|
|
for {
|
|
items, hasMore, err := es.ListByKindPage(KindIntent, before, 10)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
pages++
|
|
for _, n := range items {
|
|
if seen[n.ID] {
|
|
t.Fatalf("dup intent %d across pages", n.ID)
|
|
}
|
|
seen[n.ID] = true
|
|
}
|
|
if len(items) == 0 || !hasMore {
|
|
break
|
|
}
|
|
// newest-first within a page → the oldest (smallest id) is last; page older
|
|
// history before it next.
|
|
before = items[len(items)-1].ID
|
|
}
|
|
if len(seen) != total {
|
|
t.Fatalf("paged intents = %d, want %d", len(seen), total)
|
|
}
|
|
if pages < 3 {
|
|
t.Fatalf("expected ≥3 pages for %d items at size 10, got %d", total, pages)
|
|
}
|
|
}
|