Skip to content

Commit bd0dca1

Browse files
committed
test(dogfood): the retired /docs path answers the host's ordinary 404 on a booted showcase
The anonymous-deny surfaces file drops the viewer from its API-description endpoints and drives the retired path instead: anonymous and signed-in both get the same 404 body a path nothing ever served gets on that boot. Claude-Session: https://claude.ai/code/session_01LmjU2x1faLZ39DVTFyVca6 Co-authored-by: Claude <noreply@anthropic.com>
1 parent c8809d8 commit bd0dca1

1 file changed

Lines changed: 36 additions & 21 deletions

File tree

‎packages/qa/dogfood/test/showcase-anonymous-deny-surfaces.dogfood.test.ts‎

Lines changed: 36 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -67,12 +67,14 @@
6767
// ── Why the API-description endpoints are here (#22430) ────────────────────
6868
//
6969
// `@objectstack/rest`'s `registerOpenApiEndpoints` mounts the API-description
70-
// endpoints — the document and its viewer page — and until #22430 neither
71-
// handler asked who was calling: on a stock boot both answered an
72-
// unauthenticated caller 200, exactly as they answered a signed-in one, while
73-
// the record doors beside them answered 401. Both are driven below anonymously
74-
// (401 in the REST flat envelope, nothing of either served) and with a
75-
// signed-in member as the 200 control, on this one boot.
70+
// endpoint — the document — and until #22430 its handler did not ask who was
71+
// calling: on a stock boot it answered an unauthenticated caller 200, exactly
72+
// as it answered a signed-in one, while the record doors beside it answered
73+
// 401. It is driven below anonymously (401 in the REST flat envelope, nothing
74+
// of it served) and with a signed-in member as the 200 control, on this one
75+
// boot. Its viewer page, `/docs`, was the second endpoint until #25104 retired
76+
// it; the retired path is driven below too, and answers the host's ordinary
77+
// not-found to everyone.
7678
//
7779
// ── Why the analytics faces are here (#21061) ──────────────────────────────
7880
//
@@ -162,19 +164,22 @@ const ANALYTICS_FACES: readonly AnalyticsFace[] = [
162164

163165
// ── #22430 — the API-description endpoints (@objectstack/rest) ──────────────
164166
//
165-
// Both register on every base the server mounts; this boot mounts only the
167+
// It registers on every base the server mounts; this boot mounts only the
166168
// unscoped one (the showcase does not enable project scoping), and the
167-
// environment-scoped twin — the same handler closures — is pinned per base in
169+
// environment-scoped twin — the same handler closure — is pinned per base in
168170
// `packages/rest/src/rest-api-description-anonymous-deny.test.ts`.
169171
interface ApiDescriptionEndpoint {
170172
readonly endpoint: string;
171173
readonly path: string;
172174
}
173175
const API_DESCRIPTION_ENDPOINTS: readonly ApiDescriptionEndpoint[] = [
174176
{ endpoint: 'the API-description document', path: '/openapi.json' },
175-
{ endpoint: 'the API-description viewer', path: '/docs' },
176177
];
177178

179+
/** [#25104] The retired viewer page's path, and a path nothing ever served, for the not-found control. */
180+
const RETIRED_VIEWER_PATH = '/docs';
181+
const NEVER_SERVED_PATH = '/this-route-does-not-exist';
182+
178183
// ── #22432 — the translation faces (dispatcher-mounted; runtime domains/i18n.ts)
179184
//
180185
// One handler body serves every face, so the floor is its first statement. The
@@ -722,10 +727,10 @@ describe('showcase: anonymous posture is uniform across surfaces (#2567)', () =>
722727
// ── the API-description endpoints (@objectstack/rest) — #22430 ──────────
723728
//
724729
// The refusal is the handler's first act, so the anonymous body is the REST
725-
// seam's shared one, whole, and carries nothing of the document or the page.
726-
// The member control is the teeth: the same endpoint, with a session, is
727-
// served 200 on this boot, so the 401 is the floor's answer and the endpoint
728-
// is really mounted here.
730+
// seam's shared one, whole, and carries nothing of the document. The member
731+
// control is the teeth: the same endpoint, with a session, is served 200 on
732+
// this boot, so the 401 is the floor's answer and the endpoint is really
733+
// mounted here.
729734
it.each(API_DESCRIPTION_ENDPOINTS)('an anonymous caller on $endpoint is denied (401) and served nothing', async (ep) => {
730735
const r = await anon('GET', ep.path);
731736
expect(r.status, `${ep.endpoint}: an anonymous caller must be refused by the floor`).toBe(ANONYMOUS_DENY_STATUS);
@@ -744,13 +749,22 @@ describe('showcase: anonymous posture is uniform across surfaces (#2567)', () =>
744749
expect(Object.keys(body.paths ?? {}).length, 'the document describes the mounted surface').toBeGreaterThan(0);
745750
});
746751

747-
it('a signed-in member is served the API-description viewer page (200) — the control', async () => {
748-
const r = await stack.apiAs(memberToken, 'GET', '/docs');
749-
expect(r.status, 'an authenticated member must clear the floor and be served the viewer').toBe(200);
750-
expect(r.headers.get('content-type') ?? '', 'the viewer is an HTML page').toContain('text/html');
751-
// The page loads its sibling document on the same base — the request that,
752-
// from a signed-in browser, carries that browser's session.
753-
expect(await r.text(), 'the viewer points at its sibling document').toContain('data-url="/api/v1/openapi.json"');
752+
it('[#25104] the retired viewer page is routed NOWHERE — the host\'s ordinary 404 for everyone, not a 401', async () => {
753+
// Retired, the path must answer exactly what a path nothing ever served
754+
// answers on this boot, to an anonymous caller and to a member alike: a
755+
// 401 would mean a door is back (an auth floor only speaks for a door that
756+
// exists), and a 200 would mean the page is. The registrar-level absence
757+
// pin, on both route shapes, is `rest/src/rest-docs-viewer-retired.pin.test.ts`.
758+
const control = await anon('GET', NEVER_SERVED_PATH);
759+
expect(control.status, 'CONTROL: a path nothing serves is a 404 on this boot').toBe(404);
760+
const controlBody = await control.text();
761+
const anonRes = await anon('GET', RETIRED_VIEWER_PATH);
762+
expect(anonRes.status, 'the retired viewer path must be unrouted, not auth-refused').toBe(404);
763+
expect(await anonRes.text(), 'the retired path answers the ordinary not-found body').toBe(controlBody);
764+
const memberRes = await stack.apiAs(memberToken, 'GET', RETIRED_VIEWER_PATH);
765+
expect(memberRes.status, 'a session must not change the answer on a route that is gone').toBe(404);
766+
expect(memberRes.headers.get('content-type') ?? '', 'no page is served').not.toContain('text/html');
767+
expect(await memberRes.text(), 'the retired path answers the ordinary not-found body').toBe(controlBody);
754768
});
755769

756770
// ── the translation faces (dispatcher-mounted; runtime domains/i18n.ts) — #22432
@@ -888,7 +902,8 @@ describe('showcase: anonymous posture is uniform across surfaces (#2567)', () =>
888902
// [#21060] The two remaining REST families the checklist item names.
889903
{ seam: 'POST /batch', owner: '@objectstack/rest enforceAuth', family: 'rest-flat', call: () => anon('POST', BATCH, BATCH_PROBE_BODY) },
890904
{ seam: 'GET /security/explain', owner: '@objectstack/rest enforceAuth', family: 'rest-flat', call: () => anon('GET', EXPLAIN) },
891-
// [#22430] The API-description endpoints, labelled by endpoint.
905+
// [#22430] The API-description endpoint (one since #25104 retired the
906+
// viewer page), labelled by endpoint.
892907
...API_DESCRIPTION_ENDPOINTS.map((ep) => ({
893908
seam: ep.endpoint,
894909
owner: '@objectstack/rest registerOpenApiEndpoints (enforceAuth)',

0 commit comments

Comments
 (0)