Files
zemion 38fa45dbaa
Verify / verify (push) Canceled after 0s
Release Privacy Tools 0.2.0
2026-09-02 10:09:14 +02:00

183 lines
6.1 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { expect, test, type Page } from "@playwright/test";
import { pngFixture, tiffFixture } from "../fixtures/images";
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/privacy/");
await expect(
page.getByRole("heading", { name: "Privacy Tools" }),
).toBeVisible();
expect(external).toEqual([]);
expect(errors).toEqual([]);
});
test("serves the release identity and hardened headers", async ({
request,
}) => {
const index = await request.get("/deep/nested/privacy/");
expect(index.ok()).toBe(true);
expect(index.headers()["content-security-policy"]).toContain(
"default-src 'self'",
);
expect(await index.text()).not.toMatch(/\b(?:src|href)=["']\//u);
const manifest = await request.get("/deep/nested/privacy/toolbox-app.json");
await expect(manifest.json()).resolves.toMatchObject({
id: "de.add-ideas.privacy-tools",
version: "0.2.0",
entry: "./",
});
});
test("inspects and independently verifies a re-encoded PNG without network access", 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/privacy/");
await page.locator('input[type="file"]').setInputFiles([
{
name: "metadata-fixture.png",
mimeType: "image/png",
buffer: Buffer.from(pngFixture()),
},
{
name: "inventory-only.pdf",
mimeType: "application/pdf",
buffer: Buffer.from("%PDF-1.7\n% fixture"),
},
]);
await expect(
page.getByRole("heading", { name: "Batch inventory" }),
).toBeVisible();
await expect(page.getByText("Alice PNG").first()).toBeVisible();
await expect(page.getByText("PNG Alice").first()).toBeVisible();
await expect(page.getByText("inventory-only.pdf").first()).toBeVisible();
await expect(
page.getByRole("heading", { name: "Selective-removal policy" }),
).toBeVisible();
await expect(page.getByText("inspect-only", { exact: true })).toBeVisible();
await page.getByLabel("Policy profile").selectOption("location-only");
await expect(
page.getByText("policy-not-executable", { exact: true }),
).toBeVisible();
await page.getByRole("button", { name: "Re-encode & verify" }).click();
await expect(page.getByText("Mandatory output re-scan")).toBeVisible();
await expect(page.locator(".verification")).not.toHaveClass(/is-failed/u);
await expect(
page.getByRole("button", { name: "Download re-encoded output" }),
).toBeEnabled();
const imageDownload = page.waitForEvent("download");
await page
.getByRole("button", { name: "Download re-encoded output" })
.click();
expect((await imageDownload).suggestedFilename()).toBe("image-001.clean.png");
const safeReportDownload = page.waitForEvent("download");
await page
.getByRole("button", { name: "Download safe-share report" })
.click();
expect((await safeReportDownload).suggestedFilename()).toBe(
"privacy-tools-safe-share-report.json",
);
const detailedReportDownload = page.waitForEvent("download");
await page.getByRole("button", { name: "Download detailed report" }).click();
expect((await detailedReportDownload).suggestedFilename()).toBe(
"privacy-tools-report.json",
);
const archiveDownload = page.waitForEvent("download");
await page
.getByRole("button", {
name: "Download safe-share ZIP with 1 re-encoded image + report",
})
.click();
expect((await archiveDownload).suggestedFilename()).toBe(
"privacy-tools-re-encoded-images.zip",
);
expect(external).toEqual([]);
expect(errors).toEqual([]);
});
test("normalizes EXIF orientation while re-encoding JPEG pixels", async ({
page,
}) => {
const external = await localOnly(page);
await page.goto("/deep/nested/privacy/");
const encoded = await page.evaluate(async () => {
const canvas = document.createElement("canvas");
canvas.width = 2;
canvas.height = 1;
const context = canvas.getContext("2d");
if (!context) throw new Error("Missing test canvas");
context.fillStyle = "#ff0000";
context.fillRect(0, 0, 1, 1);
context.fillStyle = "#0000ff";
context.fillRect(1, 0, 1, 1);
const blob = await new Promise<Blob>((resolve, reject) =>
canvas.toBlob(
(value) =>
value ? resolve(value) : reject(new Error("JPEG encode failed")),
"image/jpeg",
0.95,
),
);
return [...new Uint8Array(await blob.arrayBuffer())];
});
const jpeg = addExifOrientation(Buffer.from(encoded), 6);
await page.locator('input[type="file"]').setInputFiles({
name: "oriented.jpg",
mimeType: "image/jpeg",
buffer: jpeg,
});
await expect(page.getByText("2 × 1 pixels")).toBeVisible();
await page.getByRole("button", { name: "Re-encode & verify" }).click();
await expect(page.getByText("Mandatory output re-scan")).toBeVisible();
await expect(page.locator(".verification")).not.toHaveClass(/is-failed/u);
await expect(page.getByText("Normalized", { exact: true })).toBeVisible();
expect(external).toEqual([]);
});
function addExifOrientation(jpeg: Buffer, orientation: number): Buffer {
const payload = Buffer.concat([
Buffer.from("Exif\0\0", "binary"),
Buffer.from(tiffFixture({ orientation })),
]);
const segment = Buffer.from([
0xff,
0xe1,
((payload.length + 2) >>> 8) & 0xff,
(payload.length + 2) & 0xff,
]);
return Buffer.concat([
jpeg.subarray(0, 2),
segment,
payload,
jpeg.subarray(2),
]);
}