| index a1a0134c7..5ceaf12fa 100644 |
| --- a/internal/console/economicspass_test.go |
| +++ b/internal/console/economicspass_test.go |
| @@ -619,9 +619,9 @@ func TestAccountTierStore(t *testing.T) { |
| // TestEconomicsWriteDoorsRejectNonPOST — a wrong-method request must be |
| // byte-identical to an unknown route, both signed out and signed in as a |
| // non-operator, before the operator gate, before CSRF. An operator POST |
| -// resolves the account by sqid (regardless of viewer, via |
| -// AccountAnyBySqid), sets its tier and redirects to |
| -// /fleet/economics/accounts. |
| +// resolves the account by sqid (via AccountBySqid — already |
| +// viewer-independent, since it filters deleted_at rather than membership), |
| +// sets its tier and redirects to /fleet/economics/accounts. |
| func TestEconomicsTierDoor(t *testing.T) { |
| fx := newConsoleFixture(t) |
| out := newBrowser(t) |
| @@ -629,10 +629,18 @@ func TestEconomicsTierDoor(t *testing.T) { |
| |
| const path = "/fleet/economics/tier" |
| |
| - // Signed out: GET and POST alike must be the ghost 404. |
| + // Signed out: GET is the ghost 404. |
| if status, _, body := get(t, out, fx.ts.URL+path); status != ghostStatus || body != ghostBody { |
| t.Fatalf("signed-out GET %s = %d %q, want the same 404 as a missing route (%d %q)", path, status, body, ghostStatus, ghostBody) |
| } |
| + // Signed out: POST is the same ghost 404 too (method check runs before |
| + // the session gate, but a signed-out POST must still be indistinguishable |
| + // from an unknown route — no session cookie means no CSRF token to send |
| + // at all). |
| + signedOutForm := url.Values{"csrf": {""}, "account": {fx.sqid}, "tier": {"pool"}} |
| + if status, _, body := postForm(t, out, fx.ts.URL+path, signedOutForm); status != ghostStatus || body != ghostBody { |
| + t.Fatalf("signed-out POST %s = %d %q, want the same 404 as a missing route (%d %q)", path, status, body, ghostStatus, ghostBody) |
| + } |
| |
| c := signInPaul(t, fx) |
| csrf := sessionToken(t, fx, c) |
| @@ -644,8 +652,8 @@ func TestEconomicsTierDoor(t *testing.T) { |
| |
| // Signed in, not an operator: POST is the same ghost 404 (gate before |
| // CSRF). |
| - form := url.Values{"csrf": {csrf}, "account": {fx.sqid}, "tier": {"pool"}} |
| - if status, _, body := postForm(t, c, fx.ts.URL+path, form); status != ghostStatus || body != ghostBody { |
| + opForm := url.Values{"csrf": {csrf}, "account": {fx.sqid}, "tier": {"pool"}} |
| + if status, _, body := postForm(t, c, fx.ts.URL+path, opForm); status != ghostStatus || body != ghostBody { |
| t.Fatalf("non-operator POST %s = %d %q, want the same 404 as a missing route (%d %q)", path, status, body, ghostStatus, ghostBody) |
| } |
| |
| @@ -658,12 +666,27 @@ func TestEconomicsTierDoor(t *testing.T) { |
| t.Fatalf("operator GET %s = %d %q, want the same 404 as a missing route (%d %q)", path, status, body, ghostStatus, ghostBody) |
| } |
| |
| + // Operator POST, wrong CSRF: 403, same shape TestScenarioSaveGateAndValidation |
| + // pins for the sibling doors — the gate runs AFTER the operator check, |
| + // so this is a real 403, not the ghost 404. |
| + badCSRFForm := url.Values{"csrf": {"not-the-real-token"}, "account": {fx.sqid}, "tier": {"pool"}} |
| + if status, _, _ := postForm(t, c, fx.ts.URL+path, badCSRFForm); status != http.StatusForbidden { |
| + t.Fatalf("operator POST with wrong CSRF = %d, want %d", status, http.StatusForbidden) |
| + } |
| + tiers, err := fx.st.AccountTiers() |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + if got := tiers[fx.sqid]; got != "" { |
| + t.Fatalf("account tier after a wrong-CSRF POST = %q, want unchanged %q", got, "") |
| + } |
| + |
| // Operator POST, happy path: sets the tier and redirects. |
| - status, loc, _ := postForm(t, c, fx.ts.URL+path, form) |
| + status, loc, _ := postForm(t, c, fx.ts.URL+path, opForm) |
| if status != http.StatusSeeOther || loc != "/fleet/economics/accounts" { |
| t.Fatalf("operator tier POST = %d -> %q, want 303 -> /fleet/economics/accounts", status, loc) |
| } |
| - tiers, err := fx.st.AccountTiers() |
| + tiers, err = fx.st.AccountTiers() |
| if err != nil { |
| t.Fatal(err) |
| } |