186 lines
6.0 KiB
Go
186 lines
6.0 KiB
Go
package store_test
|
|
|
|
import (
|
|
"path/filepath"
|
|
"testing"
|
|
"time"
|
|
|
|
"git.wntrmute.dev/kyle/crossbar/internal/store"
|
|
)
|
|
|
|
func open(t *testing.T, dir string) *store.Store {
|
|
s, err := store.Open(filepath.Join(dir, "crossbar.db"))
|
|
if err != nil {
|
|
t.Fatalf("Open: %v", err)
|
|
}
|
|
t.Cleanup(func() { _ = s.Close() })
|
|
return s
|
|
}
|
|
|
|
func TestOpenIsIdempotentAndWAL(t *testing.T) {
|
|
dir := t.TempDir()
|
|
s := open(t, dir)
|
|
if got := s.JournalMode(); got != "wal" {
|
|
t.Errorf("journal_mode = %q, want wal", got)
|
|
}
|
|
if err := s.Close(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
open(t, dir) // second open on the same file must not fail on existing tables
|
|
}
|
|
|
|
func TestLeasesSurviveReopen(t *testing.T) {
|
|
dir := t.TempDir()
|
|
s := open(t, dir)
|
|
now := time.Date(2026, 9, 25, 10, 0, 0, 0, time.UTC)
|
|
l := store.Lease{Route: "opencode-a", FP: "abc", Model: "m", Host: "alpha", State: store.Active, Created: now, LastUsed: now}
|
|
if err := s.SaveLease(l); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
l2 := l
|
|
l2.FP = "def"
|
|
l2.Host = "beta"
|
|
l2.State = store.Pinned
|
|
if err := s.SaveLease(l2); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// Saving the same key again replaces, not duplicates.
|
|
l.Host = "beta"
|
|
l.LastUsed = now.Add(time.Minute)
|
|
if err := s.SaveLease(l); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := s.Close(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
s = open(t, dir)
|
|
got, err := s.ListLeases()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(got) != 2 {
|
|
t.Fatalf("ListLeases = %d rows, want 2: %+v", len(got), got)
|
|
}
|
|
byFP := map[string]store.Lease{}
|
|
for _, x := range got {
|
|
byFP[x.FP] = x
|
|
}
|
|
if a := byFP["abc"]; a.Host != "beta" || !a.LastUsed.Equal(now.Add(time.Minute)) || a.State != store.Active {
|
|
t.Errorf("abc = %+v", a)
|
|
}
|
|
if d := byFP["def"]; d.State != store.Pinned || d.Host != "beta" {
|
|
t.Errorf("def = %+v", d)
|
|
}
|
|
if err := s.DeleteLease("opencode-a", "abc", "m"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
got, _ = s.ListLeases()
|
|
if len(got) != 1 || got[0].FP != "def" {
|
|
t.Errorf("after delete: %+v", got)
|
|
}
|
|
}
|
|
|
|
func TestEventsAndRequestsAndUsage(t *testing.T) {
|
|
s := open(t, t.TempDir())
|
|
t0 := time.Date(2026, 9, 25, 10, 0, 0, 0, time.UTC)
|
|
must := func(err error) {
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
must(s.RecordEvent(store.LeaseEvent{TS: t0, Route: "r1", Model: "m", FromHost: "", ToHost: "alpha", Reason: store.ReasonNew}))
|
|
must(s.RecordEvent(store.LeaseEvent{TS: t0.Add(time.Hour), Route: "r1", Model: "m", FromHost: "alpha", ToHost: "beta", Reason: store.ReasonUnhealthy}))
|
|
reqs := []store.Request{
|
|
{Route: "r1", FP: "a", Model: "m", Host: "alpha", Started: t0, QueuedMs: 0, TTFBMs: 100, TotalMs: 1000, Status: 200, Streamed: true, PromptTokens: 1000, CachedTokens: 900, CompletionTokens: 50},
|
|
{Route: "r1", FP: "a", Model: "m", Host: "alpha", Started: t0.Add(time.Minute), QueuedMs: 40, TTFBMs: 120, TotalMs: 2000, Status: 200, Streamed: true, PromptTokens: 1100, CachedTokens: 1000, CompletionTokens: 60},
|
|
{Route: "r2", FP: "b", Model: "m", Host: "beta", Started: t0.Add(2 * time.Minute), TotalMs: 500, Status: 502, Err: "upstream failed"},
|
|
{Route: "r2", FP: "b", Model: "m", Host: "beta", Started: t0.Add(-48 * time.Hour), TotalMs: 300, Status: 200, PromptTokens: 10, CompletionTokens: 5},
|
|
}
|
|
for _, r := range reqs {
|
|
must(s.RecordRequest(r))
|
|
}
|
|
must(s.RecordHostHealth(store.HostHealth{TS: t0, Host: "alpha", Healthy: true, Loaded: []string{"m"}}))
|
|
|
|
rows, err := s.Usage(t0.Add(-time.Hour), store.ByRoute)
|
|
must(err)
|
|
if len(rows) != 2 {
|
|
t.Fatalf("Usage by route since t0-1h: %d rows, want 2 (r1, r2): %+v", len(rows), rows)
|
|
}
|
|
byKey := map[string]store.UsageRow{}
|
|
for _, r := range rows {
|
|
byKey[r.Key] = r
|
|
}
|
|
r1 := byKey["r1"]
|
|
if r1.Requests != 2 || r1.Errors != 0 || r1.BusyMs != 3000 || r1.QueuedMs != 40 {
|
|
t.Errorf("r1 = %+v", r1)
|
|
}
|
|
if r1.PromptTokens != 2100 || r1.CachedTokens != 1900 || r1.CompletionTokens != 110 {
|
|
t.Errorf("r1 tokens = %+v", r1)
|
|
}
|
|
if got := r1.CacheHitRatio(); got < 0.904 || got > 0.905 {
|
|
t.Errorf("r1 cache hit ratio = %v, want 1900/2100", got)
|
|
}
|
|
r2 := byKey["r2"]
|
|
if r2.Requests != 1 || r2.Errors != 1 || r2.BusyMs != 500 {
|
|
t.Errorf("r2 = %+v (the 48h-old request is outside since)", r2)
|
|
}
|
|
if r2.CacheHitRatio() != 0 {
|
|
t.Errorf("no prompt tokens: ratio must be 0, got %v", r2.CacheHitRatio())
|
|
}
|
|
byHost, err := s.Usage(time.Time{}, store.ByHost)
|
|
must(err)
|
|
if len(byHost) != 2 {
|
|
t.Errorf("by host, all time: %+v", byHost)
|
|
}
|
|
for _, r := range byHost {
|
|
if r.Key == "beta" && r.Requests != 2 {
|
|
t.Errorf("beta all-time requests = %d, want 2", r.Requests)
|
|
}
|
|
}
|
|
byModel, err := s.Usage(time.Time{}, store.ByModel)
|
|
must(err)
|
|
if len(byModel) != 1 || byModel[0].Key != "m" || byModel[0].Requests != 4 {
|
|
t.Errorf("by model: %+v", byModel)
|
|
}
|
|
ev, err := s.Events(t0.Add(-time.Minute), 10)
|
|
must(err)
|
|
if len(ev) != 2 || ev[0].Reason != store.ReasonNew || ev[1].ToHost != "beta" {
|
|
t.Errorf("events = %+v", ev)
|
|
}
|
|
}
|
|
|
|
func TestPruneRollsUpOldRequests(t *testing.T) {
|
|
s := open(t, t.TempDir())
|
|
t0 := time.Date(2026, 9, 25, 10, 0, 0, 0, time.UTC)
|
|
old := t0.Add(-200 * 24 * time.Hour)
|
|
for i := 0; i < 3; i++ {
|
|
if err := s.RecordRequest(store.Request{Route: "r", Model: "m", Host: "h", Started: old.Add(time.Duration(i) * time.Minute), TotalMs: 100, Status: 200, PromptTokens: 10, CachedTokens: 5, CompletionTokens: 1}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
if err := s.RecordRequest(store.Request{Route: "r", Model: "m", Host: "h", Started: t0, TotalMs: 100, Status: 200}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
n, err := s.Prune(t0, 180*24*time.Hour)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if n != 3 {
|
|
t.Errorf("Prune removed %d rows, want 3", n)
|
|
}
|
|
rows, _ := s.Usage(time.Time{}, store.ByRoute)
|
|
if len(rows) != 1 || rows[0].Requests != 4 || rows[0].PromptTokens != 30 {
|
|
t.Errorf("usage must still include pruned traffic through the daily rollup: %+v", rows)
|
|
}
|
|
live, _ := s.Usage(old.Add(24*time.Hour), store.ByRoute)
|
|
if len(live) != 1 || live[0].Requests != 1 {
|
|
t.Errorf("recent-only usage = %+v", live)
|
|
}
|
|
}
|
|
|
|
func TestBadPath(t *testing.T) {
|
|
if _, err := store.Open(filepath.Join(t.TempDir(), "no", "such", "dir", "x.db")); err == nil {
|
|
t.Fatal("Open must fail when the directory does not exist")
|
|
}
|
|
}
|