mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-06 10:59:59 +00:00
Nine roles enabled an MCP surface without a browser-side spec, so nothing
ever asked what the endpoint answers a caller with no credential at all.
Lift gitea's assertion into two shared registrars and register them per
role, and give each role the MCP_SERVICE_ENABLED flag they gate on.
mcpEndpointUrl now takes the base URL rather than reading APP_BASE_URL
unconditionally: jellyfin and zammad export their own base variable, and
declaring APP_BASE_URL for them would un-skip the shared guest persona,
which collapses itself on that variable being absent.
The env-gate scanner follows require("./x") into the shared harness so a
gate declared there counts as consumed, and strips block comments first
so service-gating.js's own doc examples do not credit EMAIL, SSO and OICD
to every role that imports it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
70 lines
2.8 KiB
JavaScript
70 lines
2.8 KiB
JavaScript
const { test, expect } = require("@playwright/test");
|
|
const { resolveTimeout } = require("./timeouts");
|
|
|
|
const { decodeDotenvQuotedValue, normalizeBaseUrl, runAdminFlow, runBiberFlow, runGuestFlow , expectHstsWhenTls, gotoOnion } = require("./personas");
|
|
test.use({ ignoreHTTPSErrors: true });
|
|
|
|
require("./test-mcp-guest").register();
|
|
|
|
const appBaseUrl = normalizeBaseUrl(process.env.APP_BASE_URL || "");
|
|
const canonicalDomain = decodeDotenvQuotedValue(process.env.CANONICAL_DOMAIN || "");
|
|
|
|
test.beforeEach(async ({ page }) => {
|
|
expect(appBaseUrl, "APP_BASE_URL must be set").toBeTruthy();
|
|
expect(canonicalDomain, "CANONICAL_DOMAIN must be set").toBeTruthy();
|
|
await page.context().clearCookies();
|
|
});
|
|
|
|
test("Listmonk front page is served under canonical domain with TLS", async ({ page }) => {
|
|
const response = await gotoOnion(page, `${appBaseUrl}/`);
|
|
expect(response, "Expected Listmonk response").toBeTruthy();
|
|
expect(response.status(), "Expected Listmonk front page status < 400").toBeLessThan(400);
|
|
expect(
|
|
response.url().includes(canonicalDomain),
|
|
`Expected canonical domain "${canonicalDomain}" to back the Listmonk URL`
|
|
).toBe(true);
|
|
const headers = response.headers();
|
|
expectHstsWhenTls(headers, appBaseUrl, "Listmonk");
|
|
});
|
|
|
|
test("Listmonk returns HTML content under canonical domain", async ({ request }) => {
|
|
const response = await request.get(`${appBaseUrl}/`, { timeout: resolveTimeout(30_000) });
|
|
expect(response.status(), "Expected Listmonk front page status < 400").toBeLessThan(400);
|
|
const contentType = response.headers()["content-type"] || "";
|
|
expect(
|
|
contentType.includes("text/html"),
|
|
`Expected HTML content-type, got "${contentType}"`
|
|
).toBe(true);
|
|
});
|
|
|
|
// Persona scenarios.
|
|
// Bodies live in the shared helper roles/test-e2e-playwright/files/personas
|
|
// 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, {
|
|
adminInteraction: async (interactivePage) => {
|
|
// web-app-listmonk admin-only interaction: open a management surface.
|
|
const link = interactivePage
|
|
.getByRole("link", { name: /^(campaigns|subscribers|lists|admin)$/i })
|
|
.first();
|
|
if (await link.isVisible().catch(() => false)) {
|
|
await link.click({ timeout: resolveTimeout(30_000) }).catch(() => {});
|
|
await interactivePage.waitForLoadState("domcontentloaded", { timeout: resolveTimeout(30_000) }).catch(() => {});
|
|
await expect(interactivePage.locator("body")).toContainText(
|
|
/campaign|subscriber|list|template|listmonk/i,
|
|
{ timeout: resolveTimeout(30_000) },
|
|
);
|
|
}
|
|
},
|
|
});
|
|
});
|