platform: modularize api/gui, add docs-tests-web foundation, and refresh root config
This commit is contained in:
166
selective-vpn-api/app/transport_policy_idempotency_test.go
Normal file
166
selective-vpn-api/app/transport_policy_idempotency_test.go
Normal file
@@ -0,0 +1,166 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user