package control import ( "encoding/json" "net/http" "net/http/httptest" "strings" "testing" "git.ipao.vip/rogee/go-sip/contracts" "git.ipao.vip/rogee/go-sip/internal/store" ) func setupHandler(t *testing.T) (*Handler, *store.Store) { t.Helper() s, err := store.Open(":memory:") if 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) } h := &Handler{Store: s, BearerToken: "test-token"} t.Cleanup(func() { _ = s.Close() }) return h, s } func request(h http.Handler, method, path, body string) *httptest.ResponseRecorder { r := httptest.NewRequest(method, path, strings.NewReader(body)) r.Header.Set("Authorization", "Bearer test-token") r.Header.Set("X-Tenant-ID", "tenant-demo") r.Header.Set("X-Request-ID", "req-1") r.Header.Set("Idempotency-Key", "idem-1") w := httptest.NewRecorder() h.ServeHTTP(w, r) return w } func TestControlEndpointUsesCASAndStrictJSON(t *testing.T) { h, _ := setupHandler(t) w := request(h, http.MethodPost, "/internal/v1/outbound/tasks/task-demo/controls", `{"command_id":"ctrl-1","action":"pause","expected_task_revision":1,"reason":"operator"}`) if w.Code != http.StatusAccepted { t.Fatalf("status=%d body=%s", w.Code, w.Body.String()) } w = request(h, http.MethodPost, "/internal/v1/outbound/tasks/task-demo/controls", `{"command_id":"ctrl-2","action":"resume","expected_task_revision":99,"reason":"stale"}`) if w.Code != http.StatusConflict { t.Fatalf("status=%d body=%s", w.Code, w.Body.String()) } w = request(h, http.MethodPost, "/internal/v1/outbound/tasks/task-demo/controls", `{"command_id":"ctrl-3","action":"pause","expected_task_revision":1,"reason":"operator","extra":true}`) if w.Code != http.StatusBadRequest { t.Fatalf("strict JSON status=%d body=%s", w.Code, w.Body.String()) } } func TestCommandQueryAndReplayAreTenantScopedAndDurable(t *testing.T) { h, s := setupHandler(t) w := request(h, http.MethodGet, "/internal/v1/outbound/commands/cmd_demo_001", "") if w.Code != http.StatusOK { t.Fatalf("query status=%d body=%s", w.Code, w.Body.String()) } var body map[string]any if err := json.Unmarshal(w.Body.Bytes(), &body); err != nil || body["command_id"] != "cmd_demo_001" || body["task_id"] != "task-demo" || body["execution_id"] != "exec_demo_001" || body["aggregate_version"] != float64(1) { t.Fatalf("query body=%s err=%v", w.Body.String(), err) } w = request(h, http.MethodPost, "/internal/v1/outbound/commands/cmd_demo_001/replays", `{"command_id":"replay-1","reason":"repair"}`) if w.Code != http.StatusAccepted { t.Fatalf("replay status=%d body=%s", w.Code, w.Body.String()) } rows, err := s.DB().Query(`SELECT status FROM outbox WHERE event_id = 'replay-idem-1'`) if err != nil { t.Fatal(err) } defer rows.Close() if !rows.Next() { t.Fatal("replay was not persisted to outbox") } }