SPB Git
3.8 KB · 21 lines typescript
1import "./_env";2/** Post-deployment verification against the public URL: TLS, routes, API consistency with the database, discoverability endpoints. */3const base = (process.argv[2] ?? process.env.APP_URL ?? "https://wp.spboucher.ai").replace(/\/$/, "");4let pass = 0, fail = 0; const out: string[] = [];5const check = (name: string, ok: boolean, detail = "") => { ok ? pass++ : fail++; out.push(`${ok ? "PASS" : "FAIL"} ${name}${detail ? " — " + detail : ""}`); };6const t0 = Date.now(); const health = await fetch(base + "/api/health").then((r) => r.json()).catch((e) => ({ ok: false, error: String(e) })); check("health", health.ok === true, JSON.stringify(health)); check("health latency < 2 s", Date.now() - t0 < 2000);7const stats = await fetch(base + "/api/v1/stats").then((r) => r.json());8check("stats.papers equals health.papers (single source of truth)", stats.papers === health.papers, `${stats.papers} vs ${health.papers}`);9const list = await fetch(base + "/api/v1/papers?per_page=100").then((r) => r.json()); check("list total equals stats.papers", list.total === stats.papers, `${list.total}`); check("every listed paper has a PDF url or is flagged", list.items.every((p: { has_pdf: boolean; pdf_url: string | null }) => !p.has_pdf || p.pdf_url), "");10const first = list.items[0]; const full = await fetch(`${base}/api/v1/papers/${first.slug}`).then((r) => r.json()); check("detail endpoint", full.slug === first.slug);11const pdf = full.pdf_url ? await fetch(full.pdf_url, { method: "HEAD" }) : null; check("current manuscript downloadable", !!pdf && pdf.status === 200 && (pdf.headers.get("content-type") ?? "").includes("pdf"), pdf ? `${pdf.status} ${pdf.headers.get("content-length")} bytes, sha ${pdf.headers.get("x-checksum-sha256")?.slice(0, 12)}` : "no pdf");12for (const p of ["/", "/fr", "/papers", `/papers/${first.slug}`, `/fr/papers/${first.slug}`, "/topics", "/methods", "/datasets", "/authors", "/series", "/research-map", "/timeline", "/search?q=quebec", "/about", "/citation", "/api/docs", "/sitemap.xml", "/feed.xml", "/robots.txt", "/api/oai?verb=Identify", "/api/v1/openapi.json"]) { const r = await fetch(base + p); check(`GET ${p}`, r.status === 200, String(r.status)); }13const html = await fetch(`${base}/papers/${first.slug}`).then((r) => r.text()); check("Google Scholar meta tags", html.includes('name="citation_title"') && html.includes('name="citation_pdf_url"')); check("JSON-LD ScholarlyArticle", html.includes('"@type":"ScholarlyArticle"')); check("hreflang alternates", html.includes('hrefLang="fr"') || html.includes('hreflang="fr"'));14const fr = await fetch(`${base}/fr/papers/${first.slug}`).then((r) => r.text()); check("French render differs from English", fr.includes('lang="fr"') && fr.includes("Résumé"));15const red = await fetch(`${base}/papers/${first.identifier}`, { redirect: "manual" }); check("identifier alias → 308 canonical", red.status === 308 && (red.headers.get("location") ?? "").endsWith(`/papers/${first.slug}`), String(red.status));16check("404 for unknown paper", (await fetch(base + "/papers/wp99999")).status === 404); check("reserved WP39 hidden", (await fetch(base + "/papers/wp39")).status === 404);17const admin = await fetch(base + "/admin", { redirect: "manual" }); check("admin protected", [302, 307].includes(admin.status));18const sec = await fetch(base + "/").then((r) => r.headers); check("HSTS + CSP present", !!sec.get("strict-transport-security") && !!sec.get("content-security-policy"));19const oai = await fetch(base + "/api/oai?verb=ListRecords&metadataPrefix=oai_dc").then((r) => r.text()); check("OAI ListRecords count matches", (oai.match(/<record>/g) ?? []).length === stats.papers, `${(oai.match(/<record>/g) ?? []).length}`);20console.log(out.join("\n")); console.log(`\n${pass} passed, ${fail} failed (${base})`); process.exit(fail ? 1 : 0);21
Files · f9849d608022d879d08dd07eebfa69fe13695a09
Menu

Theme