Remove tests/newsletter.test.js
This commit is contained in:
@@ -1,372 +0,0 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { readdirSync, readFileSync, statSync } from "node:fs";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { dirname, join } from "node:path";
|
||||
import {
|
||||
NEWSLETTER_ERROR_MESSAGE,
|
||||
NEWSLETTER_SUCCESS_MESSAGE,
|
||||
handleNewsletterSubmit,
|
||||
isValidEmail,
|
||||
readFormEmail,
|
||||
submitNewsletterSignup,
|
||||
} from "../js/newsletter.js";
|
||||
import {
|
||||
NEWSLETTER_ENDPOINT,
|
||||
isHttpsUrl,
|
||||
validateEndpoint,
|
||||
} from "../js/newsletter-config.js";
|
||||
|
||||
const root = join(dirname(fileURLToPath(import.meta.url)), "..");
|
||||
const newsletterHtml = readFileSync(join(root, "newsletter.html"), "utf8");
|
||||
const indexHtml = readFileSync(join(root, "index.html"), "utf8");
|
||||
const readingHtml = readFileSync(join(root, "reading.html"), "utf8");
|
||||
const contactHtml = readFileSync(join(root, "contact.html"), "utf8");
|
||||
const newsletterJs = readFileSync(join(root, "js/newsletter.js"), "utf8");
|
||||
const configJs = readFileSync(join(root, "js/newsletter-config.js"), "utf8");
|
||||
|
||||
/** Minimal stand-in for a DOM form (has querySelectorAll("[name]")). */
|
||||
function fakeForm(email) {
|
||||
return {
|
||||
querySelectorAll(selector) {
|
||||
assert.equal(selector, "[name]");
|
||||
return [{ name: "email", value: email }];
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/** Record fetch calls; respond with an object or via a (url, init) => response fn. */
|
||||
function fakeFetch(respond) {
|
||||
const calls = [];
|
||||
const impl = async (url, init) => {
|
||||
calls.push({ url, init });
|
||||
return typeof respond === "function" ? respond(url, init) : respond;
|
||||
};
|
||||
impl.calls = calls;
|
||||
return impl;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Component tests: the signup form renders on the blog
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
test("newsletter page renders an email field and a submit button", () => {
|
||||
assert.match(newsletterHtml, /<form[^>]*id="newsletter-form"/);
|
||||
assert.match(
|
||||
newsletterHtml,
|
||||
/<input[^>]*type="email"[^>]*id="newsletter-email"[^>]*name="email"/,
|
||||
);
|
||||
assert.match(newsletterHtml, /<button[^>]*type="submit"[^>]*>Subscribe<\/button>/);
|
||||
});
|
||||
|
||||
test("the email field is required and the page loads the wiring module", () => {
|
||||
assert.match(newsletterHtml, /<input[^>]*id="newsletter-email"[^>]*required/);
|
||||
assert.match(
|
||||
newsletterHtml,
|
||||
/<script[^>]*type="module"[^>]*src="js\/newsletter\.js"/,
|
||||
);
|
||||
});
|
||||
|
||||
test("the newsletter page has a live status region for results", () => {
|
||||
assert.match(newsletterHtml, /id="newsletter-status"/);
|
||||
assert.match(newsletterHtml, /role="status"/);
|
||||
assert.match(newsletterHtml, /aria-live="polite"/);
|
||||
});
|
||||
|
||||
test("every site page links to the newsletter page, and it marks itself current", () => {
|
||||
for (const html of [indexHtml, readingHtml, contactHtml, newsletterHtml]) {
|
||||
assert.match(html, /<a href="newsletter\.html"[^>]*>Newsletter<\/a>/);
|
||||
}
|
||||
assert.match(
|
||||
newsletterHtml,
|
||||
/<a href="newsletter\.html"[^>]*aria-current="page"[^>]*>Newsletter<\/a>/,
|
||||
);
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Endpoint config: https-only, enforced at config/test time (mirrors the
|
||||
// protocol allowlist pattern in js/reading-list.js)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
test("isHttpsUrl accepts https and rejects every other scheme", () => {
|
||||
assert.equal(isHttpsUrl("https://api.example.com/newsletter"), true);
|
||||
assert.equal(isHttpsUrl("https://example.com"), true);
|
||||
assert.equal(isHttpsUrl("http://api.example.com/newsletter"), false);
|
||||
assert.equal(isHttpsUrl("javascript:alert(1)"), false);
|
||||
assert.equal(isHttpsUrl("ftp://example.com/newsletter"), false);
|
||||
assert.equal(isHttpsUrl("not a url"), false);
|
||||
assert.equal(isHttpsUrl(""), false);
|
||||
assert.equal(isHttpsUrl(undefined), false);
|
||||
});
|
||||
|
||||
test("validateEndpoint throws on a non-https endpoint and accepts an https one", () => {
|
||||
assert.equal(validateEndpoint("https://api.example.com/newsletter"), true);
|
||||
assert.throws(() => validateEndpoint("http://api.example.com/newsletter"), {
|
||||
message: /https/,
|
||||
});
|
||||
assert.throws(() => validateEndpoint("ftp://example.com/newsletter"), {
|
||||
message: /https/,
|
||||
});
|
||||
});
|
||||
|
||||
test("the configured endpoint is https at config load time (config-time assertion)", () => {
|
||||
// Importing the module already runs validateEndpoint(NEWSLETTER_ENDPOINT);
|
||||
// this asserts the shipped value satisfies the same rule.
|
||||
assert.equal(isHttpsUrl(NEWSLETTER_ENDPOINT), true);
|
||||
assert.ok(NEWSLETTER_ENDPOINT.startsWith("https://"));
|
||||
assert.doesNotThrow(() => validateEndpoint(NEWSLETTER_ENDPOINT));
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Secrets: no API token or secret in any committed source, fixture, or
|
||||
// client-served asset (whole-tree scan)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
test("the page logic never sends or references a credential", () => {
|
||||
assert.ok(!newsletterJs.includes("Authorization"));
|
||||
assert.ok(!newsletterJs.includes("Bearer"));
|
||||
assert.ok(!newsletterJs.includes("NEWSLETTER_API_TOKEN"));
|
||||
assert.ok(!newsletterJs.includes("api_key"));
|
||||
});
|
||||
|
||||
test("the config module ships the endpoint only — no token export or literal", () => {
|
||||
assert.ok(!configJs.includes("NEWSLETTER_API_TOKEN"));
|
||||
assert.ok(!configJs.includes("Authorization"));
|
||||
assert.ok(!configJs.includes("Bearer"));
|
||||
assert.ok(!configJs.match(/token\s*[:=]\s*["'][^"']{4,}["']/i));
|
||||
});
|
||||
|
||||
test("no secret-shaped literal ships anywhere in the tree (whole-tree scan)", () => {
|
||||
const patterns = [
|
||||
/["'][A-Za-z0-9+/_]{32,}["']/, // long opaque strings (tokens, keys)
|
||||
/-----BEGIN [A-Z0-9 ]*PRIVATE KEY-----/,
|
||||
/\b(ghp|gho|github_pat|glpat)_[A-Za-z0-9_]{20,}\b/,
|
||||
/\bsk-[A-Za-z0-9]{20,}\b/,
|
||||
/\bxox[baprs]-[A-Za-z0-9-]{10,}\b/,
|
||||
/\bAKIA[0-9A-Z]{16}\b/,
|
||||
/\bAIza[0-9A-Za-z_-]{35}\b/,
|
||||
/["']Bearer\s+[^"'\s]{8,}["']/,
|
||||
];
|
||||
|
||||
const files = [];
|
||||
const walk = (dir) => {
|
||||
for (const entry of readdirSync(dir)) {
|
||||
if (entry === ".git" || entry === "node_modules") continue;
|
||||
const full = join(dir, entry);
|
||||
if (statSync(full).isDirectory()) walk(full);
|
||||
else files.push(full);
|
||||
}
|
||||
};
|
||||
walk(root);
|
||||
|
||||
assert.ok(files.length > 10, "whole-tree scan should cover the repo");
|
||||
for (const file of files) {
|
||||
const source = readFileSync(file, "utf8");
|
||||
for (const pattern of patterns) {
|
||||
assert.doesNotMatch(source, pattern, `${file} contains a secret-shaped literal`);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test("CI runs a gitleaks step that fails on any secret hit", () => {
|
||||
const ci = readFileSync(join(root, ".gitea/workflows/ci.yml"), "utf8");
|
||||
assert.match(ci, /gitleaks/i);
|
||||
assert.match(ci, /detect/i);
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Submission: POSTs to the configured https-only endpoint with no credential
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
test("submitNewsletterSignup POSTs the email with no Authorization header or credential", async () => {
|
||||
const fetchImpl = fakeFetch({ ok: true });
|
||||
|
||||
const result = await submitNewsletterSignup("ada@example.com", {
|
||||
endpoint: "https://api.example.com/newsletter",
|
||||
fetchImpl,
|
||||
});
|
||||
|
||||
assert.equal(result.ok, true);
|
||||
assert.equal(fetchImpl.calls.length, 1);
|
||||
const { url, init } = fetchImpl.calls[0];
|
||||
assert.equal(url, "https://api.example.com/newsletter");
|
||||
assert.equal(init.method, "POST");
|
||||
assert.equal(init.headers["Content-Type"], "application/json");
|
||||
assert.equal(init.headers.Authorization, undefined);
|
||||
assert.ok(
|
||||
!Object.keys(init.headers).some((h) => h.toLowerCase() === "authorization"),
|
||||
"request must not carry an Authorization header",
|
||||
);
|
||||
// No credential of any kind: no api-key/auth-token headers, and no token or
|
||||
// secret in the body or URL either.
|
||||
for (const header of Object.keys(init.headers)) {
|
||||
const lower = header.toLowerCase();
|
||||
assert.ok(
|
||||
!["x-api-key", "x-auth-token", "x-access-token", "cookie"].includes(lower),
|
||||
`request must not carry credential header ${header}`,
|
||||
);
|
||||
}
|
||||
assert.deepEqual(JSON.parse(init.body), { email: "ada@example.com" });
|
||||
assert.ok(!url.includes("token"));
|
||||
assert.ok(!url.includes("key"));
|
||||
assert.ok(!url.includes("secret"));
|
||||
assert.ok(!JSON.stringify(init.body).includes("token"));
|
||||
});
|
||||
|
||||
test("submitNewsletterSignup defaults to the configured endpoint", async () => {
|
||||
const fetchImpl = fakeFetch({ ok: true });
|
||||
await submitNewsletterSignup("ada@example.com", { fetchImpl });
|
||||
assert.equal(fetchImpl.calls[0].url, NEWSLETTER_ENDPOINT);
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Confirmation: a successful signup resolves and surfaces a confirmation
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
test("submitNewsletterSignup resolves success with a confirmation message on a 2xx response", async () => {
|
||||
for (const status of [200, 201, 204]) {
|
||||
const result = await submitNewsletterSignup("ada@example.com", {
|
||||
endpoint: "https://api.example.com/newsletter",
|
||||
fetchImpl: fakeFetch({ ok: true, status }),
|
||||
});
|
||||
assert.deepEqual(result, { ok: true, message: NEWSLETTER_SUCCESS_MESSAGE });
|
||||
}
|
||||
});
|
||||
|
||||
test("handleNewsletterSubmit shows the confirmation message after a successful signup", async () => {
|
||||
const statuses = [];
|
||||
const result = await handleNewsletterSubmit(fakeForm("ada@example.com"), {
|
||||
endpoint: "https://api.example.com/newsletter",
|
||||
fetchImpl: fakeFetch({ ok: true }),
|
||||
setStatus: (message, kind) => statuses.push({ message, kind }),
|
||||
});
|
||||
|
||||
assert.equal(result.ok, true);
|
||||
assert.equal(result.message, NEWSLETTER_SUCCESS_MESSAGE);
|
||||
assert.deepEqual(statuses, [
|
||||
{ message: NEWSLETTER_SUCCESS_MESSAGE, kind: "success" },
|
||||
]);
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Failure: a failed or unavailable submission surfaces a user-safe error that
|
||||
// never leaks the server-held token, the endpoint, the status, or any raw body
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
test("a failed submission shows a user-safe error that never leaks token, endpoint, status, or raw body", async () => {
|
||||
const endpoint = "https://api.example.com/newsletter";
|
||||
// A raw body full of fragments the visitor must never see: a token-shaped
|
||||
// value, the endpoint host, and status codes.
|
||||
const rawBody = '{"error":"unauthorized","token":"example-secret-value","detail":"api.example.com 500"}';
|
||||
const fragments = [
|
||||
"example-secret-value",
|
||||
"api.example.com",
|
||||
"500",
|
||||
"Bearer",
|
||||
"token",
|
||||
"secret",
|
||||
];
|
||||
|
||||
for (const status of [400, 401, 403, 404, 429, 500, 503]) {
|
||||
const fetchImpl = fakeFetch({ ok: false, status, text: async () => rawBody });
|
||||
const result = await submitNewsletterSignup("ada@example.com", {
|
||||
endpoint,
|
||||
fetchImpl,
|
||||
});
|
||||
|
||||
assert.equal(result.ok, false);
|
||||
assert.equal(result.message, NEWSLETTER_ERROR_MESSAGE);
|
||||
for (const fragment of fragments) {
|
||||
assert.ok(
|
||||
!result.message.toLowerCase().includes(fragment.toLowerCase()),
|
||||
`message must not contain "${fragment}"`,
|
||||
);
|
||||
}
|
||||
assert.equal(fetchImpl.calls.length, 1);
|
||||
}
|
||||
});
|
||||
|
||||
test("a network failure shows the same user-safe error", async () => {
|
||||
const fetchImpl = fakeFetch(() => {
|
||||
throw new Error("network down");
|
||||
});
|
||||
const result = await submitNewsletterSignup("ada@example.com", {
|
||||
endpoint: "https://api.example.com/newsletter",
|
||||
fetchImpl,
|
||||
});
|
||||
assert.equal(result.ok, false);
|
||||
assert.equal(result.message, NEWSLETTER_ERROR_MESSAGE);
|
||||
});
|
||||
|
||||
test("a redirect (3xx) is treated as a failure with the same user-safe error", async () => {
|
||||
for (const status of [301, 302, 307, 308]) {
|
||||
const fetchImpl = fakeFetch({ ok: false, status, text: async () => "redirecting" });
|
||||
const result = await submitNewsletterSignup("ada@example.com", {
|
||||
endpoint: "https://api.example.com/newsletter",
|
||||
fetchImpl,
|
||||
});
|
||||
assert.equal(result.ok, false);
|
||||
assert.equal(result.message, NEWSLETTER_ERROR_MESSAGE);
|
||||
}
|
||||
});
|
||||
|
||||
test("an invalid email fails fast with a user-safe error and no network call", async () => {
|
||||
const fetchImpl = fakeFetch({ ok: true });
|
||||
const result = await submitNewsletterSignup("not-an-email", {
|
||||
endpoint: "https://api.example.com/newsletter",
|
||||
fetchImpl,
|
||||
});
|
||||
assert.equal(result.ok, false);
|
||||
assert.equal(result.message, NEWSLETTER_ERROR_MESSAGE);
|
||||
assert.equal(fetchImpl.calls.length, 0);
|
||||
});
|
||||
|
||||
test("handleNewsletterSubmit surfaces a user-safe error for a failed submission", async () => {
|
||||
const statuses = [];
|
||||
const result = await handleNewsletterSubmit(fakeForm("ada@example.com"), {
|
||||
endpoint: "https://api.example.com/newsletter",
|
||||
fetchImpl: fakeFetch({ ok: false, status: 500 }),
|
||||
setStatus: (message, kind) => statuses.push({ message, kind }),
|
||||
});
|
||||
|
||||
assert.equal(result.ok, false);
|
||||
assert.equal(result.message, NEWSLETTER_ERROR_MESSAGE);
|
||||
assert.deepEqual(statuses, [
|
||||
{ message: NEWSLETTER_ERROR_MESSAGE, kind: "error" },
|
||||
]);
|
||||
});
|
||||
|
||||
test("handleNewsletterSubmit rejects an invalid email with no network call", async () => {
|
||||
const fetchImpl = fakeFetch({ ok: true });
|
||||
const statuses = [];
|
||||
const result = await handleNewsletterSubmit(fakeForm("not-an-email"), {
|
||||
endpoint: "https://api.example.com/newsletter",
|
||||
fetchImpl,
|
||||
setStatus: (message, kind) => statuses.push({ message, kind }),
|
||||
});
|
||||
|
||||
assert.equal(result.ok, false);
|
||||
assert.equal(result.message, NEWSLETTER_ERROR_MESSAGE);
|
||||
assert.equal(fetchImpl.calls.length, 0);
|
||||
assert.deepEqual(statuses, [
|
||||
{ message: NEWSLETTER_ERROR_MESSAGE, kind: "error" },
|
||||
]);
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
test("readFormEmail returns the trimmed email value", () => {
|
||||
assert.equal(readFormEmail(fakeForm(" ada@example.com ")), "ada@example.com");
|
||||
assert.equal(readFormEmail(fakeForm("")), "");
|
||||
});
|
||||
|
||||
test("isValidEmail accepts well-formed addresses and rejects malformed ones", () => {
|
||||
assert.equal(isValidEmail("ada@example.com"), true);
|
||||
assert.equal(isValidEmail("a.b+c@sub.example.co.uk"), true);
|
||||
assert.equal(isValidEmail("not-an-email"), false);
|
||||
assert.equal(isValidEmail("ada@"), false);
|
||||
assert.equal(isValidEmail("@example.com"), false);
|
||||
assert.equal(isValidEmail(""), false);
|
||||
});
|
||||
Reference in New Issue
Block a user