82 lines
2.9 KiB
Go
82 lines
2.9 KiB
Go
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")
|
|
}
|
|
}
|