mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-06 10:59:59 +00:00
web-svc-logout has no surface (personas blocked), so its only check is an anonymous GET of each logout-target's canonical URL asserting the HTML contains logout.js. For an oauth2-proxy-gated app that GET 302-redirects to the Keycloak login page, which cannot carry the app's injected logout.js, so the assertion was a false negative. Mark each target with is_proxy_gated (via the sso lookup) in playwright.env.j2 and test.skip gated targets in the spec; their logout.js is verified by that app's own authenticated persona e2e. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
88 lines
3.0 KiB
JavaScript
88 lines
3.0 KiB
JavaScript
const { test, expect } = require("@playwright/test");
|
|
|
|
const { assertInjectedAssetLoadsWithoutCspBlock, decodeDotenvQuotedValue, runAdminFlow, runBiberFlow, runGuestFlow } = require("./personas");
|
|
|
|
test.use({ ignoreHTTPSErrors: true });
|
|
|
|
const appBaseUrl = decodeDotenvQuotedValue(process.env.APP_BASE_URL);
|
|
const canonicalDomain = decodeDotenvQuotedValue(process.env.CANONICAL_DOMAIN);
|
|
const cdnBaseUrl = decodeDotenvQuotedValue(process.env.CDN_BASE_URL || "");
|
|
|
|
const cdnAssetHosts = [cdnBaseUrl]
|
|
.filter(Boolean)
|
|
.map((url) => {
|
|
try {
|
|
return new URL(url).host.toLowerCase();
|
|
} catch {
|
|
return null;
|
|
}
|
|
})
|
|
.filter(Boolean);
|
|
|
|
test.beforeEach(() => {
|
|
expect(appBaseUrl, "APP_BASE_URL must be set in the Playwright env file").toBeTruthy();
|
|
expect(canonicalDomain, "CANONICAL_DOMAIN must be set in the Playwright env file").toBeTruthy();
|
|
expect(
|
|
cdnAssetHosts.length,
|
|
"CDN_BASE_URL must be set in the Playwright env file (logout.js is served from the CDN host)"
|
|
).toBeGreaterThan(0);
|
|
});
|
|
|
|
const logoutTargetRoles = (() => {
|
|
const raw = process.env.LOGOUT_TARGET_ROLES_JSON || "[]";
|
|
try {
|
|
const parsed = JSON.parse(raw);
|
|
return Array.isArray(parsed) ? parsed : [];
|
|
} catch {
|
|
return [];
|
|
}
|
|
})();
|
|
|
|
for (const target of logoutTargetRoles) {
|
|
test(`universal-logout: ${target.id} actually loads logout.js without CSP block`, async ({ page }) => {
|
|
test.skip(
|
|
Boolean(target.is_proxy_gated),
|
|
`${target.id} is oauth2-proxy-gated: its anonymous landing is the IdP login page, so the injected logout.js is verified by that app's own authenticated persona e2e, not this anonymous check`
|
|
);
|
|
expect(
|
|
target.canonical_url,
|
|
`Expected canonical_url in LOGOUT_TARGET_ROLES_JSON entry for ${target.id}`
|
|
).toBeTruthy();
|
|
const url = `${target.canonical_url.replace(/\/$/, "")}/`;
|
|
|
|
const referenceResp = await page.request.get(url, { ignoreHTTPSErrors: true });
|
|
expect(
|
|
referenceResp.status(),
|
|
`${target.id}: GET ${url} returned ${referenceResp.status()} — surface must respond for the logout assertion to be meaningful`
|
|
).toBeLessThan(500);
|
|
const referenceHtml = await referenceResp.text();
|
|
expect(
|
|
referenceHtml,
|
|
`${target.id}: HTML response from ${url} does not contain a 'logout.js' script reference`
|
|
).toContain("logout.js");
|
|
|
|
await assertInjectedAssetLoadsWithoutCspBlock(page, {
|
|
url,
|
|
hostCandidates: cdnAssetHosts,
|
|
resourceTypes: ["script"],
|
|
label: target.id,
|
|
});
|
|
});
|
|
}
|
|
|
|
// Persona scenarios.
|
|
// Bodies live in the shared helper roles/test-e2e-playwright/files/personas.js
|
|
// so every role's persona flow stays consistent.
|
|
|
|
test("guest: public-landing → auth chain → never authenticated", async ({ page }) => {
|
|
await runGuestFlow(page);
|
|
});
|
|
|
|
test("biber: app → universal logout", async ({ page }) => {
|
|
await runBiberFlow(page);
|
|
});
|
|
|
|
test("administrator: app → universal logout", async ({ page }) => {
|
|
await runAdminFlow(page);
|
|
});
|