§8 test cleanup (todo §8); pass over the §8 test accretion. Two genuine combines, the rest of §8's changes were already woven into existing tests as assertions (recoverHref→flow-view, parseJwks key-shape→jwks, ORY_TIMEOUT_SEC→config, empty-state→data-table) — no fat. (1) Deferred L3: plugins/scheduling/shifts.test.ts imported four deep src/* internals (chrome/context/guards/plugin), none the documented-stable surface — repointed all four to the src/plugin-api.ts barrel (the one contract boundary, which re-exports them), so the test models the dev/test story the contract preaches, exactly like shifts.ts does (no coverage change). (2) app.test.ts had two adjacent tests for the same surface (themed-auth GET dispatch): "themed flow init" + "already-signed-in sent home", the latter literally re-asserting the former's anonymous-init — merged into one "themed auth GET: anonymous inits a flow …; a signed-in user is sent home, except /settings", all assertions preserved. Left separate: the four distinct-stack E2E suites + app.test.ts's one-per-surface integration tests. Pure test refactor, no production code (no stability reviewer, per the §6/§7 precedent). 310 → 309 units; typecheck + tests green.
This commit is contained in:
@@ -2,10 +2,9 @@ import assert from "node:assert/strict";
|
|||||||
import type { IncomingMessage, ServerResponse } from "node:http";
|
import type { IncomingMessage, ServerResponse } from "node:http";
|
||||||
import { Readable } from "node:stream";
|
import { Readable } from "node:stream";
|
||||||
import test from "node:test";
|
import test from "node:test";
|
||||||
import type { PageChrome } from "../../src/chrome.ts";
|
// Import only from the plugin-api barrel — the same contract boundary shifts.ts uses (the host may
|
||||||
import type { RequestContext } from "../../src/context.ts";
|
// refactor any deeper src/* freely behind it); the test models the dev/test story the contract preaches.
|
||||||
import { GuardError } from "../../src/guards.ts";
|
import { GuardError, type PageChrome, type RequestContext, type RouteResult } from "../../src/plugin-api.ts";
|
||||||
import type { RouteResult } from "../../src/plugin.ts";
|
|
||||||
import {
|
import {
|
||||||
assertHttpUrl, buildFormModel, createShift, createUpstream, listShifts, newShiftForm, readInput,
|
assertHttpUrl, buildFormModel, createShift, createUpstream, listShifts, newShiftForm, readInput,
|
||||||
SHIFTS_PATH, type Shift, type ShiftInput, type ShiftsUpstream, UpstreamError, validate,
|
SHIFTS_PATH, type Shift, type ShiftInput, type ShiftsUpstream, UpstreamError, validate,
|
||||||
|
|||||||
@@ -413,39 +413,31 @@ function mockKratos(getFlow: KratosPublic["getFlow"]): KratosPublic {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
test("themed flow init: no ?flow= initialises one, relays Kratos' CSRF cookie, and an expired flow restarts", async (t) => {
|
// GET dispatch for the themed auth pages: the same handler branches on session presence.
|
||||||
const app = createApp({ kratos: mockKratos(async (_t, id) => { if (id === "stale") throw new KratosError("gone", 410, ""); return loginFlow(id); }) });
|
test("themed auth GET: anonymous inits a flow (CSRF relay, stale→restart); a signed-in user is sent home, except /settings", async (t) => {
|
||||||
|
const app = createApp({ jwks: staticJwks([ecJwk]), kratos: mockKratos(async (_t, id) => { if (id === "stale") throw new KratosError("gone", 410, ""); return loginFlow(id); }) });
|
||||||
await new Promise<void>((r) => app.listen(0, r));
|
await new Promise<void>((r) => app.listen(0, r));
|
||||||
t.after(() => app.close());
|
t.after(() => app.close());
|
||||||
const url = `http://localhost:${(app.address() as AddressInfo).port}`;
|
const url = `http://localhost:${(app.address() as AddressInfo).port}`;
|
||||||
|
|
||||||
|
// Anonymous, no ?flow= → init one + relay Kratos' CSRF cookie.
|
||||||
const init = await fetch(url + "/login", { redirect: "manual" });
|
const init = await fetch(url + "/login", { redirect: "manual" });
|
||||||
assert.equal(init.status, 303);
|
assert.equal(init.status, 303);
|
||||||
assert.equal(init.headers.get("location"), "/login?flow=new1");
|
assert.equal(init.headers.get("location"), "/login?flow=new1");
|
||||||
assert.match(init.headers.get("set-cookie") ?? "", /csrf_token=abc/);
|
assert.match(init.headers.get("set-cookie") ?? "", /csrf_token=abc/);
|
||||||
|
|
||||||
// A stale flow id (Kratos 410) bounces back to a fresh init.
|
// A stale flow id (Kratos 410) bounces back to a fresh init.
|
||||||
const stale = await fetch(url + "/login?flow=stale", { redirect: "manual" });
|
const stale = await fetch(url + "/login?flow=stale", { redirect: "manual" });
|
||||||
assert.equal(stale.status, 303);
|
assert.equal(stale.status, 303);
|
||||||
assert.equal(stale.headers.get("location"), "/login");
|
assert.equal(stale.headers.get("location"), "/login");
|
||||||
});
|
|
||||||
|
|
||||||
test("themed auth: an already-signed-in user is sent home from /login and /registration, not /settings", async (t) => {
|
// Already signed in → /login + /registration short-circuit home; /settings stays reachable (inits its flow).
|
||||||
const app = createApp({ jwks: staticJwks([ecJwk]), kratos: mockKratos(async (_t, id) => loginFlow(id)) });
|
|
||||||
await new Promise<void>((r) => app.listen(0, r));
|
|
||||||
t.after(() => app.close());
|
|
||||||
const url = `http://localhost:${(app.address() as AddressInfo).port}`;
|
|
||||||
const signedIn = { headers: { cookie: `${SESSION_COOKIE}=${mintJwt({ email: "a@b.c", exp: Math.floor(Date.now() / 1000) + 600, roles: [], sub: "u1" })}` }, redirect: "manual" as const };
|
const signedIn = { headers: { cookie: `${SESSION_COOKIE}=${mintJwt({ email: "a@b.c", exp: Math.floor(Date.now() / 1000) + 600, roles: [], sub: "u1" })}` }, redirect: "manual" as const };
|
||||||
|
|
||||||
for (const path of ["/login", "/registration"]) {
|
for (const path of ["/login", "/registration"]) {
|
||||||
const res = await fetch(url + path, signedIn);
|
const res = await fetch(url + path, signedIn);
|
||||||
assert.equal(res.status, 303, `${path} while signed in → 303`);
|
assert.equal(res.status, 303, `${path} while signed in → 303`);
|
||||||
assert.equal(res.headers.get("location"), "/");
|
assert.equal(res.headers.get("location"), "/");
|
||||||
}
|
}
|
||||||
// /settings stays reachable when signed in (inits its flow, not bounced home).
|
|
||||||
assert.equal((await fetch(url + "/settings", signedIn)).headers.get("location"), "/settings?flow=new1");
|
assert.equal((await fetch(url + "/settings", signedIn)).headers.get("location"), "/settings?flow=new1");
|
||||||
// Anonymous still gets the login flow (no short-circuit).
|
|
||||||
assert.equal((await fetch(url + "/login", { redirect: "manual" })).headers.get("location"), "/login?flow=new1");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("renders a fetched flow as the themed auth page: fields post straight to Kratos, errors surface", async (t) => {
|
test("renders a fetched flow as the themed auth page: fields post straight to Kratos, errors surface", async (t) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user