Repository navigation
Expand file tree
/
Copy pathgithub-podcast-tools.test.ts
More file actions
99 lines (87 loc) · 4.17 KB
/
Copy pathgithub-podcast-tools.test.ts
File metadata and controls
99 lines (87 loc) · 4.17 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
import assert from "node:assert/strict";
import { test } from "node:test";
import { getEpisode, getLatestEpisodes } from "../src/github-podcast-tools.js";
const feedUrl = "https://feeds.simplecast.com/ioCY0vfY";
function episode(title: string, description: string): string {
return `<item>
<title>${title}</title>
<pubDate>Fri, 20 Mar 2026 12:00:00 GMT</pubDate>
<itunes:duration>00:30:00</itunes:duration>
<description>${description}</description>
<link>https://example.com/episode?a=1&b=2</link>
</item>`;
}
function feed(items: string): string {
return `<?xml version="1.0"?>
<rss xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd">
<channel>${items}</channel>
</rss>`;
}
test("reads episode metadata and converts CDATA HTML to plain text", async (context) => {
const fetchMock = context.mock.method(globalThis, "fetch", async (
input: Parameters<typeof fetch>[0], options: Parameters<typeof fetch>[1],
) => {
assert.equal(input, feedUrl);
assert.ok(options?.signal instanceof AbortSignal);
assert.equal(options.signal.aborted, false);
return new Response(feed(episode(
"42: GitHub & Copilot",
'<![CDATA[<p>Build & learn 'together' 🚀.</p>]]>',
)));
});
assert.deepEqual(await getEpisode(), {
episodeNumber: 42,
title: "42: GitHub & Copilot",
published: "Fri, 20 Mar 2026 12:00:00 GMT",
duration: "00:30:00",
description: "Build & learn 'together' \u{1F680}.",
episodeUrl: "https://example.com/episode?a=1&b=2",
sourceFeedUrl: feedUrl,
});
assert.equal(fetchMock.mock.callCount(), 1);
});
test("handles escaped HTML and CDATA without recursively decoding entities", async (context) => {
context.mock.method(globalThis, "fetch", async () => new Response(feed([
episode("1: &lt;literal&gt;",
"<![CDATA[<p>&lt;tag&gt; &#39;</p>]]>"),
episode("2: Escaped HTML",
"<p>A &amp; B &#39;quoted&#39;</p>"),
episode("3: Mixed XML and CDATA",
"Before & <![CDATA[<b>after &lt;tag&gt;</b>]]>"),
].join(""))));
const episodes = await getLatestEpisodes();
assert.equal(episodes[0]?.title, "1: <literal>");
assert.equal(episodes[0]?.description, "<tag> '");
assert.equal(episodes[1]?.description, "A & B 'quoted'");
assert.equal(episodes[2]?.description, "Before & after <tag>");
});
test("ignores script and style contents, including mixed-case and malformed HTML", async (context) => {
context.mock.method(globalThis, "fetch", async () => new Response(feed([
episode("1: Scripts",
'<![CDATA[<p>Safe</p><ScRiPt>bad <script data-x=">">still bad</ScRiPt><STYLE>bad css</STYLE><p> text</p>]]>'),
episode("2: Unclosed script", "<![CDATA[<p>Keep</p><script>discard the rest]]>"),
episode("3: Malformed tags", '<![CDATA[<p title=">">Readable</p><!----><br> text]]>'),
].join(""))));
assert.deepEqual((await getLatestEpisodes()).map(({ description }) => description),
["Safe text", "Keep", "Readable text"]);
});
test("returns only the ten newest episodes and matches titles case-insensitively", async (context) => {
const items = Array.from({ length: 12 }, (_, index) =>
episode(`${index + 1}: GitHub & Copilot`, "<![CDATA[<p>Episode</p>]]>")).join("");
context.mock.method(globalThis, "fetch", async () => new Response(feed(items)));
const episodes = await getLatestEpisodes();
assert.equal(episodes.length, 10);
assert.deepEqual(episodes.map(({ episodeNumber }) => episodeNumber),
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
assert.equal((await getEpisode("11: github & copilot")).episodeNumber, 11);
await assert.rejects(getEpisode("Unknown episode"), /was not found/);
});
test("reports empty feeds and HTTP failures", async (context) => {
const fetchMock = context.mock.method(globalThis, "fetch",
async () => new Response(feed("")));
assert.deepEqual(await getLatestEpisodes(), []);
await assert.rejects(getEpisode(), /contained no episodes/);
fetchMock.mock.mockImplementation(async () => new Response(null, { status: 503 }));
await assert.rejects(getLatestEpisodes(), /RSS feed: 503/);
await assert.rejects(getEpisode(), /RSS feed: 503/);
});