console: pin that operator status buys nothing on the member email doors
Fix round 2's gap: emailOperatorToken was only ever exercised against the pause door, so a regression that leaked I3's IsOperator gate onto one of the other eight member doors would have shipped unnoticed. Added TestOperatorStatusBuysNothingOnMemberDoors — a fleet operator who is NOT a member of the target account must get the same byte-identical "404 page not found\n" a stranger gets on declare (write) and status (read). Verified by mutation: an IsOperator short-circuit ahead of apiAccountApp on the declare door made the test fail; reverted.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 file changed,
+27
−0
internal/console/apimail_test.go+27 −0
diff --git a/internal/console/apimail_test.go b/internal/console/apimail_test.go| index 7b2295c1c..f0549173b 100644 |
| --- a/internal/console/apimail_test.go |
| +++ b/internal/console/apimail_test.go |
| @@ -262,6 +262,33 @@ func TestEmailDoorsAre404WithNoProvisioner(t *testing.T) { |
| } |
| } |
| +// TestOperatorStatusBuysNothingOnMemberDoors: I3 moved the pause door off |
| +// apiAccountApp's member gate onto store.IsOperator — this proves that |
| +// move did not leak into the other eight. A fleet operator (IsOperator |
| +// true) who is NOT a member of the target account gets the exact same |
| +// byte-identical 404 an ordinary stranger gets on a WRITE (declare) and a |
| +// READ (status) member door. Operator status must buy nothing here. |
| +func TestOperatorStatusBuysNothingOnMemberDoors(t *testing.T) { |
| + fx, _ := emailFixture(t, "code-email-operator-not-member") |
| + ops := emailOperatorToken(t, fx, "code-email-operator-not-member-ops") |
| + |
| + status, body := postRaw(t, emailURL(fx, "titogo"), map[string]any{"from": "hello@example.com"}, ops) |
| + if status != http.StatusNotFound { |
| + t.Fatalf("declare by a non-member operator = %d %q, want 404", status, body) |
| + } |
| + if body != "404 page not found\n" { |
| + t.Fatalf("declare refusal body = %q, want the stdlib http.NotFound output", body) |
| + } |
| + |
| + status, body = getRaw(t, emailURL(fx, "titogo"), ops) |
| + if status != http.StatusNotFound { |
| + t.Fatalf("status by a non-member operator = %d %q, want 404", status, body) |
| + } |
| + if body != "404 page not found\n" { |
| + t.Fatalf("status refusal body = %q, want the stdlib http.NotFound output", body) |
| + } |
| +} |
| + |
| // ---- declare and its changed-gating ------------------------------------ |
| // TestAPIEmailDeclareWritesTheRecord: the happy path. From is required (a |