console: the mixed-version test proves its preconditions (#229)
The test could pass on 401s from a missing token row. It now requires the role rewrite to change exactly one row, checks that the token authenticates as deploy before the rewrite (positive control on the channels door), and requires every door's no-token answer to be the gate's own 401 "not signed in", so an unrouted path cannot pass as a door.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
1 file changed,
+16
−1
internal/console/deploymixed_test.go+16 −1
diff --git a/internal/console/deploymixed_test.go b/internal/console/deploymixed_test.go| index e2d03136c..b6f9662d3 100644 |
| --- a/internal/console/deploymixed_test.go |
| +++ b/internal/console/deploymixed_test.go |
| @@ -58,9 +58,18 @@ func TestUnknownStoredRoleIsNotSignedInAtEveryDoor(t *testing.T) { |
| if err != nil { |
| t.Fatal(err) |
| } |
| - if _, err := fx.st.db.Exec(`UPDATE cli_tokens SET role = 'futurerole' WHERE token_hash = ?`, hashToken(raw)); err != nil { |
| + // Positive control: before the role is rewritten the token authenticates |
| + // as deploy, so a deploy door does not answer 401. |
| + if pre := doRaw(t, http.MethodGet, fx.ts.URL+"/api/cli/apps/bab/hello/channels", raw); pre.status == http.StatusUnauthorized { |
| + t.Fatalf("deploy token was refused before the role change: %d %q", pre.status, pre.body) |
| + } |
| + res, err := fx.st.db.Exec(`UPDATE cli_tokens SET role = 'futurerole' WHERE token_hash = ?`, hashToken(raw)) |
| + if err != nil { |
| t.Fatal(err) |
| } |
| + if n, err := res.RowsAffected(); err != nil || n != 1 { |
| + t.Fatalf("role rewrite changed %d rows (err %v), want 1", n, err) |
| + } |
| if len(fx.srv.cliRoles) == 0 { |
| t.Fatal("no registered CLI doors to check") |
| } |
| @@ -72,6 +81,12 @@ func TestUnknownStoredRoleIsNotSignedInAtEveryDoor(t *testing.T) { |
| path = fill.ReplaceAllString(path, "x") |
| with := doRaw(t, method, fx.ts.URL+path, raw) |
| without := doRaw(t, method, fx.ts.URL+path, "") |
| + // The no-token side must be the gate's own refusal, so an unrouted |
| + // path (404/405 on both sides) cannot pass for a door. |
| + if without.status != http.StatusUnauthorized || !strings.Contains(without.body, "not signed in") { |
| + t.Errorf("%s: no token answered %d %q, want the gate's 401 not signed in", pattern, without.status, without.body) |
| + continue |
| + } |
| if with.status != without.status || with.body != without.body { |
| t.Errorf("%s: unknown role answered %d %q; no token answered %d %q", |
| pattern, with.status, with.body, without.status, without.body) |