Files
go-sip/internal/store/local_v01_test.go
T

149 lines
5.6 KiB
Go

package store
import (
"errors"
"path/filepath"
"testing"
"time"
)
func TestPersistLocalCommandIsAtomicAndIdempotent(t *testing.T) {
st, err := Open(filepath.Join(t.TempDir(), "dispatcher.db"))
if err != nil {
t.Fatal(err)
}
defer st.Close()
bundle := localConfigBundle("dispatcher-1", "tenant-id-1", "tenant-key-1", "task-1", 1, 1, 1, 2, 2, time.Now().UTC())
if err := st.SaveLocalConfigBundle(bundle); err != nil {
t.Fatal(err)
}
record := localAdmissionCommand("command-1", "execution-1", "task-1", bundle, "dispatcher-1", "tenant-id-1", "tenant-key-1")
seedLocalCommandAssignments(t, st, record)
record.Exchange = "agent-call.saas.v3"
record.RoutingKey = "d.dispatcher-1.out"
duplicate, err := st.PersistLocalCommand(record)
if err != nil {
t.Fatal(err)
}
if duplicate {
t.Fatal("first command was reported as a duplicate")
}
var inboxStatus string
var receiptCount, outboxCount int
if err := st.db.QueryRow(`SELECT status FROM inbox WHERE tenant_id=? AND command_id=?`, record.TenantID, record.CommandID).Scan(&inboxStatus); err != nil {
t.Fatal(err)
}
if inboxStatus != "persisted" {
t.Fatalf("inbox status = %q, want persisted", inboxStatus)
}
if err := st.db.QueryRow(`SELECT COUNT(*) FROM mq_command_receipts WHERE tenant_id=? AND command_id=? AND response_id=?`, record.TenantID, record.CommandID, record.ReceiptID).Scan(&receiptCount); err != nil {
t.Fatal(err)
}
if err := st.db.QueryRow(`SELECT COUNT(*) FROM outbox WHERE event_id=? AND status='pending'`, record.ReceiptID).Scan(&outboxCount); err != nil {
t.Fatal(err)
}
if receiptCount != 1 || outboxCount != 1 {
t.Fatalf("receipt links=%d outbox rows=%d, want 1 each", receiptCount, outboxCount)
}
first, err := st.ClaimOutbox(1)
if err != nil || len(first) != 1 {
t.Fatalf("claim first receipt: records=%d err=%v", len(first), err)
}
if err := st.MarkOutboxPublished(first[0].ID); err != nil {
t.Fatal(err)
}
duplicate, err = st.PersistLocalCommand(record)
if err != nil {
t.Fatal(err)
}
if !duplicate {
t.Fatal("redelivered command was not identified as a duplicate")
}
second, err := st.ClaimOutbox(1)
if err != nil || len(second) != 1 {
t.Fatalf("claim recovered receipt: records=%d err=%v", len(second), err)
}
if second[0].EventID != first[0].EventID || string(second[0].Body) != string(first[0].Body) {
t.Fatalf("duplicate changed receipt: first=%+v second=%+v", first[0], second[0])
}
record.Body = []byte(`{"command_id":"command-1","callee":"different"}`)
if _, err := st.PersistLocalCommand(record); !errors.Is(err, ErrCommandConflict) {
t.Fatalf("same command ID with different body error = %v, want ErrCommandConflict", err)
}
}
func TestEnqueueLocalEventIsIdempotentAndRejectsContentConflict(t *testing.T) {
st, err := Open(filepath.Join(t.TempDir(), "dispatcher.db"))
if err != nil {
t.Fatal(err)
}
defer st.Close()
event := LocalEventRecord{
EventID: "call-result-1", TenantKey: "tenant-key-1", Exchange: "agent-call.saas.v3",
RoutingKey: "d.dispatcher-1.out", Body: []byte(`{"event_id":"call-result-1"}`),
}
duplicate, err := st.EnqueueLocalEvent(event)
if err != nil || duplicate {
t.Fatalf("first enqueue duplicate=%v err=%v", duplicate, err)
}
duplicate, err = st.EnqueueLocalEvent(event)
if err != nil || !duplicate {
t.Fatalf("same event enqueue duplicate=%v err=%v", duplicate, err)
}
var count int
if err := st.db.QueryRow(`SELECT COUNT(*) FROM outbox WHERE event_id=?`, event.EventID).Scan(&count); err != nil {
t.Fatal(err)
}
if count != 1 {
t.Fatalf("outbox rows = %d, want 1", count)
}
event.Body = []byte(`{"event_id":"call-result-1","different":true}`)
if _, err := st.EnqueueLocalEvent(event); !errors.Is(err, ErrCommandConflict) {
t.Fatalf("same event ID with different body error = %v, want ErrCommandConflict", err)
}
}
func TestVerifyLocalAcceptedCommandBindsCallResultToReceipt(t *testing.T) {
st, err := Open(filepath.Join(t.TempDir(), "dispatcher.db"))
if err != nil {
t.Fatal(err)
}
defer st.Close()
bundle := localConfigBundle("dispatcher-a", "tenant-a", "tenant-a", "task-a", 1, 1, 1, 2, 2, time.Now().UTC())
if err := st.SaveLocalConfigBundle(bundle); err != nil {
t.Fatal(err)
}
record := localAdmissionCommand("command-a", "execution-a", "task-a", bundle, "dispatcher-a", "tenant-a", "tenant-a")
seedLocalCommandAssignments(t, st, record)
record.Body = []byte(`{"command_id":"command-a","command_type":"call.execute","tenant_id":"tenant-a","tenant_key":"tenant-a","payload":{"task_id":"task-a"}}`)
record.ReceiptID = "receipt-a"
record.Exchange = "agent-call.saas.v3"
record.RoutingKey = "d.dispatcher-a.out"
record.ReceiptBody = []byte(`{"event_type":"command.result","tenant_id":"tenant-a","tenant_key":"tenant-a","payload":{"command_id":"command-a","command_type":"call.execute","status":"accepted","execution_id":"execution-a"}}`)
_, err = st.PersistLocalCommand(record)
if err != nil {
t.Fatal(err)
}
if err := st.VerifyLocalAcceptedCommand("tenant-a", "tenant-a", "command-a", "execution-a", "task-a"); err != nil {
t.Fatalf("valid result binding: %v", err)
}
for _, tc := range []struct {
tenantID, tenantKey, commandID, executionID, taskID string
}{
{"tenant-b", "tenant-a", "command-a", "execution-a", "task-a"},
{"tenant-a", "tenant-b", "command-a", "execution-a", "task-a"},
{"tenant-a", "tenant-a", "command-a", "execution-b", "task-a"},
{"tenant-a", "tenant-a", "command-a", "execution-a", "task-b"},
} {
if err := st.VerifyLocalAcceptedCommand(tc.tenantID, tc.tenantKey, tc.commandID, tc.executionID, tc.taskID); !errors.Is(err, ErrCommandConflict) {
t.Fatalf("invalid result binding %+v error = %v, want ErrCommandConflict", tc, err)
}
}
}