Files
ObsiGate/tests/frontend/pdf-viewer.test.mjs
T
bruno 133644a0ba
CI / lint (push) Successful in 1m36s
CI / security (push) Successful in 1m3s
CI / test (push) Failing after 3m41s
CI / build (push) Skipped
CI / e2e (push) Skipped
fix(pdf): affichage des pages du viewer PDF via iframe (BUG-060)
2026-09-17 19:39:48 -04:00

95 lines
3.6 KiB
JavaScript

#!/usr/bin/env node
/**
* ObsiGate — Viewer PDF non-regression tests (BUG-060).
*
* Static checks on the source of the PDF inline viewer:
* - BUG-060 : the CSP set by BUG-034 puts `object-src 'none'`, which blocks
* `<embed>`/`<object>`. The PDF body was therefore never rendered (blank
* pages). The viewer must now use an `<iframe>` — permitted by
* `frame-src 'self'` since the PDF stream URL is same-origin.
*
* Usage: node tests/frontend/pdf-viewer.test.mjs
*/
import { strict as assert } from "node:assert";
import { readFileSync } from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const ROOT = path.join(__dirname, "..", "..");
const viewer = readFileSync(path.join(ROOT, "frontend", "js", "viewer.js"), "utf8");
const main = readFileSync(path.join(ROOT, "backend", "main.py"), "utf8");
const css = readFileSync(path.join(ROOT, "frontend", "style.css"), "utf8");
function test(label, fn) {
try {
fn();
console.log(" \u2713 " + label);
} catch (err) {
console.error(" \u2717 " + label + "\n " + err.message);
process.exitCode = 1;
}
}
// ── viewer.js : le rendu PDF passe par une iframe ─────────────────────────
test("viewer.js — PDF branch renders the stream in an <iframe class=pdf-iframe>", () => {
const block = viewer.match(/if \(data\.is_pdf\) \{([\s\S]*?)\n \}/);
assert.ok(block, "PDF render block not found");
assert.match(
block[1],
/<iframe src="\$\{pdfUrl\}" class="pdf-iframe"/,
"PDF must use <iframe>, not <embed>/<object> (CSP object-src 'none' otherwise blocks it)",
);
assert.match(
block[1],
/\.pdf-iframe[\s\S]*?src="\$\{pdfUrl\}"/,
"iframe src must come from the /pdf/stream URL",
);
});
test("viewer.js — no <embed>/<object> left in the source", () => {
assert.doesNotMatch(viewer, /<embed\b/i, "<embed> is blocked by CSP object-src 'none'");
assert.doesNotMatch(viewer, /<object\b/i, "<object> is blocked by CSP object-src 'none'");
});
test("viewer.js — TOC still targets the pdf-iframe via contentWindow", () => {
assert.match(
viewer,
/document\.querySelector\('\.pdf-iframe'\)\.contentWindow\.location\.hash='page=\$\{item\.page\}'/,
"TOC links must keep navigating the iframe",
);
});
// ── backend : la CSP autorise le cadre same-origin ─────────────────────────
test("backend CSP — frame-src 'self' allows same-origin iframes", () => {
const csp = main.match(/frame-src ([^";]+);/);
assert.ok(csp, "CSP frame-src directive not found");
assert.ok(
csp[1].includes("'self'"),
`frame-src must allow 'self' (found: ${csp[1]}) — otherwise the PDF iframe is blocked`,
);
});
test("backend CSP — object-src 'none' stays in place (no defusing)", () => {
assert.match(
main,
/object-src 'none';/,
"object-src must stay locked to 'none': the fix is moving to <iframe>, not weakening CSP",
);
});
// ── style.css : l'iframe garde une hauteur utile ───────────────────────────
test("style.css — .pdf-iframe fills the viewer body", () => {
const rule = css.match(/\.pdf-iframe\s*\{([^}]*)\}/);
assert.ok(rule, ".pdf-iframe rule not found");
assert.match(rule[1], /flex:\s*1/, "iframe must stretch to fill the available height");
assert.match(rule[1], /min-height/, "iframe must keep its minimum height");
});
if (process.exitCode) {
console.error("\nPDF viewer tests FAILED");
} else {
console.log("\nAll PDF viewer tests passed.");
}