test(e2e): make the distance journeys fail loudly, not skip quietly
The existing distance journeys all skip when no school has a published figure, which is right when the feature is off — and wrong when it is supposed to be on and is silently broken, because that shows up as a green run full of skips. The new gate fails in exactly that case. Feature state is read from the data, not from /api/flags: the public proxy denies that path on purpose, since it names unreleased features. Presence of the admission_distance key is the observable effect. Verified against staging, where the feature is currently on: the on-gate passes, the off-gate skips, the existing eight distance journeys are unaffected. One honest caveat — the /api/flags check passes on staging today because that image predates the endpoint, not because the denylist works. The denylist itself is covered by the jest unit test; this is defence in depth and becomes a real assertion once deployed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015mWQnpye9F299NVRCCSRvj
This commit is contained in:
1 parent
c3ba7aae0d
commit
4f01fbdedb
1 file changed
+74
@@ -1226,6 +1226,27 @@ const CUTOFF_CANDIDATE_URNS = [
|
|||||||
101099, 100553, 102574, 100769, // mixed
|
101099, 100553, 102574, 100769, // mixed
|
||||||
];
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the last-distance-offered feature is switched on here.
|
||||||
|
*
|
||||||
|
* Read from the data rather than from /api/flags, which the public proxy
|
||||||
|
* denies on purpose — the endpoint names unreleased features. The observable
|
||||||
|
* effect is the field's presence: the flag is off iff no candidate school
|
||||||
|
* carries an `admission_distance` key at all.
|
||||||
|
*
|
||||||
|
* The distinction that matters: `admission_distance: null` means this school
|
||||||
|
* has no published cut-off, and the key being ABSENT means cut-offs are not
|
||||||
|
* being published at all.
|
||||||
|
*/
|
||||||
|
async function distanceFeatureIsOn(page: Page): Promise<boolean> {
|
||||||
|
for (const urn of CUTOFF_CANDIDATE_URNS) {
|
||||||
|
const res = await page.request.get(`/api/schools/${urn}`);
|
||||||
|
if (!res.ok()) continue;
|
||||||
|
if ('admission_distance' in (await res.json())) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
async function schoolWithCutoff(page: Page) {
|
async function schoolWithCutoff(page: Page) {
|
||||||
for (const urn of CUTOFF_CANDIDATE_URNS) {
|
for (const urn of CUTOFF_CANDIDATE_URNS) {
|
||||||
const res = await page.request.get(`/api/schools/${urn}`);
|
const res = await page.request.get(`/api/schools/${urn}`);
|
||||||
@@ -1237,6 +1258,59 @@ async function schoolWithCutoff(page: Page) {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test('when the distance feature is on, a school with a cut-off is findable', async ({ page }) => {
|
||||||
|
/*
|
||||||
|
* The gate that stops the other distance journeys passing vacuously.
|
||||||
|
*
|
||||||
|
* They all skip when schoolWithCutoff() finds nothing, which is right when
|
||||||
|
* the feature is off — but it means a feature that is *supposed* to be on
|
||||||
|
* and is silently broken shows up as a green run full of skips. This test
|
||||||
|
* fails in that case.
|
||||||
|
*/
|
||||||
|
test.skip(!(await distanceFeatureIsOn(page)),
|
||||||
|
'the admission_distance flag is off in this environment');
|
||||||
|
|
||||||
|
expect(await schoolWithCutoff(page),
|
||||||
|
'the distance feature is on, but no candidate school has a cut-off — '
|
||||||
|
+ 'the flag is on and the data or the query behind it is broken')
|
||||||
|
.not.toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('with the distance feature off, the section is absent rather than empty', async ({ page }) => {
|
||||||
|
// Shipping dark means the page renders as it did before the feature existed,
|
||||||
|
// not as a feature with its content removed.
|
||||||
|
test.skip(await distanceFeatureIsOn(page),
|
||||||
|
'the admission_distance flag is on in this environment');
|
||||||
|
|
||||||
|
// A school that exists, found rather than hardcoded — a 404 page would
|
||||||
|
// satisfy the absent-heading assertion without proving anything.
|
||||||
|
//
|
||||||
|
// A plain loop, not Array.find: find's predicate is synchronous, so an async
|
||||||
|
// one returns a Promise, every Promise is truthy, and it would always hand
|
||||||
|
// back the first URN whether or not that school exists.
|
||||||
|
let urn: number | null = null;
|
||||||
|
for (const candidate of CUTOFF_CANDIDATE_URNS) {
|
||||||
|
if ((await page.request.get(`/api/schools/${candidate}`)).ok()) {
|
||||||
|
urn = candidate;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
expect(urn, 'no candidate school resolves in this environment').not.toBeNull();
|
||||||
|
|
||||||
|
await page.goto(`/school/${urn}`);
|
||||||
|
await expect(page.locator('h1')).toBeVisible();
|
||||||
|
|
||||||
|
await expect(page.getByRole('heading', { name: /How far away are you\?/ }))
|
||||||
|
.toHaveCount(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('/api/flags is not reachable from the public internet', async ({ page }) => {
|
||||||
|
// It names every unreleased feature and whether it is on. Next reads it
|
||||||
|
// server-side over the Docker network; the public proxy must deny it.
|
||||||
|
const res = await page.request.get('/api/flags');
|
||||||
|
expect(res.status()).toBe(404);
|
||||||
|
});
|
||||||
|
|
||||||
test('a published cut-off distance is shown with the year it belongs to', async ({ page }) => {
|
test('a published cut-off distance is shown with the year it belongs to', async ({ page }) => {
|
||||||
const found = await schoolWithCutoff(page);
|
const found = await schoolWithCutoff(page);
|
||||||
test.skip(found === null, 'no school in the sample has a published cut-off distance yet');
|
test.skip(found === null, 'no school in the sample has a published cut-off distance yet');
|
||||||
|
|||||||
Reference in new issue
Block a user