Files
wehub-resource-sync f99010fae1
CI / lint (push) Failing after 1s
CI / frontend (push) Failing after 1s
CI / scripts (push) Failing after 1s
CI / Go Test (ubuntu-latest) (push) Failing after 0s
CI / frontend-node-25 (push) Failing after 1s
CI / docs (push) Failing after 0s
CI / coverage (push) Failing after 0s
CI / e2e (push) Failing after 0s
Docker / build-and-push (push) Failing after 1s
CI / integration (push) Failing after 4m43s
CI / Go Test (windows-latest) (push) Has been cancelled
CI / Desktop Unit Tests (Windows) (push) Has been cancelled
Desktop Artifacts / Desktop Build (Linux (arm64)) (push) Has been cancelled
Desktop Artifacts / Desktop Build (Linux) (push) Has been cancelled
Desktop Artifacts / Desktop Build (Windows) (push) Has been cancelled
Desktop Artifacts (macOS) / Desktop Build (macOS (aarch64)) (push) Has been cancelled
Desktop Artifacts (macOS) / Desktop Build (macOS (x86_64)) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:30:36 +08:00

164 lines
5.1 KiB
Go

//go:build !(windows && arm64)
package duckdb
import (
"context"
"testing"
"github.com/stretchr/testify/require"
"go.kenn.io/agentsview/internal/db"
)
// syncedStoreFromWrites seeds a local SQLite DB with the given batch writes and
// returns a DuckDB store mirroring it, so sort/keyset SQL can be exercised
// against the DuckDB dialect (CAST placeholders, COALESCE sentinel).
func syncedStoreFromWrites(t *testing.T, writes []db.SessionBatchWrite) *Store {
t.Helper()
ctx := context.Background()
local := newLocalDB(t)
_, err := local.WriteSessionBatchAtomic(writes)
require.NoError(t, err)
syncer := newInMemoryTestSync(t, local, SyncOptions{})
_, err = syncer.Push(ctx, true, nil)
require.NoError(t, err)
return NewStoreFromDB(syncer.DB())
}
func sortSeedSession(id, project, ts string, signals db.SessionSignalUpdate) db.SessionBatchWrite {
return db.SessionBatchWrite{
Session: db.Session{
ID: id,
Project: project,
Machine: "test-machine",
Agent: "claude",
StartedAt: new(ts),
EndedAt: new(ts),
MessageCount: 2,
UserMessageCount: 2,
RelationshipType: "root",
},
Signals: signals,
DataVersion: 1,
ReplaceMessages: true,
}
}
func duckWalk(t *testing.T, store *Store, f db.SessionFilter) []string {
t.Helper()
ctx := context.Background()
var got []string
seen := map[string]bool{}
cursor := ""
for {
f.Cursor = cursor
page, err := store.ListSessions(ctx, f)
require.NoError(t, err)
for _, s := range page.Sessions {
require.False(t, seen[s.ID], "duplicate %s", s.ID)
seen[s.ID] = true
got = append(got, s.ID)
}
if page.NextCursor == "" {
break
}
cursor = page.NextCursor
}
return got
}
// TestDuckDBSort_SecretsVersioned mirrors the SQLite secrets-sort behavior on
// DuckDB, including the gating CASE inside ORDER BY and the keyset cursor.
func TestDuckDBSort_SecretsVersioned(t *testing.T) {
store := syncedStoreFromWrites(t, []db.SessionBatchWrite{
sortSeedSession("sec-cur", "sec", "2026-03-01T00:00:00Z", db.SessionSignalUpdate{
SecretLeakCount: 5, SecretsRulesVersion: "v1",
}),
sortSeedSession("sec-stale", "sec", "2026-03-02T00:00:00Z", db.SessionSignalUpdate{
SecretLeakCount: 9, SecretsRulesVersion: "old",
}),
sortSeedSession("sec-none", "sec", "2026-03-03T00:00:00Z", db.SessionSignalUpdate{}),
})
desc := true
walked := duckWalk(t, store, db.SessionFilter{
Project: "sec",
OrderBy: "secrets",
Descending: &desc,
SecretsRulesVersions: []string{"v1"},
Limit: 1,
})
require.Len(t, walked, 3)
require.Equal(t, "sec-cur", walked[0],
"current-version session leads once the stale 9 is gated to 0")
}
func multiKeySeed(id string, msgs int, started string) db.SessionBatchWrite {
return db.SessionBatchWrite{
Session: db.Session{
ID: id,
Project: "mk",
Machine: "test-machine",
Agent: "claude",
StartedAt: new(started),
MessageCount: msgs,
UserMessageCount: 2,
RelationshipType: "root",
},
DataVersion: 1,
ReplaceMessages: true,
}
}
// TestDuckDBSort_MultiKey mirrors the SQLite mixed-direction multi-key behavior
// on DuckDB (CAST cursor placeholders, lexicographic OR-expansion): messages
// ascending, then started descending, with the id tie-break following the last
// term's direction. The paginated walk must equal the full-listing order.
func TestDuckDBSort_MultiKey(t *testing.T) {
store := syncedStoreFromWrites(t, []db.SessionBatchWrite{
multiKeySeed("mk-a", 1, "2024-03-01T00:00:00Z"),
multiKeySeed("mk-b", 1, "2024-01-01T00:00:00Z"),
multiKeySeed("mk-c", 2, "2024-02-01T00:00:00Z"),
multiKeySeed("mk-d", 2, "2024-05-01T00:00:00Z"),
multiKeySeed("mk-e", 1, "2024-03-01T00:00:00Z"), // ties mk-a
})
asc, desc := false, true
sortKeys := []db.SortKey{
{Key: "messages", Descending: &asc},
{Key: "started", Descending: &desc},
}
full, err := store.ListSessions(context.Background(), db.SessionFilter{
Project: "mk", Sort: sortKeys, Limit: 100,
})
require.NoError(t, err)
want := make([]string, len(full.Sessions))
for i, s := range full.Sessions {
want[i] = s.ID
}
require.Equal(t, []string{"mk-e", "mk-a", "mk-b", "mk-d", "mk-c"}, want)
walked := duckWalk(t, store, db.SessionFilter{Project: "mk", Sort: sortKeys, Limit: 1})
require.Equal(t, want, walked, "paginated walk matches full listing")
}
// TestDuckDBSort_NullsLast mirrors the nullable-sort (health) behavior on
// DuckDB: NULLs sort last and pagination crosses the sentinel boundary.
func TestDuckDBSort_NullsLast(t *testing.T) {
store := syncedStoreFromWrites(t, []db.SessionBatchWrite{
sortSeedSession("h20", "health", "2026-03-01T00:00:00Z", db.SessionSignalUpdate{
HealthScore: new(20),
}),
sortSeedSession("h80", "health", "2026-03-02T00:00:00Z", db.SessionSignalUpdate{
HealthScore: new(80),
}),
sortSeedSession("hnull", "health", "2026-03-03T00:00:00Z", db.SessionSignalUpdate{}),
})
walked := duckWalk(t, store, db.SessionFilter{
Project: "health", OrderBy: "health", Limit: 1,
})
require.Equal(t, []string{"h20", "h80", "hnull"}, walked)
}