+51
−8
+113
−0
| Original line number | Diff line number | Diff line |
|---|---|---|
| @@ -2,6 +2,7 @@ package cfsetup | ||
import (
|
||
"context"
|
||
"io"
|
||
"net/http"
|
||
"net/http/httptest"
|
||
"strings"
|
||
| @@ -116,3 +117,115 @@ func TestExecRunnerSatisfiesAPITokenVerifier(t *testing.T) { | ||
t.Fatal("ExecRunner must implement apiTokenVerifier so Setup verifies the token before side effects")
|
||
}
|
||
}
|
||
func recommendedPolicy(t *testing.T) AccessPolicy {
|
||
t.Helper()
|
||
p, err := BuildRecommendedPolicy("felis-recommended", AccessIdentity{Emails: []string{"[email protected]"}})
|
||
if err != nil {
|
||
t.Fatalf("build policy: %v", err)
|
||
}
|
||
return p
|
||
}
|
||
// TestExecRunnerCreateAccessPolicyUpdatesExisting is the regression for the
|
||
// fail-open swap: an Access app that already carries a policy of this name must
|
||
// be OVERWRITTEN with the guarded body. The old behavior swallowed
|
||
// "already exists" as success, so a re-run with a changed identity (or a
|
||
// hand-made broader policy) silently kept the old rule set while reporting a
|
||
// completed setup.
|
||
func TestExecRunnerCreateAccessPolicyUpdatesExisting(t *testing.T) {
|
||
var methods []string
|
||
var putBody string
|
||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||
methods = append(methods, r.Method)
|
||
switch {
|
||
case r.Method == http.MethodGet:
|
||
_, _ = w.Write([]byte(`{"success":true,"errors":[],"result":[{"id":"pol-1","name":"felis-recommended"}]}`))
|
||
case r.Method == http.MethodPut && strings.HasSuffix(r.URL.Path, "/policies/pol-1"):
|
||
b, _ := io.ReadAll(r.Body)
|
||
putBody = string(b)
|
||
_, _ = w.Write([]byte(`{"success":true,"errors":[],"result":{}}`))
|
||
default:
|
||
t.Fatalf("unexpected %s %s", r.Method, r.URL.Path)
|
||
}
|
||
}))
|
||
defer srv.Close()
|
||
r := &ExecRunner{APIToken: "t", AccountID: "acc", APIBase: srv.URL}
|
||
if err := r.CreateAccessPolicy(context.Background(), "app-1", recommendedPolicy(t)); err != nil {
|
||
t.Fatalf("CreateAccessPolicy = %v, want nil", err)
|
||
}
|
||
if len(methods) != 2 || methods[0] != http.MethodGet || methods[1] != http.MethodPut {
|
||
t.Fatalf("call sequence = %v, want [GET PUT] (lookup then overwrite)", methods)
|
||
}
|
||
if !strings.Contains(putBody, `"email"`) || !strings.Contains(putBody, "felis-recommended") {
|
||
t.Fatalf("PUT body must carry the full guarded policy, got %s", putBody)
|
||
}
|
||
}
|
||
// TestExecRunnerCreateAccessPolicyCreatesWhenAbsent: the happy path still POSTs
|
||
// when no policy of this name exists.
|
||
func TestExecRunnerCreateAccessPolicyCreatesWhenAbsent(t *testing.T) {
|
||
var methods []string
|
||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||
methods = append(methods, r.Method)
|
||
switch {
|
||
case r.Method == http.MethodGet:
|
||
_, _ = w.Write([]byte(`{"success":true,"errors":[],"result":[]}`))
|
||
case r.Method == http.MethodPost && strings.HasSuffix(r.URL.Path, "/policies"):
|
||
_, _ = w.Write([]byte(`{"success":true,"errors":[],"result":{}}`))
|
||
default:
|
||
t.Fatalf("unexpected %s %s", r.Method, r.URL.Path)
|
||
}
|
||
}))
|
||
defer srv.Close()
|
||
r := &ExecRunner{APIToken: "t", AccountID: "acc", APIBase: srv.URL}
|
||
if err := r.CreateAccessPolicy(context.Background(), "app-1", recommendedPolicy(t)); err != nil {
|
||
t.Fatalf("CreateAccessPolicy = %v, want nil", err)
|
||
}
|
||
if len(methods) != 2 || methods[0] != http.MethodGet || methods[1] != http.MethodPost {
|
||
t.Fatalf("call sequence = %v, want [GET POST]", methods)
|
||
}
|
||
}
|
||
// TestExecRunnerCreateAccessPolicyRaceFallsBackToUpdate: a POST losing to a
|
||
// concurrent creator ("already exists") must re-lookup and PUT, never swallow.
|
||
func TestExecRunnerCreateAccessPolicyRaceFallsBackToUpdate(t *testing.T) {
|
||
var methods []string
|
||
getCount := 0
|
||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||
methods = append(methods, r.Method)
|
||
switch r.Method {
|
||
case http.MethodGet:
|
||
getCount++
|
||
if getCount == 1 {
|
||
_, _ = w.Write([]byte(`{"success":true,"errors":[],"result":[]}`))
|
||
} else {
|
||
_, _ = w.Write([]byte(`{"success":true,"errors":[],"result":[{"id":"pol-9","name":"felis-recommended"}]}`))
|
||
}
|
||
case http.MethodPost:
|
||
w.WriteHeader(http.StatusBadRequest)
|
||
_, _ = w.Write([]byte(`{"success":false,"errors":[{"code":11015,"message":"policy_already_exists"}]}`))
|
||
case http.MethodPut:
|
||
_, _ = w.Write([]byte(`{"success":true,"errors":[],"result":{}}`))
|
||
default:
|
||
t.Fatalf("unexpected %s", r.Method)
|
||
}
|
||
}))
|
||
defer srv.Close()
|
||
r := &ExecRunner{APIToken: "t", AccountID: "acc", APIBase: srv.URL}
|
||
if err := r.CreateAccessPolicy(context.Background(), "app-1", recommendedPolicy(t)); err != nil {
|
||
t.Fatalf("CreateAccessPolicy = %v, want nil after race fallback", err)
|
||
}
|
||
want := []string{"GET", "POST", "GET", "PUT"}
|
||
if len(methods) != len(want) {
|
||
t.Fatalf("call sequence = %v, want %v", methods, want)
|
||
}
|
||
for i := range want {
|
||
if methods[i] != want[i] {
|
||
t.Fatalf("call sequence = %v, want %v", methods, want)
|
||
}
|
||
}
|
||
} |
||
Loading