206 lines
7.8 KiB
TypeScript
206 lines
7.8 KiB
TypeScript
import { expect, test, type Page } from "@playwright/test";
|
||
|
||
const ORIGIN = "http://127.0.0.1:4173";
|
||
async function localOnly(page: Page) {
|
||
const external: string[] = [];
|
||
await page.route("**/*", async (route) => {
|
||
const url = new URL(route.request().url());
|
||
if (url.origin !== ORIGIN) {
|
||
external.push(url.href);
|
||
await route.abort();
|
||
} else await route.continue();
|
||
});
|
||
return external;
|
||
}
|
||
|
||
test("runs from a nested path without external requests", async ({ page }) => {
|
||
const errors: string[] = [];
|
||
page.on("pageerror", (error) => errors.push(error.message));
|
||
page.on("console", (message) => {
|
||
if (message.type() === "error") errors.push(message.text());
|
||
});
|
||
const external = await localOnly(page);
|
||
await page.goto("/deep/nested/rand/");
|
||
await expect(
|
||
page.getByRole("heading", { name: "Random Tools" }),
|
||
).toBeVisible();
|
||
expect(external).toEqual([]);
|
||
expect(errors).toEqual([]);
|
||
});
|
||
|
||
test("repeats deterministic output without external requests", async ({
|
||
page,
|
||
}) => {
|
||
const external = await localOnly(page);
|
||
await page.goto("/deep/nested/rand/");
|
||
await page.getByLabel("Random source").selectOption("deterministic");
|
||
await page
|
||
.getByRole("textbox", { name: "Seed", exact: true })
|
||
.fill("browser-recipe");
|
||
await page.getByLabel("Count").fill("5");
|
||
await page.getByRole("button", { name: "Generate numbers" }).click();
|
||
const result = page.locator(".result > pre");
|
||
const first = await result.textContent();
|
||
expect(first?.trim().split("\n")).toHaveLength(5);
|
||
await page.getByRole("button", { name: "Generate numbers" }).click();
|
||
await expect(result).toHaveText(first ?? "");
|
||
await expect(
|
||
page.getByText(/deterministic-non-cryptographic/u),
|
||
).toBeAttached();
|
||
expect(external).toEqual([]);
|
||
});
|
||
|
||
test("runs the local draw catalogue with recipe metadata", async ({ page }) => {
|
||
const external = await localOnly(page);
|
||
await page.goto("/deep/nested/rand/");
|
||
await page.getByLabel("Random source").selectOption("deterministic");
|
||
await page
|
||
.getByRole("textbox", { name: "Seed", exact: true })
|
||
.fill("draw-catalogue");
|
||
await page.getByRole("button", { name: "Draws" }).click();
|
||
|
||
await page.getByLabel("Result count").fill("7");
|
||
await page.getByRole("button", { name: "Generate draw" }).click();
|
||
await expect(page.locator(".result > pre")).toContainText(/Heads|Tails/u);
|
||
|
||
await page.getByLabel("Draw type").selectOption("cards");
|
||
await page.getByLabel("Cards to deal").fill("5");
|
||
await page.getByRole("button", { name: "Generate draw" }).click();
|
||
await expect(page.locator(".result > pre")).toContainText(/[♠♥♦♣]/u);
|
||
|
||
await page.getByLabel("Draw type").selectOption("sequence");
|
||
await page.getByLabel("Sequence minimum").fill("5");
|
||
await page.getByLabel("Sequence maximum (inclusive)").fill("8");
|
||
await page.getByRole("button", { name: "Generate draw" }).click();
|
||
const sequence = (await page.locator(".result > pre").textContent())
|
||
?.trim()
|
||
.split("\n")
|
||
.map(Number)
|
||
.sort((a, b) => a - b);
|
||
expect(sequence).toEqual([5, 6, 7, 8]);
|
||
|
||
await page.getByLabel("Draw type").selectOption("dates");
|
||
await page.getByLabel("Result count").fill("3");
|
||
await page.getByLabel("Start date (inclusive)").fill("2026-01-01");
|
||
await page.getByLabel("End date (inclusive)").fill("2026-01-31");
|
||
await page.getByLabel("Do not repeat dates").check();
|
||
await page.getByRole("button", { name: "Generate draw" }).click();
|
||
await expect(page.locator(".result > pre")).toContainText(/2026-01-/u);
|
||
|
||
await page.getByLabel("Draw type").selectOption("decimals");
|
||
await page.getByLabel("Result count").fill("2");
|
||
await page.getByLabel("Decimal places").fill("4");
|
||
await page.getByRole("button", { name: "Generate draw" }).click();
|
||
await expect(page.locator(".result > pre")).toContainText(/0\.\d{4}/u);
|
||
|
||
await page.getByLabel("Draw type").selectOption("coordinates");
|
||
await page.getByLabel("Result count").fill("2");
|
||
await page.getByLabel("Coordinate decimal places").fill("4");
|
||
await page.getByRole("button", { name: "Generate draw" }).click();
|
||
await expect(page.locator(".result > pre")).toContainText(
|
||
/-?\d+\.\d{4}, -?\d+\.\d{4}/u,
|
||
);
|
||
await expect(
|
||
page.getByText(/mathematical samples on a spherical model/u),
|
||
).toBeVisible();
|
||
expect(external).toEqual([]);
|
||
});
|
||
|
||
test("identifies normalized custom passphrase inputs without announcing output", async ({
|
||
page,
|
||
}) => {
|
||
const external = await localOnly(page);
|
||
await page.goto("/deep/nested/rand/");
|
||
await page.getByLabel("Random source").selectOption("deterministic");
|
||
await page
|
||
.getByRole("textbox", { name: "Seed", exact: true })
|
||
.fill("word-list-identity");
|
||
await page.getByRole("button", { name: "Passphrases" }).click();
|
||
await page
|
||
.getByLabel(/Optional custom word list/u)
|
||
.fill(" alpha \n\nbeta\n gamma ");
|
||
await page.getByRole("button", { name: "Generate passphrase" }).click();
|
||
|
||
const result = page.locator(".result");
|
||
await expect(result.getByRole("status")).toHaveText(
|
||
/Result ready: Passphrase/u,
|
||
);
|
||
await expect(result).not.toHaveAttribute("aria-live");
|
||
await result.getByText("Reproduction metadata").click();
|
||
await expect(result.locator("details pre")).toContainText(
|
||
/"normalizedCount": 3/u,
|
||
);
|
||
await expect(result.locator("details pre")).toContainText(
|
||
/"sha256": "[0-9a-f]{64}"/u,
|
||
);
|
||
await expect(result.locator("details pre")).toContainText(
|
||
/"customInputRequiredForReproduction": true/u,
|
||
);
|
||
expect(external).toEqual([]);
|
||
});
|
||
|
||
test("runs weighted draws and a local commit–reveal ceremony", async ({
|
||
page,
|
||
}) => {
|
||
const external = await localOnly(page);
|
||
await page.goto("/deep/nested/rand/");
|
||
await page.getByLabel("Random source").selectOption("deterministic");
|
||
await page
|
||
.getByRole("textbox", { name: "Seed", exact: true })
|
||
.fill("weighted");
|
||
await page.getByRole("button", { name: "Lists" }).click();
|
||
await page.getByLabel("Sampling model").selectOption("weighted");
|
||
await page
|
||
.getByRole("textbox", { name: /CSV rows/u })
|
||
.fill('"Alpha, Inc",10\nBeta,2\nGamma,1');
|
||
await page.getByLabel(/Sample count/u).fill("2");
|
||
await page.getByRole("button", { name: "Draw weighted sample" }).click();
|
||
const weighted = (await page.locator(".result > pre").textContent())
|
||
?.trim()
|
||
.split("\n");
|
||
expect(weighted).toHaveLength(2);
|
||
expect(
|
||
weighted?.every((value) => ["Alpha, Inc", "Beta", "Gamma"].includes(value)),
|
||
).toBe(true);
|
||
expect(new Set(weighted).size).toBe(2);
|
||
|
||
await page.getByRole("button", { name: "Commit–reveal" }).click();
|
||
await page.getByRole("button", { name: /Generate commitment/u }).click();
|
||
await expect(page.getByLabel("Public commitment")).toContainText(
|
||
/"commitment": "[A-Za-z0-9_-]{43}"/u,
|
||
);
|
||
await expect(page.getByLabel("Private reveal")).toContainText(
|
||
/"nonce": "[A-Za-z0-9_-]{43}"/u,
|
||
);
|
||
await page
|
||
.getByRole("button", { name: "Use this reveal in verifier" })
|
||
.click();
|
||
await page.getByRole("button", { name: /Verify reveals/u }).click();
|
||
await expect(page.getByText("1 reveals verified")).toBeVisible();
|
||
await expect(page.getByText(/Final Base64url seed:/u)).toBeVisible();
|
||
expect(external).toEqual([]);
|
||
});
|
||
|
||
test("serves the release identity and hardened headers", async ({
|
||
request,
|
||
}) => {
|
||
const index = await request.get("/deep/nested/rand/");
|
||
expect(index.ok()).toBe(true);
|
||
expect(index.headers()["content-security-policy"]).toContain(
|
||
"default-src 'self'",
|
||
);
|
||
expect(index.headers()["content-security-policy"]).toContain(
|
||
"connect-src 'self'",
|
||
);
|
||
expect(index.headers()["content-security-policy"]).not.toMatch(
|
||
/connect-src[^;]*https?:/u,
|
||
);
|
||
expect(await index.text()).not.toMatch(/\b(?:src|href)=["']\//u);
|
||
const manifest = await request.get("/deep/nested/rand/toolbox-app.json");
|
||
await expect(manifest.json()).resolves.toMatchObject({
|
||
id: "de.add-ideas.rand-tools",
|
||
version: "0.2.0",
|
||
entry: "./",
|
||
});
|
||
});
|