// Forum posts (platform "xenforo") in the model and the store: detection, the // stored-record validator, the conversation a forum post belongs to, and // upsertPosts — the import's write, which updates an edited post in place // (append-only, last record wins). SYNTHETIC posts only. // // Run with: pnpm --filter yt-dlp-transcript-common exec tsx --test lib/forumPosts.test.ts import { test } from "node:test"; import assert from "node:assert/strict"; import { mkdtemp, readFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import path from "node:path"; import { detectPlatform, isSocialPlatform } from "./platform"; import { isPostPlatform, parsePost, postConversation, postPermalink, postPlatformLabel, uploadDateFromCreatedAt, type Post, } from "./posts"; import { readAllPosts, readSeenPostIds, supersedesPost, upsertPosts } from "./posts-server"; import { handleFromAccountUrl } from "../social/fetchers"; function forumPost(id: string, position: number, extra: Partial = {}): Post { const createdAt = new Date(Date.UTC(2026, 0, 1, 0, position)).toISOString(); return { id, slug: `teapots/${id}`, channelSlug: "teapots", author: `Member${position}`, createdAt, uploadDate: uploadDateFromCreatedAt(createdAt), text: `Words of post ${position}.`, url: `https://forum.example/posts/${id}/`, platform: "xenforo", isReply: position > 1, isRepost: false, links: [], forum: { host: "forum.example", threadId: "4242", position, page: 1 }, ...extra, }; } test("detection: forum thread URLs are the xenforo platform, a social one", () => { assert.equal(detectPlatform("https://forum.example/threads/a-title.4242/"), "xenforo"); assert.equal(detectPlatform("https://forum.example/threads/a-title.4242/page-9"), "xenforo"); assert.equal(detectPlatform("https://forum.example/index.php?threads/a-title.4242/"), "xenforo"); // Kiwi Farms is known by host (it runs XenForo). assert.equal(detectPlatform("https://kiwifarms.st/members/someone.1/"), "xenforo"); assert.equal(detectPlatform("https://forum.example/members/someone.1/"), null); assert.equal(detectPlatform("https://www.youtube.com/@x/threads"), "youtube"); assert.equal(isSocialPlatform("xenforo"), true); assert.equal(isPostPlatform("xenforo"), true); assert.equal(postPlatformLabel("xenforo"), "Forum"); assert.equal(handleFromAccountUrl("https://forum.example/threads/a-title.4242/page-3"), "a-title.4242"); assert.equal(postPermalink("xenforo", "", "77", "https://forum.example/"), "https://forum.example/posts/77/"); }); test("the validator keeps forum facts and media, and drops what is malformed", () => { const p = forumPost("10", 3, { media: [ { kind: "image", url: "https://images.example/a.jpg", name: "a.jpg" }, { kind: "embed", url: "https://www.youtube.com/watch?v=AbCdEfGhIjK", provider: "youtube" }, ], mediaCount: 2, forum: { host: "forum.example", threadId: "4242", threadTitle: "Teapots", position: 3, page: 1, authorId: "9", editedAt: "2026-01-02T00:00:00.000Z", quotes: [{ postId: "8", author: "Member1" }], }, }); assert.deepEqual(parsePost(JSON.parse(JSON.stringify(p))), p); const bad = parsePost({ ...p, media: [{ kind: "sticker", url: "x" }, { kind: "image" }], forum: { host: "forum.example" }, }); assert.equal(bad?.media, undefined); assert.equal(bad?.forum, undefined); // A forum post with no stored url gets its permalink from the forum host. const { url: _url, ...noUrl } = p; assert.equal(parsePost(noUrl)?.url, "https://forum.example/posts/10/"); }); test("a forum post's conversation is its quote graph, in thread order", () => { const a = forumPost("1", 1); const b = forumPost("2", 2, { forum: { host: "forum.example", threadId: "4242", position: 2, quotes: [{ postId: "1" }] } }); const c = forumPost("3", 3, { forum: { host: "forum.example", threadId: "4242", position: 3, quotes: [{ postId: "2" }] } }); const d = forumPost("4", 4, { forum: { host: "forum.example", threadId: "4242", position: 4, quotes: [{ postId: "3" }] } }); const unrelated = forumPost("5", 5); const all = [unrelated, d, c, b, a]; assert.deepEqual(postConversation(c, all).map((p) => p.id), ["1", "2", "3", "4"]); assert.deepEqual(postConversation(unrelated, all).map((p) => p.id), ["5"]); assert.deepEqual(postConversation(c, all, 1).map((p) => p.id), ["2", "3", "4"]); }); test("a non-forum post's conversation is still its reply thread", () => { const base = { ...forumPost("x", 1), platform: "bluesky" as const, forum: undefined }; const root = { ...base, id: "r", threadId: "r", createdAt: "2026-01-01T00:00:00.000Z" }; const reply = { ...base, id: "s", threadId: "r", createdAt: "2026-01-01T00:01:00.000Z" }; const other = { ...base, id: "t", threadId: "t" }; assert.deepEqual(postConversation(reply, [other, reply, root]).map((p) => p.id), ["r", "s"]); }); test("supersedesPost: a later edit wins, an older one never does", () => { const stored = forumPost("1", 1, { forum: { host: "h", threadId: "1", editedAt: "2026-02-01T00:00:00.000Z" } }); const newer = { ...stored, text: "changed", forum: { ...stored.forum!, editedAt: "2026-03-01T00:00:00.000Z" } }; const older = { ...stored, text: "older words", forum: { ...stored.forum!, editedAt: "2026-01-15T00:00:00.000Z" } }; const unedited = { ...stored, text: "never edited", forum: { host: "h", threadId: "1" } }; assert.equal(supersedesPost(stored, { ...stored }), false); assert.equal(supersedesPost(stored, newer), true); assert.equal(supersedesPost(stored, older), false); assert.equal(supersedesPost(stored, unedited), false); const plain = forumPost("2", 2); assert.equal(supersedesPost(plain, { ...plain, text: "re-read" }), true); }); test("upsertPosts: new posts appended, an edit updates the post, a repeat changes nothing", async () => { const root = await mkdtemp(path.join(tmpdir(), "forum-upsert-")); const first = await upsertPosts(root, [forumPost("1", 1), forumPost("2", 2)]); assert.deepEqual([first.written, first.updated, first.unchanged], [2, 0, 0]); const edited = forumPost("2", 2, { text: "Words of post 2, edited.", forum: { host: "forum.example", threadId: "4242", position: 2, page: 1, editedAt: "2026-01-05T00:00:00.000Z" }, }); const second = await upsertPosts(root, [forumPost("1", 1), edited, forumPost("3", 3)]); assert.deepEqual([second.written, second.updated, second.unchanged], [1, 1, 1]); const all = await readAllPosts(root); assert.deepEqual(all.map((p) => p.id), ["3", "2", "1"]); assert.equal(all.find((p) => p.id === "2")?.text, "Words of post 2, edited."); assert.equal((await readSeenPostIds(root)).size, 3); // The archive lists each id once: an update is not a new post. const archive = await readFile(path.join(root, "posts-archive"), "utf8"); assert.equal(archive.trim().split("\n").length, 3); const third = await upsertPosts(root, [edited]); assert.deepEqual([third.written, third.updated, third.unchanged], [0, 0, 1]); });