186 lines
5.5 KiB
Go
186 lines
5.5 KiB
Go
package store
|
|
|
|
import (
|
|
"database/sql"
|
|
"errors"
|
|
"testing"
|
|
"time"
|
|
|
|
"git.ipao.vip/rogee/go-sip/contracts"
|
|
"git.ipao.vip/rogee/go-sip/internal/tenant"
|
|
_ "modernc.org/sqlite"
|
|
)
|
|
|
|
func testStore(t *testing.T) *Store {
|
|
t.Helper()
|
|
db, err := sql.Open("sqlite", ":memory:")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
now := time.Date(2026, 9, 18, 0, 0, 0, 0, time.UTC)
|
|
s, err := New(db, func() time.Time { return now })
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Cleanup(func() { _ = s.Close() })
|
|
return s
|
|
}
|
|
|
|
func TestIngestIsDurableAndIdempotent(t *testing.T) {
|
|
s := testStore(t)
|
|
raw, err := contracts.Read("examples/call.execute.json")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
result, err := s.IngestCommand(raw, "agent-call.tenant.tenant-demo-key.call.execute")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if result.Duplicate {
|
|
t.Fatal("first command marked duplicate")
|
|
}
|
|
second, err := s.IngestCommand(raw, tenant.CommandRoutingPrefix+"tenant-demo-key"+tenant.CommandRoutingSuffix)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !second.Duplicate {
|
|
t.Fatal("second command was not idempotent")
|
|
}
|
|
outbox, err := s.ClaimOutbox(10)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(outbox) != 1 {
|
|
t.Fatalf("outbox rows = %d, want 1", len(outbox))
|
|
}
|
|
}
|
|
|
|
func TestIngestRejectsRoutingMismatch(t *testing.T) {
|
|
s := testStore(t)
|
|
raw, err := contracts.Read("examples/call.execute.json")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := s.IngestCommand(raw, "agent-call.tenant.other.call.execute"); err == nil {
|
|
t.Fatal("expected routing mismatch")
|
|
}
|
|
}
|
|
|
|
func TestQuotaIsAtomicAndUnknownIsNotReleased(t *testing.T) {
|
|
s := testStore(t)
|
|
for _, scope := range []string{"tenant:tenant-demo-key", "global", "cell:cell-1"} {
|
|
if err := s.SetQuota(scope, 1); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
if err := s.Reserve("r1", "e1", "tenant-demo-key", []string{"tenant:tenant-demo-key", "global", "cell:cell-1"}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := s.Reserve("r2", "e2", "tenant-demo-key", []string{"tenant:tenant-demo-key", "global", "cell:cell-1"}); !errors.Is(err, ErrNoCapacity) {
|
|
t.Fatalf("reserve error = %v, want ErrNoCapacity", err)
|
|
}
|
|
if err := s.ReleaseReservationWithScopes("r1", []string{"tenant:tenant-demo-key", "global", "cell:cell-1"}, true); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := s.Reserve("r3", "e3", "tenant-demo-key", []string{"tenant:tenant-demo-key", "global", "cell:cell-1"}); !errors.Is(err, ErrNoCapacity) {
|
|
t.Fatalf("unknown reservation released capacity: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestFinalizeReservationRequeuesBeforeRemoteSubmission(t *testing.T) {
|
|
s := testStore(t)
|
|
if err := s.SetQuota("global", 1); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
raw, err := contracts.Read("examples/call.execute.json")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := s.IngestCommand(raw, "agent-call.tenant.tenant-demo-key.call.execute"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := s.Reserve("r-requeue", "exec_demo_001", "tenant-demo-key", []string{"global"}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := s.MarkTaskReserved("exec_demo_001"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := s.FinalizeReservation("r-requeue", "exec_demo_001", false); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var taskStatus string
|
|
if err := s.DB().QueryRow(`SELECT status FROM tasks WHERE execution_id = 'exec_demo_001'`).Scan(&taskStatus); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if taskStatus != "accepted" {
|
|
t.Fatalf("task status=%q, want accepted", taskStatus)
|
|
}
|
|
var reserved, unknown int64
|
|
if err := s.DB().QueryRow(`SELECT reserved_value, unknown_value FROM quotas WHERE scope = 'global'`).Scan(&reserved, &unknown); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if reserved != 0 || unknown != 0 {
|
|
t.Fatalf("quota reserved=%d unknown=%d, want 0/0", reserved, unknown)
|
|
}
|
|
}
|
|
|
|
func TestFinalizeReservationKeepsUnknownCounted(t *testing.T) {
|
|
s := testStore(t)
|
|
if err := s.SetQuota("global", 1); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
raw, err := contracts.Read("examples/call.execute.json")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := s.IngestCommand(raw, "agent-call.tenant.tenant-demo-key.call.execute"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := s.Reserve("r-unknown", "exec_demo_001", "tenant-demo-key", []string{"global"}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := s.MarkTaskReserved("exec_demo_001"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := s.FinalizeReservation("r-unknown", "exec_demo_001", true); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var taskStatus string
|
|
if err := s.DB().QueryRow(`SELECT status FROM tasks WHERE execution_id = 'exec_demo_001'`).Scan(&taskStatus); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if taskStatus != "unknown" {
|
|
t.Fatalf("task status=%q, want unknown", taskStatus)
|
|
}
|
|
var reserved, unknown int64
|
|
if err := s.DB().QueryRow(`SELECT reserved_value, unknown_value FROM quotas WHERE scope = 'global'`).Scan(&reserved, &unknown); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if reserved != 0 || unknown != 1 {
|
|
t.Fatalf("quota reserved=%d unknown=%d, want 0/1", reserved, unknown)
|
|
}
|
|
}
|
|
|
|
func TestControlCASAndStopBarrier(t *testing.T) {
|
|
s := testStore(t)
|
|
raw, err := contracts.Read("examples/call.execute.json")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := s.IngestCommand(raw, "agent-call.tenant.tenant-demo-key.call.execute"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := s.ApplyControl("exec_demo_001", 1, "pause"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := s.ApplyControl("exec_demo_001", 99, "resume"); !errors.Is(err, ErrCASConflict) {
|
|
t.Fatalf("revision mismatch error = %v", err)
|
|
}
|
|
if err := s.ApplyControl("exec_demo_001", 1, "stop"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := s.ApplyControl("exec_demo_001", 1, "resume"); !errors.Is(err, ErrCASConflict) {
|
|
t.Fatalf("stopped resume error = %v", err)
|
|
}
|
|
}
|