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

193 lines
7.9 KiB
Go

package store
import (
"crypto/sha256"
"database/sql"
"encoding/hex"
"encoding/json"
"errors"
"strings"
"testing"
"time"
agentv1 "git.ipao.vip/rogee/go-sip/gen/agent/v1"
)
func localTestOriginationBinding(t *testing.T, st *Store, executionID string) *agentv1.ExecutionBinding {
t.Helper()
binding := &agentv1.ExecutionBinding{ExecutionId: executionID, AttemptId: executionID}
if err := st.DB().QueryRow(`SELECT tenant_id,tenant_key,task_id,task_item_id,task_revision,agent_version_id,route_policy_id,caller_profile_id FROM tasks WHERE execution_id=?`, executionID).Scan(
&binding.TenantId, &binding.TenantKey, &binding.TaskId, &binding.TaskItemId, &binding.TaskRevision,
&binding.AgentVersionId, &binding.RoutePolicyId, &binding.CallerProfileId,
); err != nil {
t.Fatal(err)
}
return binding
}
func readyLocalOrigination(t *testing.T) (*Store, LocalCommandRecord) {
t.Helper()
st := openLocalDiscoveryTestStore(t)
at := time.Date(2026, 9, 21, 2, 0, 0, 0, time.UTC)
bundle := localConfigBundle("d-1", "tenant-a", "tenant-key-a", "task-a", 1, 1, 1, 1, 1, at)
if err := st.SaveLocalConfigBundle(bundle); err != nil {
t.Fatal(err)
}
record := localAdmissionCommand("command-a", "execution-a", "task-a", bundle, "d-1", "tenant-a", "tenant-key-a")
seedLocalCommandAssignments(t, st, record)
if duplicate, err := st.PersistLocalCommand(record); err != nil || duplicate {
t.Fatalf("persist accepted command: duplicate=%v err=%v", duplicate, err)
}
return st, record
}
func TestLocalOriginationDecisionsAreDurableAndOneShot(t *testing.T) {
for _, decision := range []string{"issued", "refused"} {
t.Run(decision, func(t *testing.T) {
st, record := readyLocalOrigination(t)
decisionAt := time.Date(2026, 9, 21, 2, 0, 0, 0, time.UTC)
pending, err := st.LocalPendingOriginationForCommand(record.Body)
if err != nil || pending != "execution-a" {
t.Fatalf("pending execution=%q err=%v", pending, err)
}
candidate, err := st.LoadLocalOrigination("d-1", pending)
if err != nil || candidate.CommandID != "command-a" || candidate.Callee != "15003164745" ||
candidate.Snapshot.SelectedTrunkID != "mock-trunk" || len(candidate.SnapshotSHA256) != 64 {
t.Fatalf("bound origination=%+v err=%v", candidate, err)
}
binding := localTestOriginationBinding(t, st, pending)
if decision == "issued" {
if err := st.ClaimLocalOrigination("d-1", pending, "agent-1", binding, decisionAt); err != nil {
t.Fatal(err)
}
if err := st.ClaimLocalOrigination("d-1", pending, "agent-1", binding, decisionAt); !errors.Is(err, ErrLocalOriginationNotAuthorized) {
t.Fatalf("second claim error=%v", err)
}
if err := st.RefuseLocalOrigination("d-1", pending, "control_closed", decisionAt); !errors.Is(err, ErrLocalOriginationNotAuthorized) {
t.Fatalf("issued instruction was overwritten by refusal: %v", err)
}
} else {
if err := st.RefuseLocalOrigination("d-1", pending, "policy_denied", decisionAt); err != nil {
t.Fatal(err)
}
if err := st.RefuseLocalOrigination("d-1", pending, "control_closed", decisionAt); err != nil {
t.Fatalf("replayed refusal error=%v", err)
}
if err := st.ClaimLocalOrigination("d-1", pending, "agent-1", binding, decisionAt); !errors.Is(err, ErrLocalOriginationNotAuthorized) {
t.Fatalf("refused instruction was revived: %v", err)
}
}
got, err := st.LocalOriginationDecision(pending)
if err != nil || got != decision {
t.Fatalf("durable decision=%q want=%q err=%v", got, decision, err)
}
var bound int
if err := st.DB().QueryRow(`SELECT COUNT(*) FROM execution_agents WHERE execution_id=?`, pending).Scan(&bound); err != nil {
t.Fatal(err)
}
wantBound := 0
if decision == "issued" {
wantBound = 1
}
if bound != wantBound {
t.Fatalf("claim and Agent control binding were not atomic: decision=%s bindings=%d", decision, bound)
}
if _, err := st.LocalPendingOriginationForCommand(record.Body); !errors.Is(err, sql.ErrNoRows) {
t.Fatalf("decided execution still pending: %v", err)
}
assertLocalQuota(t, st, LocalTenantQuotaScope("d-1", "tenant-a"), 1, 1) // F08 owns release.
})
}
}
func TestLocalOriginationFailClosedOnCorruptOrPausedFacts(t *testing.T) {
for _, tc := range []struct {
name string
change func(t *testing.T, st *Store)
}{
{"modified command hash", func(t *testing.T, st *Store) {
t.Helper()
if _, err := st.db.Exec(`UPDATE inbox SET body_hash='bad' WHERE command_id='command-a'`); err != nil {
t.Fatal(err)
}
}},
{"modified snapshot hash", func(t *testing.T, st *Store) {
t.Helper()
if _, err := st.db.Exec(`UPDATE local_v01_execution_configs SET content_sha256=? WHERE execution_id='execution-a'`, strings.Repeat("0", 64)); err != nil {
t.Fatal(err)
}
}},
{"snapshot identity mismatch with valid hash", func(t *testing.T, st *Store) {
t.Helper()
var body []byte
if err := st.db.QueryRow(`SELECT body FROM local_v01_execution_configs WHERE execution_id='execution-a'`).Scan(&body); err != nil {
t.Fatal(err)
}
var snapshot LocalExecutionConfigSnapshot
if err := json.Unmarshal(body, &snapshot); err != nil {
t.Fatal(err)
}
snapshot.TaskID = "different-task"
changed, err := json.Marshal(snapshot)
if err != nil {
t.Fatal(err)
}
hash := sha256.Sum256(changed)
if _, err := st.db.Exec(`UPDATE local_v01_execution_configs SET body=?,content_sha256=? WHERE execution_id='execution-a'`, changed, hex.EncodeToString(hash[:])); err != nil {
t.Fatal(err)
}
}},
{"paused assignment", func(t *testing.T, st *Store) {
t.Helper()
if _, err := st.SetLocalTaskAdmissionBarrier("d-1", "task-a", "tenant-a", "tenant-key-a", "paused"); err != nil {
t.Fatal(err)
}
}},
} {
t.Run(tc.name, func(t *testing.T) {
st, record := readyLocalOrigination(t)
tc.change(t, st)
if _, err := st.LoadLocalOrigination("d-1", "execution-a"); !errors.Is(err, ErrLocalOriginationNotAuthorized) {
t.Fatalf("invalid facts did not block origination: %v", err)
}
if tc.name == "paused assignment" {
if err := st.ClaimLocalOrigination("d-1", "execution-a", "agent-1", localTestOriginationBinding(t, st, "execution-a"), time.Date(2026, 9, 21, 2, 0, 0, 0, time.UTC)); !errors.Is(err, ErrLocalOriginationNotAuthorized) {
t.Fatalf("pause crossed atomic claim: %v", err)
}
var bound int
if err := st.DB().QueryRow(`SELECT COUNT(*) FROM execution_agents WHERE execution_id='execution-a'`).Scan(&bound); err != nil || bound != 0 {
t.Fatalf("failed claim left an Agent binding: count=%d err=%v", bound, err)
}
if err := st.RefuseLocalOrigination("d-1", "execution-a", "control_closed", time.Date(2026, 9, 21, 2, 0, 0, 0, time.UTC)); err != nil {
t.Fatal(err)
}
if _, err := st.LocalPendingOriginationForCommand(record.Body); !errors.Is(err, sql.ErrNoRows) {
t.Fatalf("paused refusal reentered queue: %v", err)
}
}
})
}
}
func TestLocalOriginationRejectsMissingOrInvalidIdentity(t *testing.T) {
st, record := readyLocalOrigination(t)
if _, err := st.LocalPendingOriginationForCommand(nil); !errors.Is(err, sql.ErrNoRows) {
t.Fatalf("empty command lookup error=%v", err)
}
if _, err := st.LocalPendingOriginationForCommand(append(append([]byte(nil), record.Body...), ' ')); !errors.Is(err, sql.ErrNoRows) {
t.Fatalf("changed command bytes matched a pending execution: %v", err)
}
if _, err := st.LoadLocalOrigination("other-dispatcher", "execution-a"); !errors.Is(err, ErrLocalOriginationNotAuthorized) {
t.Fatalf("wrong Dispatcher identity error=%v", err)
}
if err := st.RefuseLocalOrigination("d-1", "execution-a", "unrecognized", time.Date(2026, 9, 21, 2, 0, 0, 0, time.UTC)); err == nil {
t.Fatal("invalid refusal reason was persisted")
}
if _, err := st.LocalOriginationDecision(""); !errors.Is(err, ErrLocalOriginationNotAuthorized) {
t.Fatalf("empty execution ID error=%v", err)
}
if _, err := st.LocalOriginationDecision("missing"); !errors.Is(err, sql.ErrNoRows) {
t.Fatalf("missing decision error=%v", err)
}
}