c8559676ed
executus CI / test (push) Has been cancelled
Merges the skill half of the persona/skill pair plus the second nested module. (Squashed onto main from phase-4b-skill; the audit/budget/persona batteries it was stacked on already landed via the P4 merge.) - skill/: clean-redesign Skill noun + LEAN SkillStore (lifecycle/versions/ schedule only) + ToRunnable + Memory default. - contrib/store/: separate go.mod carrying modernc.org/sqlite, so the driver never enters the core go.sum. db.Budget()/Personas()/Skills()/Audit() back all four store seams (JSON-blob + indexed columns; round-trip tested). Includes the verified gadfly #5 fixes (AppendVersion tx+UNIQUE+error, Mark*ScheduledRun atomic json_set, busy_timeout, NaN guard). - CI: builds + tests the nested module and asserts it owns the sqlite driver. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
66 lines
1.7 KiB
Go
66 lines
1.7 KiB
Go
package store
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"testing"
|
|
"time"
|
|
|
|
"gitea.stevedudenhoeffer.com/steve/executus/budget"
|
|
)
|
|
|
|
// TestSQLiteBudgetConformance runs the budget battery over the SQLite store and
|
|
// asserts the same rolling-window contract the in-memory store must satisfy.
|
|
func TestSQLiteBudgetConformance(t *testing.T) {
|
|
ctx := context.Background()
|
|
db, err := Open(":memory:")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer db.Close()
|
|
st := db.Budget()
|
|
if err := st.Initialize(ctx); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
now := time.Now().UTC()
|
|
b := budget.NewDBBudget(st, func() float64 { return 100 }, nil, func() time.Time { return now })
|
|
|
|
if err := b.Check(ctx, "u"); err != nil {
|
|
t.Fatalf("fresh caller should pass: %v", err)
|
|
}
|
|
b.Commit(ctx, "u", 60)
|
|
if err := b.Check(ctx, "u"); err != nil {
|
|
t.Fatalf("60/100 should pass: %v", err)
|
|
}
|
|
b.Commit(ctx, "u", 50) // 110 total
|
|
if err := b.Check(ctx, "u"); !errors.Is(err, budget.ErrBudgetExceeded) {
|
|
t.Fatalf("110/100 should be ErrBudgetExceeded, got %v", err)
|
|
}
|
|
|
|
// Direct Get reflects the persisted row.
|
|
row, err := st.Get(ctx, "u")
|
|
if err != nil || row == nil {
|
|
t.Fatalf("Get: %v %+v", err, row)
|
|
}
|
|
if row.SecondsUsed != 110 || row.RunsCount != 2 {
|
|
t.Errorf("row = %+v, want seconds=110 runs=2", row)
|
|
}
|
|
|
|
// Window rolls over after 7 days.
|
|
now = now.Add(8 * 24 * time.Hour)
|
|
b.Commit(ctx, "u", 1)
|
|
if err := b.Check(ctx, "u"); err != nil {
|
|
t.Fatalf("after rollover should pass: %v", err)
|
|
}
|
|
row, _ = st.Get(ctx, "u")
|
|
if row.SecondsUsed != 1 || row.RunsCount != 1 {
|
|
t.Errorf("post-rollover row = %+v, want seconds=1 runs=1", row)
|
|
}
|
|
|
|
// Unknown user -> (nil, nil).
|
|
if r, err := st.Get(ctx, "nobody"); err != nil || r != nil {
|
|
t.Errorf("Get(unknown) = %+v %v, want nil,nil", r, err)
|
|
}
|
|
}
|