Files
miti99/scripts/newsletter/network.test.js
T
tiennm99 d07f48d69e test(newsletter): add a golden-output parity suite
Captures the Go engine's stdout for 26 deterministic cases, commits those
outputs as fixtures, and asserts the JavaScript engine reproduces them through
the real CLI surface, exit codes included. This is both the migration's proof of
correctness and the regression net the engine never had.

Every fixture was generated from the Go engine rather than written by hand, and
reviewed before commit so no existing bug became a permanent contract. Each
behaviour that a JavaScript-idiomatic rewrite would silently break has a case
that goes red when the naive implementation is reintroduced; verified by
mutating the engine and re-running.

Content-scanning commands are pointed at a fixture tree through the subprocess
working directory. Both engines already resolve content/post from the process
CWD, so neither needed a test-only configuration knob, and the suite is proven
isolated from the real content by renaming it away and re-running.

Cases that depend on live upstreams are opt-in behind NEWSLETTER_NET=1: a
publication's feed window moves, and a suite that goes red on someone else's
outage teaches people to ignore red. The deterministic tier reaches the network
only through .invalid hosts, which never resolve, so it runs offline in about
two seconds.

The golden files are compared byte for byte, so scripts/newsletter is pinned to
LF; the repository otherwise checks out CRLF, which would break every comparison
on a fresh clone.
2026-09-18 16:24:16 +07:00

103 lines
4.4 KiB
JavaScript

// Live-upstream tier. Opt-in via NEWSLETTER_NET=1: upstream content moves, and a
// suite that goes red on someone else's outage teaches people to ignore red.
//
// These assert structure and invariants rather than captured bytes, because the
// bytes legitimately change when a publication posts.
import assert from "node:assert/strict";
import { execFileSync } from "node:child_process";
import { join } from "node:path";
import { test } from "node:test";
const HERE = import.meta.dirname;
const ENGINE = join(HERE, "index.js");
const FIXTURE_REPO = join(HERE, "__fixtures__", "repo");
const skip = process.env.NEWSLETTER_NET !== "1";
const OPTS = { skip: skip ? "set NEWSLETTER_NET=1 to run live-upstream cases" : false };
/**
* @param {string[]} args
* @returns {{stdout: string, code: number}}
*/
function runEngine(args) {
try {
return {
stdout: execFileSync("node", [ENGINE, ...args], { cwd: FIXTURE_REPO, encoding: "utf8" }),
code: 0,
};
} catch (err) {
return { stdout: err.stdout ?? "", code: err.status ?? 1 };
}
}
const WATCH = "https://www.youtube.com/watch?v=dQw4w9WgXcQ";
const CANONICAL = WATCH;
test("add-url: a watch URL routes to youtube and resolves oEmbed metadata", OPTS, () => {
const out = JSON.parse(runEngine(["add-url", WATCH + "&utm_source=nl"]).stdout);
assert.equal(out.route, "youtube");
assert.equal(out.clean_url, CANONICAL);
assert.ok(out.title.length > 0, "oEmbed returned no title");
assert.ok(out.author.length > 0, "oEmbed returned no author");
});
test("add-url: youtu.be and shorts collapse onto the canonical watch identity", OPTS, () => {
const short = JSON.parse(runEngine(["add-url", "https://youtu.be/dQw4w9WgXcQ"]).stdout);
assert.equal(short.route, "youtube");
assert.equal(short.clean_url, CANONICAL);
const shorts = JSON.parse(runEngine(["add-url", "https://www.youtube.com/shorts/dQw4w9WgXcQ"]).stdout);
assert.equal(shorts.route, "youtube");
assert.equal(shorts.clean_url, CANONICAL);
});
test("add-url: a playlist is not a youtube route", OPTS, () => {
const out = JSON.parse(runEngine(["add-url", "https://www.youtube.com/playlist?list=PLrAXtmRdnEQy6nuLMfO6uZ1a4Z3AQoMek"]).stdout);
assert.equal(out.route, "article");
assert.equal(out.title, undefined);
});
test("find-substack-post: a uuid no publication carries reports a bare miss", OPTS, () => {
const out = JSON.parse(runEngine(["find-substack-post", "--uuid", "ffffffff-ffff-ffff-ffff-ffffffffffff"]).stdout);
assert.deepEqual(out, { found: false });
});
test("find-substack-post: a deep miss reports the crawl budget and cutoff", OPTS, () => {
const out = JSON.parse(runEngine(["find-substack-post", "--uuid", "ffffffff-ffff-ffff-ffff-ffffffffffff", "--deep"]).stdout);
assert.equal(out.found, false);
assert.equal(out.source, "sitemap");
assert.equal(out.budget, 40);
assert.ok(Number.isInteger(out.scanned));
// cutoff is null (present, not omitted) when no sitemap could be fetched.
assert.ok("cutoff" in out, "cutoff key is missing");
assert.ok(out.cutoff === null || /^\d{4}-\d{2}-\d{2}$/.test(out.cutoff));
});
test("find-substack-post: a uuid from the live feed resolves to its post", OPTS, async () => {
const res = await fetch("https://blog.bytebytego.com/feed", { headers: { "user-agent": "Mozilla/5.0" } });
const feed = await res.text();
const m = /public\/images\/([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/.exec(feed);
assert.ok(m !== null, "the live feed carried no image uuid");
const out = JSON.parse(runEngine(["find-substack-post", "--uuid", m[1]]).stdout);
assert.equal(out.found, true);
assert.equal(out.source, "rss");
assert.ok(out.postUrl.startsWith("https://"));
assert.ok(out.postTitle.length > 0);
assert.ok(Array.isArray(out.candidates));
});
test("fetch-via-defuddle: a reachable page returns frontmatter and a body", OPTS, () => {
const { stdout, code } = runEngine(["fetch-via-defuddle", "https://example.com/"]);
assert.equal(code, 0);
// Both tiers emit the same shape, so the caller parses one format.
assert.ok(stdout.startsWith("---\n"), "no YAML frontmatter");
assert.match(stdout, /^title: /m);
const body = stdout.split("\n---\n")[1] ?? "";
assert.ok(body.trim().length > 0, "frontmatter with no body");
});
test("fetch-via-defuddle: an unresolvable host exhausts both tiers and exits 1", OPTS, () => {
const { code } = runEngine(["fetch-via-defuddle", "https://nonexistent-host-xyz-12345.invalid/a"]);
assert.equal(code, 1);
});