167 lines
6.7 KiB
Go
167 lines
6.7 KiB
Go
package app
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func withTransportPolicyMutationTestPaths(t *testing.T) {
|
|
t.Helper()
|
|
tmp := t.TempDir()
|
|
|
|
prevClients := transportClientsStatePath
|
|
prevIfaces := transportInterfacesStatePath
|
|
prevPolicy := transportPolicyStatePath
|
|
prevPolicySnap := transportPolicySnapshotPath
|
|
prevPlan := transportPolicyPlanStatePath
|
|
prevRuntime := transportPolicyRuntimeStatePath
|
|
prevRuntimeSnap := transportPolicyRuntimeSnapPath
|
|
prevOwners := transportOwnershipStatePath
|
|
prevConflicts := transportConflictsStatePath
|
|
prevIdem := transportPolicyIdempotencyStatePath
|
|
|
|
transportClientsStatePath = filepath.Join(tmp, "transport-clients.json")
|
|
transportInterfacesStatePath = filepath.Join(tmp, "transport-interfaces.json")
|
|
transportPolicyStatePath = filepath.Join(tmp, "transport-policies.json")
|
|
transportPolicySnapshotPath = filepath.Join(tmp, "transport-policies.prev.json")
|
|
transportPolicyPlanStatePath = filepath.Join(tmp, "transport-policies.plan.json")
|
|
transportPolicyRuntimeStatePath = filepath.Join(tmp, "transport-policies.runtime.json")
|
|
transportPolicyRuntimeSnapPath = filepath.Join(tmp, "transport-policies.runtime.prev.json")
|
|
transportOwnershipStatePath = filepath.Join(tmp, "transport-ownership.json")
|
|
transportConflictsStatePath = filepath.Join(tmp, "transport-conflicts.json")
|
|
transportPolicyIdempotencyStatePath = filepath.Join(tmp, "transport-policy-idempotency.json")
|
|
|
|
t.Cleanup(func() {
|
|
transportClientsStatePath = prevClients
|
|
transportInterfacesStatePath = prevIfaces
|
|
transportPolicyStatePath = prevPolicy
|
|
transportPolicySnapshotPath = prevPolicySnap
|
|
transportPolicyPlanStatePath = prevPlan
|
|
transportPolicyRuntimeStatePath = prevRuntime
|
|
transportPolicyRuntimeSnapPath = prevRuntimeSnap
|
|
transportOwnershipStatePath = prevOwners
|
|
transportConflictsStatePath = prevConflicts
|
|
transportPolicyIdempotencyStatePath = prevIdem
|
|
})
|
|
}
|
|
|
|
func TestTransportPolicyApplyIdempotencyReplay(t *testing.T) {
|
|
withTransportPolicyMutationTestPaths(t)
|
|
|
|
body := `{"base_revision":0,"intents":[]}`
|
|
req1 := httptest.NewRequest(http.MethodPost, "/api/v1/transport/policies/apply", strings.NewReader(body))
|
|
req1.Header.Set("Idempotency-Key", "apply-replay-1")
|
|
rec1 := httptest.NewRecorder()
|
|
handleTransportPoliciesApplyExec(rec1, req1)
|
|
if rec1.Code != http.StatusOK {
|
|
t.Fatalf("unexpected status: %d body=%s", rec1.Code, rec1.Body.String())
|
|
}
|
|
var resp1 TransportPolicyResponse
|
|
if err := json.Unmarshal(rec1.Body.Bytes(), &resp1); err != nil {
|
|
t.Fatalf("decode first apply response: %v", err)
|
|
}
|
|
if !resp1.OK || resp1.PolicyRevision != 1 || strings.TrimSpace(resp1.ApplyID) == "" {
|
|
t.Fatalf("unexpected first apply response: %#v", resp1)
|
|
}
|
|
|
|
req2 := httptest.NewRequest(http.MethodPost, "/api/v1/transport/policies/apply", strings.NewReader(body))
|
|
req2.Header.Set("Idempotency-Key", "apply-replay-1")
|
|
rec2 := httptest.NewRecorder()
|
|
handleTransportPoliciesApplyExec(rec2, req2)
|
|
if rec2.Code != http.StatusOK {
|
|
t.Fatalf("unexpected replay status: %d body=%s", rec2.Code, rec2.Body.String())
|
|
}
|
|
var resp2 TransportPolicyResponse
|
|
if err := json.Unmarshal(rec2.Body.Bytes(), &resp2); err != nil {
|
|
t.Fatalf("decode replay apply response: %v", err)
|
|
}
|
|
if !resp2.OK || resp2.PolicyRevision != 1 || resp2.ApplyID != resp1.ApplyID {
|
|
t.Fatalf("unexpected replay response: first=%#v second=%#v", resp1, resp2)
|
|
}
|
|
|
|
current := loadTransportPolicyState()
|
|
if current.Revision != 1 {
|
|
t.Fatalf("policy revision must remain 1 after replay, got %d", current.Revision)
|
|
}
|
|
idem := loadTransportPolicyIdempotencyState()
|
|
if len(idem.Items) != 1 {
|
|
t.Fatalf("expected one idempotency record, got %#v", idem)
|
|
}
|
|
}
|
|
|
|
func TestTransportPolicyApplyIdempotencyRejectsPayloadReuse(t *testing.T) {
|
|
withTransportPolicyMutationTestPaths(t)
|
|
|
|
req1 := httptest.NewRequest(http.MethodPost, "/api/v1/transport/policies/apply", strings.NewReader(`{"base_revision":0,"intents":[]}`))
|
|
req1.Header.Set("Idempotency-Key", "apply-conflict-1")
|
|
rec1 := httptest.NewRecorder()
|
|
handleTransportPoliciesApplyExec(rec1, req1)
|
|
|
|
req2 := httptest.NewRequest(http.MethodPost, "/api/v1/transport/policies/apply", strings.NewReader(`{"base_revision":1,"intents":[]}`))
|
|
req2.Header.Set("Idempotency-Key", "apply-conflict-1")
|
|
rec2 := httptest.NewRecorder()
|
|
handleTransportPoliciesApplyExec(rec2, req2)
|
|
if rec2.Code != http.StatusOK {
|
|
t.Fatalf("unexpected conflict status: %d body=%s", rec2.Code, rec2.Body.String())
|
|
}
|
|
var resp2 TransportPolicyResponse
|
|
if err := json.Unmarshal(rec2.Body.Bytes(), &resp2); err != nil {
|
|
t.Fatalf("decode conflict apply response: %v", err)
|
|
}
|
|
if resp2.OK || resp2.Code != "IDEMPOTENCY_KEY_REUSED" {
|
|
t.Fatalf("unexpected idempotency conflict response: %#v", resp2)
|
|
}
|
|
|
|
current := loadTransportPolicyState()
|
|
if current.Revision != 1 {
|
|
t.Fatalf("policy revision must remain 1 after conflicting replay, got %d", current.Revision)
|
|
}
|
|
}
|
|
|
|
func TestTransportPolicyRollbackIdempotencyReplay(t *testing.T) {
|
|
withTransportPolicyMutationTestPaths(t)
|
|
|
|
applyReq := httptest.NewRequest(http.MethodPost, "/api/v1/transport/policies/apply", strings.NewReader(`{"base_revision":0,"intents":[]}`))
|
|
applyRec := httptest.NewRecorder()
|
|
handleTransportPoliciesApplyExec(applyRec, applyReq)
|
|
|
|
req1 := httptest.NewRequest(http.MethodPost, "/api/v1/transport/policies/rollback", strings.NewReader(`{"base_revision":1}`))
|
|
req1.Header.Set("Idempotency-Key", "rollback-replay-1")
|
|
rec1 := httptest.NewRecorder()
|
|
handleTransportPoliciesRollbackExec(rec1, req1)
|
|
if rec1.Code != http.StatusOK {
|
|
t.Fatalf("unexpected rollback status: %d body=%s", rec1.Code, rec1.Body.String())
|
|
}
|
|
var resp1 TransportPolicyResponse
|
|
if err := json.Unmarshal(rec1.Body.Bytes(), &resp1); err != nil {
|
|
t.Fatalf("decode first rollback response: %v", err)
|
|
}
|
|
if !resp1.OK || resp1.PolicyRevision != 2 || strings.TrimSpace(resp1.ApplyID) == "" {
|
|
t.Fatalf("unexpected first rollback response: %#v", resp1)
|
|
}
|
|
|
|
req2 := httptest.NewRequest(http.MethodPost, "/api/v1/transport/policies/rollback", strings.NewReader(`{"base_revision":1}`))
|
|
req2.Header.Set("Idempotency-Key", "rollback-replay-1")
|
|
rec2 := httptest.NewRecorder()
|
|
handleTransportPoliciesRollbackExec(rec2, req2)
|
|
if rec2.Code != http.StatusOK {
|
|
t.Fatalf("unexpected rollback replay status: %d body=%s", rec2.Code, rec2.Body.String())
|
|
}
|
|
var resp2 TransportPolicyResponse
|
|
if err := json.Unmarshal(rec2.Body.Bytes(), &resp2); err != nil {
|
|
t.Fatalf("decode replay rollback response: %v", err)
|
|
}
|
|
if !resp2.OK || resp2.PolicyRevision != 2 || resp2.ApplyID != resp1.ApplyID {
|
|
t.Fatalf("unexpected rollback replay response: first=%#v second=%#v", resp1, resp2)
|
|
}
|
|
|
|
current := loadTransportPolicyState()
|
|
if current.Revision != 2 {
|
|
t.Fatalf("policy revision must remain 2 after rollback replay, got %d", current.Revision)
|
|
}
|
|
}
|