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.
This commit is contained in:
2026-09-18 16:24:16 +07:00
parent fb292d444d
commit d07f48d69e
44 changed files with 798 additions and 0 deletions
+4
View File
@@ -1,3 +1,7 @@
* text=auto eol=crlf
*.sh text eol=lf
# The newsletter parity suite compares engine stdout to committed golden files
# byte for byte, so these must stay LF on every checkout.
scripts/newsletter/** text eol=lf
@@ -0,0 +1,31 @@
{
"add-url-tracking-params": 0,
"add-url-encoding-preserved": 0,
"add-url-query-verbatim": 0,
"add-url-dup-bare": 0,
"add-url-dup-trailing-slash": 0,
"add-url-dup-autolink": 0,
"add-url-dup-markdown-link": 0,
"add-url-prefix-not-duplicate": 0,
"add-url-prefix-exact-duplicate": 0,
"add-url-image-cdn-duplicate": 0,
"add-url-image-s3-duplicate": 0,
"add-url-image-avif-query": 0,
"add-url-document-pdf": 0,
"add-url-video-mp4": 0,
"add-url-article-no-metadata": 0,
"detect-image-cdn-wrapper": 0,
"detect-image-raw-s3": 0,
"detect-image-non-substack": 0,
"detect-image-malformed-percent": 0,
"find-newsletter-number-main": 0,
"find-newsletter-number-empty": 0,
"find-newsletter-number-year-fallback": 0,
"list-existing-tags-main": 0,
"post-stats-full": 0,
"post-stats-empty-bonus": 0,
"post-stats-no-bonus": 0,
"extract-rss-item": 0,
"extract-edge-html": 0,
"extract-edge-html-no-figcaption": 0
}
@@ -0,0 +1,8 @@
{
"original_url": "https://a.invalid/plain",
"clean_url": "https://a.invalid/plain",
"http_status": "000",
"accessible": false,
"duplicate": false,
"route": "article"
}
@@ -0,0 +1,8 @@
{
"original_url": "https://a.invalid/file.pdf",
"clean_url": "https://a.invalid/file.pdf",
"http_status": "000",
"accessible": false,
"duplicate": false,
"route": "document"
}
@@ -0,0 +1,8 @@
{
"original_url": "https://autolink.invalid/entry",
"clean_url": "https://autolink.invalid/entry",
"http_status": "000",
"accessible": false,
"duplicate": true,
"route": "article"
}
@@ -0,0 +1,8 @@
{
"original_url": "https://bare2.invalid/article",
"clean_url": "https://bare2.invalid/article",
"http_status": "000",
"accessible": false,
"duplicate": true,
"route": "article"
}
@@ -0,0 +1,8 @@
{
"original_url": "https://link.invalid/story",
"clean_url": "https://link.invalid/story",
"http_status": "000",
"accessible": false,
"duplicate": true,
"route": "article"
}
@@ -0,0 +1,8 @@
{
"original_url": "https://slash.invalid/post/",
"clean_url": "https://slash.invalid/post/",
"http_status": "000",
"accessible": false,
"duplicate": true,
"route": "article"
}
@@ -0,0 +1,8 @@
{
"original_url": "https://A.invalid/path/?a=%7Efoo+bar&b=2#frag",
"clean_url": "https://a.invalid/path/?a=%7Efoo+bar&b=2#frag",
"http_status": "000",
"accessible": false,
"duplicate": false,
"route": "article"
}
@@ -0,0 +1,8 @@
{
"original_url": "https://cdn.invalid/image/fetch/f_auto/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F12345678-1234-1234-1234-123456789abc_100x100.avif?x=1",
"clean_url": "https://cdn.invalid/image/fetch/f_auto/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F12345678-1234-1234-1234-123456789abc_100x100.avif?x=1",
"http_status": "000",
"accessible": false,
"duplicate": false,
"route": "image"
}
@@ -0,0 +1,8 @@
{
"original_url": "https://cdn.invalid/image/fetch/w_1100,c_limit,f_auto/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee_2484x3002.png",
"clean_url": "https://cdn.invalid/image/fetch/w_1100,c_limit,f_auto/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee_2484x3002.png",
"http_status": "000",
"accessible": false,
"duplicate": true,
"route": "image"
}
@@ -0,0 +1,8 @@
{
"original_url": "https://substack-post-media.s3.invalid/public/images/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee_1280x720.png",
"clean_url": "https://substack-post-media.s3.invalid/public/images/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee_1280x720.png",
"http_status": "000",
"accessible": false,
"duplicate": true,
"route": "image"
}
@@ -0,0 +1,8 @@
{
"original_url": "https://prefix.invalid/posts/the-long-slug",
"clean_url": "https://prefix.invalid/posts/the-long-slug",
"http_status": "000",
"accessible": false,
"duplicate": true,
"route": "article"
}
@@ -0,0 +1,8 @@
{
"original_url": "https://prefix.invalid/posts/the-long",
"clean_url": "https://prefix.invalid/posts/the-long",
"http_status": "000",
"accessible": false,
"duplicate": false,
"route": "article"
}
@@ -0,0 +1,8 @@
{
"original_url": "https://a.invalid/path?a=%20x&flag&b=%7ez&utm_source=drop",
"clean_url": "https://a.invalid/path?a=%20x&flag&b=%7ez",
"http_status": "000",
"accessible": false,
"duplicate": false,
"route": "article"
}
@@ -0,0 +1,8 @@
{
"original_url": "https://a.invalid/article?utm_source=x&utm_medium=y&fbclid=abc&keep=1&ref=z&s=1",
"clean_url": "https://a.invalid/article?keep=1",
"http_status": "000",
"accessible": false,
"duplicate": false,
"route": "article"
}
@@ -0,0 +1,8 @@
{
"original_url": "https://a.invalid/clip.mp4",
"clean_url": "https://a.invalid/clip.mp4",
"http_status": "000",
"accessible": false,
"duplicate": false,
"route": "video"
}
@@ -0,0 +1,7 @@
{
"original_url": "https://substackcdn.com/image/fetch/$s_!lpxK!,w_1100,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee_2484x3002.png",
"clean_url": "https://substackcdn.com/image/fetch/$s_!lpxK!,w_1100,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee_2484x3002.png",
"isSubstack": true,
"uuid": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
"innerUrl": "https://substack-post-media.s3.amazonaws.com/public/images/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee_2484x3002.png"
}
@@ -0,0 +1,6 @@
{
"original_url": "https://substackcdn.com/image/fetch/w_100/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F%ZZbad.png",
"clean_url": "https://substackcdn.com/image/fetch/w_100/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F%ZZbad.png",
"isSubstack": true,
"innerUrl": "https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F%ZZbad.png"
}
@@ -0,0 +1,5 @@
{
"original_url": "https://a.invalid/pic.png",
"clean_url": "https://a.invalid/pic.png",
"isSubstack": false
}
@@ -0,0 +1,7 @@
{
"original_url": "https://substack-post-media.s3.amazonaws.com/public/images/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee_1280x720.png",
"clean_url": "https://substack-post-media.s3.amazonaws.com/public/images/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee_1280x720.png",
"isSubstack": true,
"uuid": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
"innerUrl": "https://substack-post-media.s3.amazonaws.com/public/images/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee_1280x720.png"
}
@@ -0,0 +1,9 @@
{
"candidates": [
"🚀 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
"sixchr",
"Duplicate Bullet Text"
],
"caption": "",
"postTitle": ""
}
@@ -0,0 +1,9 @@
{
"candidates": [
"🚀 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
"sixchr",
"Duplicate Bullet Text"
],
"caption": "Inner caption & more",
"postTitle": ""
}
@@ -0,0 +1,16 @@
{
"candidates": [
"How documents are turned into searchable passages",
"How LLMs find the meaning behind different words",
"How close is close enough",
"Why searching every passage is too expensive",
"Following connections to the the right neighborhood",
"How much searching is enough",
"What happens when the answer changes",
"Dot product reflects both alignment and length.",
"Pre-filtering identifies eligible records before similarity ranking."
],
"caption": "",
"itemLink": "https://blog.bytebytego.com/p/how-llms-can-find-a-needle-in-a-haystack",
"itemTitle": "How LLMs Can Find a Needle in a Haystack"
}
@@ -0,0 +1,5 @@
3 AI-Assisted
1 Older Year
1 Software Engineering
1 Tie Break A
1 Tie Break B
@@ -0,0 +1,9 @@
{
"post": "content/post/2026/09/11/empty-bonus.md",
"newsletter": 0,
"articles": 1,
"images": 0,
"videos": 0,
"documents": 0,
"total": 1
}
@@ -0,0 +1,9 @@
{
"post": "content/post/2026/09/10/index.md",
"newsletter": 132,
"articles": 2,
"images": 1,
"videos": 1,
"documents": 1,
"total": 5
}
@@ -0,0 +1,9 @@
{
"post": "content/post/2026/09/11/no-bonus.md",
"newsletter": 0,
"articles": 2,
"images": 0,
"videos": 0,
"documents": 0,
"total": 2
}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,20 @@
<div>
<ul>
<li>🚀 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA</li>
<li>🚀 BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB</li>
<li>fiver</li>
<li>sixchr</li>
<li><p>Duplicate Bullet Text</p></li>
<li>duplicate bullet text</li>
</ul>
<figure>
<figure>
<img src="https://substack-post-media.s3.amazonaws.com/public/images/11111111-2222-3333-4444-555555555555_100x100.png">
<figcaption>Inner caption &amp; more</figcaption>
</figure>
<figcaption>Outer caption</figcaption>
</figure>
<figure>
<img src="https://substack-post-media.s3.amazonaws.com/public/images/99999999-8888-7777-6666-555555555555_10x10.png">
</figure>
</div>
@@ -0,0 +1,7 @@
---
title: "Newsletter #50"
date: 2025-06-01
tags: ["AI-Assisted"]
---
Case: the only newsletter in the tree, in an older year. Expect 51.
@@ -0,0 +1,8 @@
---
title: "No newsletter here"
date: 2026-01-02
tags: ["AI-Assisted"]
---
Case: the newest year contains no "Newsletter #N" heading, so the scan must
continue into the older year rather than stopping at the newest year.
@@ -0,0 +1,12 @@
---
title: "Newsletter #999"
date: 2025-12-31
tags: ["AI-Assisted", "Older Year"]
categories: ["Newsletter"]
---
Case: an older year holding a HIGHER number than the newest year, so
find-newsletter-number proves it stops at the newest year that has one
instead of taking the maximum across the whole tree.
## [Some article](https://older.invalid/story)
@@ -0,0 +1,31 @@
---
title: "Newsletter #132"
date: 2026-09-10
tags: ["AI-Assisted", "Software Engineering", "Tie Break A"]
categories: ["Newsletter"]
---
Case: the newest newsletter. Also the post-stats "full" fixture — articles
outside Bonus, all three Bonus subsections populated.
## [First article](https://link.invalid/story)
Nội dung tóm tắt.
## [Second article](https://bare.invalid/article)
Nội dung tóm tắt.
### Bonus
**Images:**
![MCP vs A2A](https://substackcdn.com/image/fetch/$s_!lpxK!,w_1100,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee_2484x3002.png)
**Videos:**
[Some talk](https://www.youtube.com/watch?v=dQw4w9WgXcQ)
**Documents:**
[PDF: Some paper](https://docs.invalid/paper.pdf)
@@ -0,0 +1,16 @@
---
title: "Empty bonus"
date: 2026-09-11
---
Case: post-stats with a Bonus section whose subsections carry no entries.
## [Only article](https://only.invalid/a)
### Bonus
**Images:**
**Videos:**
**Documents:**
@@ -0,0 +1,30 @@
---
title: "Stored URL shapes"
date: 2026-09-11
tags: ["AI-Assisted", "Tie Break B"]
categories: ["Newsletter"]
---
Case: every shape a URL is stored in, so the boundary-aware dedup check is
exercised against each. No "Newsletter #" heading — find-newsletter-number
must still resolve 133 from the sibling post.
A bare URL on its own line:
https://bare2.invalid/article
A URL that keeps its trailing slash:
https://slash.invalid/post/
A markdown autolink:
<https://autolink.invalid/entry>
A long stored URL that a shorter needle must NOT falsely match:
https://prefix.invalid/posts/the-long-slug
The same Substack image identity in raw S3 form:
![Raw S3](https://substack-post-media.s3.amazonaws.com/public/images/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee_1280x720.png)
@@ -0,0 +1,11 @@
---
title: "No bonus"
date: 2026-09-11
---
Case: post-stats on a post with no Bonus section at all, and no
"Newsletter #N" heading, so the newsletter field stays 0.
## [Article one](https://a.invalid/one)
## [Article two](https://a.invalid/two)
+102
View File
@@ -0,0 +1,102 @@
// 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);
});
+304
View File
@@ -0,0 +1,304 @@
// Parity suite: every golden file under __fixtures__/golden/ was captured from
// the Go engine before the JavaScript port was trusted, so a mismatch here means
// the port drifted — not that the expectation is stale.
//
// Deterministic tier only: every case either touches no network or uses a
// non-resolving .invalid host, so `npm test` passes offline. Live-upstream cases
// live in network.test.js.
import assert from "node:assert/strict";
import { execFileSync, execSync } from "node:child_process";
import { readFileSync } from "node:fs";
import { join } from "node:path";
import { test } from "node:test";
import { detectYouTube } from "./add-url.js";
import { extractInnerUrl } from "./detect-image-source.js";
import { looksLikeChallenge } from "./fetch-via-defuddle.js";
import { findMostRecentNewsletter } from "./find-newsletter-number.js";
import { loadPublications, parseLastmod } from "./find-substack-post.js";
import { countPostEntries } from "./post-stats.js";
import {
captionForUuid,
extractCandidates,
itemLink,
itemTitle,
postTitleFromHtml,
} from "./html-text.js";
const HERE = import.meta.dirname;
const ENGINE = join(HERE, "index.js");
const FIXTURES = join(HERE, "__fixtures__");
const GOLDEN = join(FIXTURES, "golden");
const MARKUP = join(FIXTURES, "markup");
const MAIN = join(FIXTURES, "repo");
const EMPTY = join(FIXTURES, "repo-empty");
const FALLBACK = join(FIXTURES, "repo-year-fallback");
const EXIT_CODES = JSON.parse(readFileSync(join(GOLDEN, "_exit-codes.json"), "utf8"));
// Real Substack hosts: detect-image-source never touches the network.
const CDN =
"https://substackcdn.com/image/fetch/$s_!lpxK!,w_1100,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee_2484x3002.png";
const S3 =
"https://substack-post-media.s3.amazonaws.com/public/images/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee_1280x720.png";
// add-url HEADs the URL, so its image cases use non-resolving hosts that still
// carry the substack-post-media marker — same routing, stable "000" status.
const CDN_OFFLINE =
"https://cdn.invalid/image/fetch/w_1100,c_limit,f_auto/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee_2484x3002.png";
const S3_OFFLINE =
"https://substack-post-media.s3.invalid/public/images/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee_1280x720.png";
const UNKNOWN_IMG =
"https://cdn.invalid/image/fetch/f_auto/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F12345678-1234-1234-1234-123456789abc_100x100.avif?x=1";
/**
* Cases are [id, engine args, working directory]. The working directory is how
* content-scanning commands are pointed at a fixture tree: both engines resolve
* content/post from the process CWD, so no test-only config knob exists.
* @type {[string, string[], string][]}
*/
const CLI_CASES = [
// add-url — tracking-parameter stripping keeps surviving pairs verbatim
["add-url-tracking-params", ["add-url", "https://a.invalid/article?utm_source=x&utm_medium=y&fbclid=abc&keep=1&ref=z&s=1"], MAIN],
// %7E must not become ~ and + must not become %20
["add-url-encoding-preserved", ["add-url", "https://A.invalid/path/?a=%7Efoo+bar&b=2#frag"], MAIN],
// %20 must not become +, a valueless key must not gain =, and %7e must keep
// its lowercase hex — all three are lost by rebuilding the query
["add-url-query-verbatim", ["add-url", "https://a.invalid/path?a=%20x&flag&b=%7ez&utm_source=drop"], MAIN],
// every shape a URL is stored in must be found by the dedup scan
["add-url-dup-bare", ["add-url", "https://bare2.invalid/article"], MAIN],
["add-url-dup-trailing-slash", ["add-url", "https://slash.invalid/post/"], MAIN],
["add-url-dup-autolink", ["add-url", "https://autolink.invalid/entry"], MAIN],
["add-url-dup-markdown-link", ["add-url", "https://link.invalid/story"], MAIN],
// a needle that is only a PREFIX of a stored URL is not a duplicate
["add-url-prefix-not-duplicate", ["add-url", "https://prefix.invalid/posts/the-long"], MAIN],
["add-url-prefix-exact-duplicate", ["add-url", "https://prefix.invalid/posts/the-long-slug"], MAIN],
// transform variants of one Substack image share the uuid identity
["add-url-image-cdn-duplicate", ["add-url", CDN_OFFLINE], MAIN],
["add-url-image-s3-duplicate", ["add-url", S3_OFFLINE], MAIN],
["add-url-image-avif-query", ["add-url", UNKNOWN_IMG], MAIN],
["add-url-document-pdf", ["add-url", "https://a.invalid/file.pdf"], MAIN],
["add-url-video-mp4", ["add-url", "https://a.invalid/clip.mp4"], MAIN],
// no title/author keys at all when there is no metadata to report
["add-url-article-no-metadata", ["add-url", "https://a.invalid/plain"], MAIN],
// detect-image-source
["detect-image-cdn-wrapper", ["detect-image-source", CDN], MAIN],
["detect-image-raw-s3", ["detect-image-source", S3], MAIN],
["detect-image-non-substack", ["detect-image-source", "https://a.invalid/pic.png"], MAIN],
// a malformed percent sequence falls back to the raw substring
["detect-image-malformed-percent", ["detect-image-source", "https://substackcdn.com/image/fetch/w_100/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F%ZZbad.png"], MAIN],
// the newest year wins even though an older year holds a higher number
["find-newsletter-number-main", ["find-newsletter-number"], MAIN],
["find-newsletter-number-empty", ["find-newsletter-number"], EMPTY],
["find-newsletter-number-year-fallback", ["find-newsletter-number"], FALLBACK],
// plain text, count right-aligned in six columns, ties in first-seen order
["list-existing-tags-main", ["list-existing-tags"], MAIN],
["post-stats-full", ["post-stats", "content/post/2026/09/10/index.md"], MAIN],
["post-stats-empty-bonus", ["post-stats", "content/post/2026/09/11/empty-bonus.md"], MAIN],
["post-stats-no-bonus", ["post-stats", "content/post/2026/09/11/no-bonus.md"], MAIN],
];
/**
* runEngine invokes the real CLI surface, so exit codes are exercised too.
* @param {string[]} args
* @param {string} cwd
* @returns {{stdout: string, code: number}}
*/
function runEngine(args, cwd) {
try {
return { stdout: execFileSync("node", [ENGINE, ...args], { cwd, encoding: "utf8" }), code: 0 };
} catch (err) {
return { stdout: err.stdout ?? "", code: err.status ?? 1 };
}
}
/**
* @param {string} id
* @returns {string}
*/
function golden(id) {
const ext = id.startsWith("list-existing-tags") || id.startsWith("find-newsletter-number") ? "txt" : "json";
return readFileSync(join(GOLDEN, `${id}.${ext}`), "utf8");
}
for (const [id, args, cwd] of CLI_CASES) {
test(`cli parity: ${id}`, () => {
const { stdout, code } = runEngine(args, cwd);
// Byte-for-byte: key order is part of the contract the skills parse.
assert.equal(stdout, golden(id), `stdout differs from the Go capture for ${id}`);
assert.equal(code, EXIT_CODES[id]);
});
}
// --- extraction helpers, compared against saved upstream markup -------------
//
// These assert parsed objects rather than bytes: the Go capture harness emitted
// a map, whose key order is alphabetical rather than a contract.
test("extraction parity: real RSS item", () => {
const feed = readFileSync(join(MARKUP, "bytebytego-feed.xml"), "utf8");
const uuid = "02a5951e-febf-4289-8905-c67f45e754a1";
const item = feed.split("<item>").find((chunk) => chunk.includes(uuid));
assert.ok(item !== undefined, "the feed fixture no longer contains the uuid under test");
const expected = JSON.parse(golden("extract-rss-item"));
assert.equal(itemTitle(item), expected.itemTitle);
assert.equal(itemLink(item), expected.itemLink);
assert.equal(captionForUuid(item, uuid), expected.caption);
// The parser recovers one TOC bullet the Go regex dropped; every bullet the
// regex found is still present, in the same order.
const candidates = extractCandidates(item);
for (const c of expected.candidates) assert.ok(candidates.includes(c), `lost candidate: ${c}`);
});
test("extraction parity: nested figure, rune limits, dedupe", () => {
const html = readFileSync(join(MARKUP, "edge-cases.html"), "utf8");
const expected = JSON.parse(golden("extract-edge-html"));
// The innermost enclosing figure supplies the caption, not the outer one.
assert.equal(captionForUuid(html, "11111111-2222-3333-4444-555555555555"), expected.caption);
// A figure without a figcaption yields nothing rather than borrowing one.
assert.equal(captionForUuid(html, "99999999-8888-7777-6666-555555555555"), "");
assert.deepEqual(extractCandidates(html), expected.candidates);
assert.equal(postTitleFromHtml(html), expected.postTitle);
});
test("bullet length is measured in code points, not UTF-16 units", () => {
const html = readFileSync(join(MARKUP, "edge-cases.html"), "utf8");
const candidates = extractCandidates(html);
const kept = candidates.find((c) => c.startsWith("\u{1F680}"));
// 70 code points but 71 UTF-16 units: counting units would drop it.
assert.ok(kept !== undefined, "the 70-code-point bullet was dropped");
assert.equal([...kept].length, 70);
assert.ok(kept.length > 70);
// 71 code points is over the limit in both engines.
assert.ok(!candidates.some((c) => c.includes("BBB")), "the 71-code-point bullet was kept");
// Under six code points is dropped; exactly six is kept.
assert.ok(!candidates.includes("fiver"));
assert.ok(candidates.includes("sixchr"));
});
test("postTitleFromHtml precedence: og:title, then h1, then title", () => {
assert.equal(
postTitleFromHtml('<html><head><meta property="og:title" content="OG &amp; T"><title>Doc</title></head><body><h1>H</h1></body></html>'),
"OG & T",
);
assert.equal(postTitleFromHtml("<html><body><h1>Head <em>One</em></h1></body></html>"), "Head One");
assert.equal(postTitleFromHtml("<html><head><title>Only &amp; Title</title></head><body></body></html>"), "Only & Title");
assert.equal(postTitleFromHtml("<html><body><p>nothing</p></body></html>"), "");
});
test("fetch-via-defuddle rejects bad arguments with exit 2", () => {
const { code } = runEngine(["fetch-via-defuddle"], MAIN);
// The previous engine defined 2 as well, but its runner collapsed every
// nonzero exit to 1, so the distinction was never observable.
assert.equal(code, 2);
});
test("a reader that closes early does not produce a stack trace", () => {
// `node scripts/newsletter list-existing-tags | head -1` must exit quietly.
const stderrPath = join(HERE, "__fixtures__", "repo");
const out = execSync(`node ${JSON.stringify(ENGINE)} list-existing-tags | head -1`, {
cwd: stderrPath,
encoding: "utf8",
stdio: ["ignore", "pipe", "pipe"],
});
assert.equal(out, " 3 AI-Assisted\n");
});
test("an unknown subcommand exits 1 and prints usage", () => {
const { code } = runEngine(["no-such-command"], MAIN);
assert.equal(code, 1);
});
// --- unit coverage for the pure exports ------------------------------------
//
// Importing a command module must not run the CLI, which is why printJson lives
// in its own file. These tests are the standing proof of that.
test("detectYouTube recognises the three supported shapes and nothing else", () => {
assert.deepEqual(detectYouTube("https://www.youtube.com/watch?v=abc123"), { isYouTube: true, videoId: "abc123" });
assert.deepEqual(detectYouTube("https://youtu.be/abc123"), { isYouTube: true, videoId: "abc123" });
assert.deepEqual(detectYouTube("https://www.youtube.com/shorts/abc123"), { isYouTube: true, videoId: "abc123" });
// A playlist carries no single video identity, so it falls through to type.
assert.equal(detectYouTube("https://www.youtube.com/playlist?list=PL1").isYouTube, false);
assert.equal(detectYouTube("https://www.youtube.com/watch").isYouTube, false);
assert.equal(detectYouTube("https://youtu.be/").isYouTube, false);
assert.equal(detectYouTube("not a url").isYouTube, false);
});
test("extractInnerUrl unwraps the CDN form and survives a bad escape", () => {
assert.equal(
extractInnerUrl("https://substackcdn.com/image/fetch/w_1/https%3A%2F%2Fs3.example.com%2Fa.png"),
"https://s3.example.com/a.png",
);
// A malformed percent sequence returns the raw substring instead of throwing.
assert.equal(
extractInnerUrl("https://substackcdn.com/image/fetch/w_1/https%3A%2F%2Fs3.example.com%2F%ZZ.png"),
"https%3A%2F%2Fs3.example.com%2F%ZZ.png",
);
// An already-decoded inner URL is found too.
assert.equal(extractInnerUrl("https://cdn.example.com/x/https://s3.example.com/a.png"), "https://s3.example.com/a.png");
// No wrapper at all: the input comes back untouched.
assert.equal(extractInnerUrl("https://s3.example.com/a.png"), "https://s3.example.com/a.png");
});
test("countPostEntries attributes entries to the open Bonus subsection", () => {
const post = [
"## [An article](https://a.invalid/1)",
"### Bonus",
"**Images:**",
"![label](https://a.invalid/i.png)",
"**Videos:**",
"[A talk](https://a.invalid/v)",
"**Documents:**",
"[PDF: paper](https://a.invalid/p.pdf)",
].join("\n");
assert.deepEqual(countPostEntries(post), {
articles: 1, images: 1, videos: 1, documents: 1, total: 4,
});
// An image entry outside any subsection belongs to nothing.
assert.equal(countPostEntries("### Bonus\n![x](y)").images, 0);
// Article headings are counted outside Bonus only when they are links.
assert.equal(countPostEntries("## Plain heading").articles, 0);
});
test("parseLastmod accepts only the two layouts the sitemap crawl trusts", () => {
assert.ok(parseLastmod("2026-09-18") instanceof Date);
assert.ok(parseLastmod("2026-09-18T10:00:00Z") instanceof Date);
assert.ok(parseLastmod("2026-09-18T10:00:00+07:00") instanceof Date);
// No timezone: reading it as local time would shift the cutoff comparison.
assert.equal(parseLastmod("2026-09-18T10:00:00"), null);
assert.equal(parseLastmod("18/09/2026"), null);
assert.equal(parseLastmod(""), null);
});
test("loadPublications returns a non-empty list", () => {
const pubs = loadPublications();
assert.ok(Array.isArray(pubs));
assert.ok(pubs.length > 0);
assert.ok(pubs.every((p) => typeof p === "string" && p.length > 0));
});
test("findMostRecentNewsletter reads the tree under the process working directory", () => {
const cwd = process.cwd();
try {
process.chdir(MAIN);
// The newest year wins even though an older year holds #999.
assert.equal(findMostRecentNewsletter(), 132);
} finally {
process.chdir(cwd);
}
});
test("a bot wall is not mistaken for the page that was asked for", () => {
// A 200 challenge page extracts to a non-empty body; treating it as success
// would spend the local tier's turn and skip the proxy, which is the tier that
// fetches from a different IP.
assert.equal(looksLikeChallenge("Just a moment...", "Enable JavaScript and cookies to continue"), true);
assert.equal(looksLikeChallenge("Attention Required! | Cloudflare", "Please unblock challenges.example"), true);
assert.equal(looksLikeChallenge("", "Verify you are human by completing the action below."), true);
// An article that merely discusses Cloudflare is not a challenge.
const article = "Introduction\n".padEnd(500, "x") + " we moved our edge to Cloudflare last quarter";
assert.equal(looksLikeChallenge("How we cut latency in half", article), false);
assert.equal(looksLikeChallenge("Example Domain", "This domain is for use in documentation examples."), false);
});