Files
go-sip/internal/dispatcher/local_flow_test.go
T

157 lines
5.8 KiB
Go

package dispatcher
import (
"context"
"encoding/json"
"errors"
"testing"
"time"
"git.ipao.vip/rogee/go-sip/contracts"
agentv1 "git.ipao.vip/rogee/go-sip/gen/agent/v1"
"git.ipao.vip/rogee/go-sip/internal/agent"
"git.ipao.vip/rogee/go-sip/internal/ai"
"git.ipao.vip/rogee/go-sip/internal/contract"
"git.ipao.vip/rogee/go-sip/internal/health"
"git.ipao.vip/rogee/go-sip/internal/store"
"git.ipao.vip/rogee/go-sip/internal/testfixture"
)
func TestLocalContractBackedFlowEvidence(t *testing.T) {
now := time.Date(2026, 9, 18, 0, 0, 30, 0, time.UTC)
artifactRaw, err := contracts.Read("examples/static-cell-artifact.json")
if err != nil {
t.Fatal(err)
}
if _, err := contract.ValidateStaticArtifact(artifactRaw, contract.StaticArtifactExpectation{
CellID: "cell-a",
Mode: "mock",
SourceRelease: "management-snapshot-1",
SourceDigest: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
ConfigSHA256: "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
AllowedEgressPoolIDs: []string{"egress-mock"},
RequiredTrunkIDs: []string{"trunk-mock"},
}); err != nil {
t.Fatal(err)
}
snapshotRaw, err := contracts.Read("examples/agent-version-asr-only.json")
if err != nil {
t.Fatal(err)
}
snapshot, err := ai.ValidateForMode(snapshotRaw, ai.ModeASROnly)
if err != nil {
t.Fatal(err)
}
authorizationRaw, err := contracts.Files.ReadFile("upstream/v1/examples/ai-authorization.json")
if err != nil {
t.Fatal(err)
}
if _, err := ai.ValidateAuthorization(authorizationRaw, snapshot, "tenant-1", "tenant-demo-key", "egress-mock", now); err != nil {
t.Fatal(err)
}
sample := (health.Sampler{Now: func() time.Time { return now }}).Sample(context.Background(), t.TempDir())
if sample.SampleFresh || sample.MissingReason == "" {
t.Fatalf("resource sample must remain explicitly partial/unknown: %+v", sample)
}
st, err := store.Open(":memory:")
if err != nil {
t.Fatal(err)
}
defer st.Close()
if err := st.BindDispatcherID(testfixture.DispatcherID); err != nil {
t.Fatal(err)
}
for _, scope := range []string{"tenant:tenant-demo-key", "global", "cell:cell-a"} {
if err := st.SetQuota(scope, 1); err != nil {
t.Fatal(err)
}
}
d, err := New(st, nil, func() time.Time { return now })
if err != nil {
t.Fatal(err)
}
callRaw, err := testfixture.Execute()
if err != nil {
t.Fatal(err)
}
if _, err := d.AcceptCommand(callRaw, testfixture.InboundKey("tenant-demo-key")); err != nil {
t.Fatal(err)
}
task, err := d.ReserveTask("tenant-demo-key", "reservation-local-flow", []string{"tenant:tenant-demo-key", "global", "cell:cell-a"})
if err != nil {
t.Fatal(err)
}
coordinator := NewAgentCoordinator(func() time.Time { return now })
status := agentStatusForLocalFlow()
client := startMockAgent(t, &status)
if err := coordinator.Register("agent-a", client); err != nil {
t.Fatal(err)
}
if _, err := coordinator.Activate(context.Background(), "agent-a", "cell-a", "boot-a", "epoch-a", 1); err != nil {
t.Fatal(err)
}
result, err := d.ExecuteReserved(context.Background(), coordinator, "agent-a", task, callRaw, "reservation-local-flow", snapshot.Digest)
if err != nil || result.Unknown || result.Permit == nil || result.Receipt == nil {
t.Fatalf("local execution result=%+v err=%v", result, err)
}
controlRaw, err := contracts.Files.ReadFile("upstream/" + contract.MQSourceCommit + "/examples/task-control.json")
if err != nil {
t.Fatal(err)
}
var control map[string]any
if err := json.Unmarshal(controlRaw, &control); err != nil {
t.Fatal(err)
}
control["dispatcher_id"], control["tenant_id"], control["tenant_key"] = testfixture.DispatcherID, task.TenantID, task.TenantKey
controlIssued := time.Now().UTC()
control["issued_at"], control["not_after"] = controlIssued.Format(time.RFC3339Nano), controlIssued.Add(time.Minute).Format(time.RFC3339Nano)
payload := control["payload"].(map[string]any)
payload["task_id"], payload["expected_task_revision"] = task.TaskID, task.TaskRevision
controlRaw, err = json.Marshal(control)
if err != nil {
t.Fatal(err)
}
if err := d.AcceptMQMessage(controlRaw, testfixture.InboundKey(task.TenantKey)); err != nil {
t.Fatal(err)
}
if count, err := d.ProcessTaskControls(context.Background(), lostControlReply{coordinator}, 10); err == nil || count != 0 {
t.Fatal("lost reply incorrectly recorded applied")
}
if count, err := d.ProcessTaskControls(context.Background(), coordinator, 10); err != nil || count != 1 {
t.Fatalf("control recovery: count=%d err=%v", count, err)
}
if count, err := d.ProcessTaskControls(context.Background(), coordinator, 10); err != nil || count != 0 {
t.Fatal("applied target repeated")
}
controlled, err := st.FindTask(task.TenantID, task.TaskID)
if err != nil || controlled.Status != "paused" || controlled.TaskRevision != task.TaskRevision+1 {
t.Fatalf("control result: %+v %v", controlled, err)
}
event, err := (agent.EventWriter{DispatcherID: testfixture.DispatcherID, TenantID: "tenant-1", TenantKey: "tenant-demo-key", TraceID: "trace-local"}).TranscriptUpdated(now, "event-local", "call-local", "turn-local", "segment-local", "customer", "hello", 1, true, 0, 100)
if err != nil {
t.Fatal(err)
}
if err := contract.ValidateEvent(event); err != nil {
t.Fatal(err)
}
}
type lostControlReply struct{ coordinator *AgentCoordinator }
func (c lostControlReply) Control(ctx context.Context, id string, binding *agentv1.ExecutionBinding, action agentv1.ControlAction, policy agentv1.ActiveCallPolicy) (*agentv1.ApplyTaskControlResponse, error) {
if _, err := c.coordinator.Control(ctx, id, binding, action, policy); err != nil {
return nil, err
}
return nil, errors.New("injected loss of applied control reply")
}
func agentStatusForLocalFlow() agentv1.AgentStatus {
return agentv1.AgentStatus{AgentId: "agent-a", CellId: "cell-a"}
}