T-HUB/apps/api/src/feeds/feed-normalize.test.ts
glass beadian aa5e262f3a Tactical Media Broadcast Hub — initial release
Self-hosted multi-platform broadcast newsroom for activist collectives:
publishing pipeline with group approval and DST-proof scheduling, TweetDeck-style
column wall with hostile-content-safe rendering, live control room with Auto
Director, bridged chat governance with polls and quorum, distributed sandboxed
transcode workers, encrypted content-addressed archive, monthly analytics.

MIT licensed. See README.md and docs/.
2026-10-10 11:56:42 +00:00

366 lines
23 KiB
TypeScript

import { randomUUID } from "node:crypto";
import { describe, expect, it } from "vitest";
import { ConnectorRegistry } from "@tactical-media-broadcast-hub/connector-sdk";
import type { ConnectorConnection, FeedItem } from "@tactical-media-broadcast-hub/core";
import { createMockSocialConnector, createMockSocialState } from "../../../../packages/connector-mock-social/src/index.js";
import { DEFAULT_MEDIA_PROXY, parseProviderHtml, type CustomEmoji, type LinkPreview, type MediaProxyPolicy, type RichTextDocument } from "../../../../packages/safe-content/src/index.js";
import { astToPlainText, feedAuthorRef, feedItemMedia, normalizeFeedItem, type MediaRef, type NormalizedFeedItem } from "./feed-normalize.js";
import { FeedsService, feedCacheKey } from "./feeds.service.js";
import type { FeedDependencies } from "./feeds.dependencies.js";
import { MemoryFeedRedis } from "./testing.js";
/** Per-connector fixtures (design §3.2 provider table) + hostile-HTML cases. */
interface ProviderFixture {
provider: string;
contentKind?: "text" | "html";
instanceUrl?: string;
emojis?: readonly CustomEmoji[];
item: FeedItem;
url?: unknown;
expiresAt?: unknown;
linkPreview?: unknown;
}
const PROXY: MediaProxyPolicy = DEFAULT_MEDIA_PROXY;
const connection = (fields: Record<string, string> = {}): ConnectorConnection => ({
id: "conn-1", workspaceId: "ws-1", provider: "mastodon", channelId: "conn-1",
credentialId: { kind: "credential-ref", id: "cred-1" }, fields, createdAt: new Date(),
});
const mastodonStatus: FeedItem = {
id: "1001", provider: "mastodon", remoteId: "1001", authorHandle: "alice@foreso.social",
// The exact HTML shape Mastodon emits: p/br/a, mention + hashtag + invisible
// ellipsis link spans, HTML-escaped ampersands, raw emoji <img> tags.
text: '<p>A call for <span class="h-card"><a href="https://foreso.social/@bob" class="u-url mention">@<span>bob</span></a></span> &amp; friends</p><p>#<a href="https://foreso.social/tags/acampada26" rel="tag">acampada26</a> kicks off <a href="https://example.org/manifesto" rel="nofollow noopener noreferrer noopener" target="_blank"><span class="invisible">https://</span><span class="ellipsis">example.org/manifes</span><span class="invisible">to</span></a> <img src="https://foreso.social/emoji/ok.png" alt=":ok:" draggable="false"><br>line two <b>bolded</b></p>',
mediaUrls: ["https://cdn.foreso.social/media/1234.png", "https://cdn.foreso.social/media/clip.mp4"],
createdAt: new Date("2026-10-06T12:00:00.000Z"),
metrics: { reblogs: 2, replies: 1, favourites: 5, broken: Number.NaN, infinite: Number.POSITIVE_INFINITY },
};
const xssStatus: FeedItem = {
id: "2002", provider: "mastodon", remoteId: "2002", authorHandle: "mallory@evil.example",
text: '<p>hi <script>alert("pwned")</script><img src=x onerror="alert(1)"><iframe srcdoc="<script>alert(2)</script>"></iframe><style>body{}</style><svg><circle/></svg><a href="javascript:alert(3)">click</a><a href="https://ok.example/pass">pass</a><a href="java\tscript:alert(4)">tabbed</a></p><p>after</p>',
mediaUrls: ["javascript:alert(5)", "data:text/html,evil", "//protocol-relative.example/x.png", "https://cdn.example/ok.png"],
createdAt: new Date("2026-10-06T12:01:00.000Z"),
metrics: {},
};
const fixturePlainText: ProviderFixture = {
// Plain-text provider source (Bluesky/Telegram-style): auto-linked, no HTML.
provider: "bluesky",
item: {
id: "1", provider: "bluesky", remoteId: "at://did:plc:abc/app.bsky.feed.post/1",
authorHandle: "acampada.bsky.social", mediaUrls: [], createdAt: new Date("2026-10-06T10:00:00.000Z"),
metrics: { likes: 3 },
text: "Line one\n\n hub https://github.com/eche/acampada26/blob/main/core/search.ts #acampada26 @alice@foreso.social",
},
url: "https://bsky.app/profile/acampada.bsky.social/post/1",
};
const fixtureMastodonHtml: ProviderFixture = {
// Mastodon status HTML: allowlist parser, mention + ellipsis link + emoji.
provider: "mastodon",
contentKind: "html",
emojis: [{ shortcode: "ok", url: "https://foreso.social/emoji/ok.png" }],
item: mastodonStatus,
url: "https://foreso.social/@alice/1001",
};
const fixtureHostileHtml: ProviderFixture = {
// Hostile HTML from a compromised/bogus federated peer.
provider: "mastodon",
contentKind: "html",
instanceUrl: "https://EVIL.example:443/",
item: xssStatus,
url: "javascript:alert(6)",
linkPreview: { url: "javascript:alert(7)", title: "javascript:alert(8)", image: "javascript:alert(9)" },
};
const fixtureMastodonLinkPreview: ProviderFixture = {
// Mastodon HTML + card (link-preview) payload as one source row.
provider: "mastodon",
contentKind: "html",
item: { ...mastodonStatus, id: "1002", remoteId: "1002", text: '<p>with a <span class="h-card"><a href="https://foreso.social/@bob" class="u-url mention">@<span>bob</span></a></span> card</p>', mediaUrls: [] },
url: "https://foreso.social/@alice/1002",
linkPreview: {
url: "https://example.org/manifesto", title: "Manifesto &lt;scripts&gt; &amp; plans",
description: "A plan", siteName: "example dot org", image: "https://cdn.example.org/cover.png",
},
};
const fixtures: Record<"plainText" | "mastodonHtml" | "hostileHtml" | "mastodonLinkPreview", ProviderFixture> = {
plainText: fixturePlainText,
mastodonHtml: fixtureMastodonHtml,
hostileHtml: fixtureHostileHtml,
mastodonLinkPreview: fixtureMastodonLinkPreview,
};
/** No provider CDN URL outside the media-proxy query string, ever. */
function assertProxyOnly(media: MediaRef[]): void {
const serialized = JSON.stringify(media);
let index = serialized.indexOf("https://");
while (index !== -1) {
expect(serialized.slice(0, index)).toContain(`${PROXY.proxyPath}?url=`);
index = serialized.indexOf("https://", index + 1);
}
for (const ref of media) expect(ref.proxyUrl.startsWith(`${PROXY.proxyPath}?url=`)).toBe(true);
}
/** Raw-remnant scan applied to every fixture's AST, plain text and media. */
function assertZeroRawRemnants(ast: RichTextDocument, plainText: string, media: MediaRef[]): void {
const astJson = JSON.stringify(ast);
const hostile: RegExp[] = [
/<\s*(script|iframe|style|img|svg|math|template|form|object|embed|applet|frame)\b/i,
/<\s*\/\s*/, /<!--/, /&#/, /&amp;|&lt;|&gt;|&quot;|&apos;|&nbsp;/i,
/javascript:/i, /vbscript:/i, /data:/i, /file:/i, /\/\/evil/, /\/\/protocol-relative/,
/on(?:error|load|click|mouseover)\b|on\w+\s*=/i, /srcdoc|src\s*=|href\s*=/i, /alert\s*\(/i,
];
for (const haystack of [astJson, plainText, JSON.stringify(media)]) {
for (const pattern of hostile) expect(haystack).not.toMatch(pattern);
}
expect(astJson).not.toContain("<p>");
expect(astJson).not.toContain("<b>");
assertProxyOnly(media);
}
function normalizeFixture(fixture: ProviderFixture) {
return normalizeFeedItem(fixture.item, { ...connection(fixture.instanceUrl ? { instanceUrl: fixture.instanceUrl } : {}), provider: fixture.provider }, {
contentKind: fixture.contentKind,
emojis: fixture.emojis,
url: fixture.url,
expiresAt: fixture.expiresAt,
linkPreview: fixture.linkPreview,
});
}
const flat = (ast: RichTextDocument): string[] =>
ast.children.map((paragraph) => paragraph.children.map((node) => node.type).join(","));
describe("feed normalization v2 (columns P0c)", () => {
it("keeps legacy /feeds fields byte-identical while adding the v2 fields additively", () => {
const normalized = normalizeFeedItem(mastodonStatus, connection(), { url: "https://foreso.social/@alice/1001" });
expect(normalized).not.toBeNull();
expect(normalized).toMatchObject({
id: "1001", remoteId: "1001", provider: "mastodon", connectionId: "conn-1",
authorHandle: "alice@foreso.social", text: mastodonStatus.text,
mediaUrls: mastodonStatus.mediaUrls,
metrics: { reblogs: 2, replies: 1, favourites: 5 }, // NaN/Infinity dropped
createdAt: new Date("2026-10-06T12:00:00.000Z"),
});
expect(Object.keys(normalized?.metrics ?? {})).toEqual(["reblogs", "replies", "favourites"]);
expect(normalized?.authorRef).toBe("mastodon:alice@foreso.social");
expect(normalized?.url).toBe("https://foreso.social/@alice/1001");
expect(normalized?.content?.type).toBe("doc");
expect(typeof normalized?.plainText).toBe("string");
expect(Array.isArray(normalized?.media)).toBe(true);
expect(normalized?.expiresAt).toBeNull();
});
it("normalizes the plain-text fixture with auto-linked http(s) URLs only", () => {
const normalized = normalizeFixture(fixturePlainText);
expect(normalized).not.toBeNull();
assertZeroRawRemnants(normalized?.content as RichTextDocument, normalized?.plainText as string, normalized?.media as MediaRef[]);
expect(flat(normalized?.content as RichTextDocument)).toEqual([
"text",
"text,link,text,hashtag,text,mention",
]);
const link = normalized?.content?.children[1]?.children.find((node) => node.type === "link");
expect(link?.type === "link" && link.href).toBe("https://github.com/eche/acampada26/blob/main/core/search.ts");
expect(link?.type === "link" && link.host).toBe("github.com");
expect(normalized?.plainText).toBe("Line one\n\n hub github.com/eche/acampada26/blob/main/core/search.ts #acampada26 @alice@foreso.social");
expect(normalized?.authorRef).toBe("bluesky:acampada.bsky.social");
expect(normalized?.url).toBe("https://bsky.app/profile/acampada.bsky.social/post/1");
expect(normalized?.media).toEqual([]);
expect(normalized?.expiresAt).toBeNull();
});
it("normalizes the Mastodon HTML fixture (mention, ellipsis link, emoji, breaks) with zero raw remnants", () => {
const normalized = normalizeFixture(fixtureMastodonHtml);
expect(normalized).not.toBeNull();
const ast = normalized?.content as RichTextDocument;
assertZeroRawRemnants(ast, normalized?.plainText as string, normalized?.media as MediaRef[]);
expect(ast.truncated).toBe(false);
// Invisible spans drop their URL scheme, the ellipsis span becomes "…",
// the mention qualifies its handle from the href host, the emoji <img>
// alt becomes a proxied emoji node, <b> unwraps to plain text.
expect(normalized?.plainText).toBe("A call for @bob@foreso.social & friends\n\n#acampada26 kicks off example.org/manifes… :ok:\nline two bolded");
expect(flat(ast)).toEqual([
"text,mention,text",
"text,link,text,link,text,emoji,linebreak,text",
]);
const mention = ast.children[0]?.children[1];
expect(mention).toEqual({ type: "mention", handle: "bob@foreso.social", href: "https://foreso.social/@bob" });
const link = ast.children[1]?.children.find((node) => node.type === "link" && node.href === "https://example.org/manifesto");
expect(link?.type === "link" && link.host).toBe("example.org");
expect(link?.type === "link" && link.children.map((child) => child.type === "text" ? child.text : null).join("")).toBe("example.org/manifes…");
const emoji = ast.children[1]?.children.find((node) => node.type === "emoji");
expect(emoji?.type === "emoji" && emoji.src).toBe(`${PROXY.proxyPath}?url=${encodeURIComponent("https://foreso.social/emoji/ok.png")}`);
// Attachments become proxy media refs; kind is sniffed from the extension.
expect(normalized?.media).toEqual([
{ kind: "image", proxyUrl: `${PROXY.proxyPath}?url=${encodeURIComponent("https://cdn.foreso.social/media/1234.png")}` },
{ kind: "video", proxyUrl: `${PROXY.proxyPath}?url=${encodeURIComponent("https://cdn.foreso.social/media/clip.mp4")}` },
]);
assertProxyOnly(normalized?.media as MediaRef[]);
});
it("normalizes the hostile-HTML fixture: scripts, iframes, handlers and unsafe schemes leave no remnants", () => {
const normalized = normalizeFixture(fixtureHostileHtml);
expect(normalized).not.toBeNull();
const ast = normalized?.content as RichTextDocument;
assertZeroRawRemnants(ast, normalized?.plainText as string, normalized?.media as MediaRef[]);
// Script/style/iframe/svg contents vanish entirely; img alt is empty; the
// javascript: link degrades to its inert label; the tab-split "java\tscript:"
// scheme is refused; only the one safe link survives as a real link node.
expect(flat(ast)).toEqual(["text,link,text", "text"]);
const onlyLink = ast.children[0]?.children[1];
expect(onlyLink).toEqual({
type: "link", href: "https://ok.example/pass", host: "ok.example",
children: [{ type: "text", text: "pass" }],
});
expect(normalized?.plainText).toBe("hi clickpasstabbed\n\nafter");
expect(normalized?.plainText).not.toMatch(/alert|pwned|onerror|javascript|srcdoc/i);
// Federated host appends to the author ref via instanceUrl (punycoded, port stripped).
expect(normalized?.authorRef).toBe("mastodon:mallory@evil.example");
// Permalink with an unsafe scheme is refused, not carried.
expect(normalized?.url).toBeNull();
// Hostile media URLs are dropped wholesale; only the safe https URL is proxied.
expect(normalized?.mediaUrls).toHaveLength(4); // legacy field stays as the source delivered it
expect(normalized?.media).toEqual([{ kind: "image", proxyUrl: `${PROXY.proxyPath}?url=${encodeURIComponent("https://cdn.example/ok.png")}` }]);
assertProxyOnly(normalized?.media as MediaRef[]);
// Hostile card: unsafe URL/title/image all rejected → no preview survives.
expect(normalized?.linkPreview).toBeUndefined();
});
it("sanitizes the Mastodon link-preview card payload (text fields, proxied image, safe href)", () => {
const normalized = normalizeFixture(fixtureMastodonLinkPreview);
expect(normalized).not.toBeNull();
const preview = normalized?.linkPreview as LinkPreview;
expect(preview).not.toBeNull();
expect(preview).toEqual({
type: "linkPreview", href: "https://example.org/manifesto", host: "example.org",
title: "Manifesto <scripts> & plans", description: "A plan", siteName: "example dot org",
image: `${PROXY.proxyPath}?url=${encodeURIComponent("https://cdn.example.org/cover.png")}`,
});
// OpenGraph fields are text: the entity-decoded title carries no live markup.
expect(preview.title).toContain("<scripts>");
expect(preview.title).not.toMatch(/&lt;|&amp;/);
assertZeroRawRemnants(normalized?.content as RichTextDocument, normalized?.plainText as string, normalized?.media as MediaRef[]);
});
it("round-trips every fixture AST through JSON (storage shape) with a stable plainText", () => {
for (const [name, fixture] of Object.entries(fixtures)) {
const normalized = normalizeFixture(fixture);
expect(normalized, name).not.toBeNull();
const ast = normalized?.content as RichTextDocument;
assertZeroRawRemnants(ast, normalized?.plainText as string, normalized?.media as MediaRef[]);
const roundTripped = JSON.parse(JSON.stringify(ast)) as RichTextDocument;
expect(roundTripped, name).toEqual(ast);
expect(astToPlainText(roundTripped), name).toBe(normalized?.plainText);
}
});
it("matches the reference parseProviderHtml derivation for the Mastodon fixture", () => {
const fixture = fixtureMastodonHtml;
const reference = parseProviderHtml(fixture.item.text, { emojis: fixture.emojis });
const normalized = normalizeFixture(fixture);
expect(normalized?.content).toEqual(reference);
expect(astToPlainText(reference)).toBe(normalized?.plainText);
});
it("builds canonical author refs: handle host wins, instanceUrl fills federated hosts, IDN punycodes", () => {
expect(feedAuthorRef("mastodon", "alice@foreso.social")).toBe("mastodon:alice@foreso.social");
expect(feedAuthorRef("mastodon", "alice", "https://fedoraproject.org/@alice")).toBe("mastodon:alice@fedoraproject.org");
expect(feedAuthorRef("mastodon", "alice", "https://bücher.example:8443/path")).toBe("mastodon:alice@xn--bcher-kva.example");
expect(feedAuthorRef("mastodon", "alice", "gopher://odd.example")).toBe("mastodon:alice");
expect(feedAuthorRef("mastodon", "@bob@", "https://ok.example")).toBeNull();
expect(feedAuthorRef("mastodon", "has space")).toBeNull();
expect(feedAuthorRef("mastodon", "")).toBeNull();
});
it("expires items per ToS: explicit Date, date-like string, and unparseable → null", () => {
const base = { ...mastodonStatus, mediaUrls: [] };
const at = normalizeFeedItem(base, connection(), { expiresAt: new Date("2026-11-05T00:00:00.000Z") });
expect(at?.expiresAt).toEqual(new Date("2026-11-05T00:00:00.000Z"));
const fromString = normalizeFeedItem(base, connection(), { expiresAt: "2026-11-05T00:00:00.000Z" });
expect(fromString?.expiresAt).toEqual(new Date("2026-11-05T00:00:00.000Z"));
expect(normalizeFeedItem(base, connection(), { expiresAt: "not-a-date" })?.expiresAt).toBeNull();
expect(normalizeFeedItem(base, connection())?.expiresAt).toBeNull();
});
it("drops items failing the legacy validation", () => {
expect(normalizeFeedItem({ ...mastodonStatus, remoteId: 42 as unknown as string }, connection())).toBeNull();
expect(normalizeFeedItem({ ...mastodonStatus, createdAt: new Date(Number.NaN) }, connection())).toBeNull();
expect(normalizeFeedItem({ ...mastodonStatus, authorHandle: 7 as unknown as string }, connection())).toBeNull();
expect(normalizeFeedItem(mastodonStatus, connection())).not.toBeNull();
});
it("proxies only safe media: scheme tricks, protocol-relative and oversized URLs are dropped", () => {
const media = feedItemMedia([
"https://cdn.example/a.png", "javascript:alert(1)", "data:text/html,x", "//rel.example/b.png",
"https://user:pass@cred.example/c.jpg", "not a url", "https://cdn.example/d.webm?v=2", 42,
]);
// Credential-bearing URLs are refused by the same safe-URL gate.
expect(media.map((ref) => ref.kind)).toEqual(["image", "video"]); // extension sniff
expect(media[1]?.proxyUrl).toBe(`${PROXY.proxyPath}?url=${encodeURIComponent("https://cdn.example/d.webm?v=2")}`);
assertProxyOnly(media);
expect(feedItemMedia("not-an-array")).toEqual([]);
});
it("serves legacy cache payloads (no AST) and v2 payloads through the unchanged /feeds shape", async () => {
const state = createMockSocialState();
state.feed = [];
const mock = createMockSocialConnector(state);
const registry = new ConnectorRegistry(); registry.register(mock);
const redis = new MemoryFeedRedis();
const connectionId = randomUUID();
const deps: FeedDependencies = {
redis, registry, workspaces: async () => ["ws-1"],
connections: async () => [{ id: connectionId, provider: "mock-social", channelId: connectionId, fields: {}, health: "healthy", credentialRotatedAt: null, recentPublishes: [], createdAt: new Date().toISOString() }],
connection: async (_ws, id) => ({ id, workspaceId: "ws-1", provider: "mock-social", channelId: id, credentialId: { kind: "credential-ref", id }, fields: {}, createdAt: new Date() }),
visible: async () => [connectionId], audit: async () => undefined, requestsPerSecond: () => 0.5, random: () => 0.5,
};
const service = new FeedsService(deps);
const session = { workspaceId: "ws-1", userId: randomUUID(), role: "Owner", authzEpoch: 1, issuedAt: 0, expiresAt: 9_999_999_999 };
const key = feedCacheKey("ws-1", connectionId);
const legacy = { id: "legacy", remoteId: "legacy", provider: "mock-social", connectionId, authorHandle: "old@social", text: "legacy item", mediaUrls: ["https://cdn.old.example/a.png"], createdAt: new Date("2026-10-01T00:00:00.000Z"), metrics: { likes: 1 } };
await redis.set(key, JSON.stringify({ items: [legacy], fetchedAt: new Date().toISOString(), handle: "old@social", displayName: "Old" }), { pxMs: 99_000 });
const served = await service.read(session);
expect(served.items).toHaveLength(1);
expect(served.items[0]).toMatchObject({ remoteId: "legacy", text: "legacy item", metrics: { likes: 1 } });
expect(served.items[0]?.createdAt).toBeInstanceOf(Date);
expect("content" in (served.items[0] as object)).toBe(false); // additive: absent, not null
expect("expiresAt" in (served.items[0] as object)).toBe(false);
// A v2 payload keeps the legacy fields and adds the new ones through the same response.
const normalized = normalizeFeedItem({ ...legacy, id: "v2", remoteId: "v2" }, { ...connection(), provider: "mock-social" }, { url: "https://old.example/legacy/2", expiresAt: new Date("2026-11-01T00:00:00.000Z") });
await redis.set(key, JSON.stringify({ items: [normalized], fetchedAt: new Date().toISOString(), handle: "old@social", displayName: "Old" }), { pxMs: 99_000 });
const v2Served = await service.read(session);
expect(v2Served.items[0]).toMatchObject({ remoteId: "v2", text: "legacy item", authorRef: "mock-social:old@social", url: "https://old.example/legacy/2" });
expect((v2Served.items[0] as { content?: { type?: string } }).content?.type).toBe("doc");
expect(v2Served.items[0]?.createdAt).toBeInstanceOf(Date);
expect((v2Served.items[0] as unknown as { expiresAt?: Date }).expiresAt).toEqual(new Date("2026-11-01T00:00:00.000Z"));
});
it("still polls and serves end-to-end: connector feed → v2 cache → /feeds response", async () => {
const state = createMockSocialState();
state.feed = [{
id: "e2e", remoteId: "e2e", provider: "mock-social", authorHandle: "carol@social",
text: "End to end #acampada", mediaUrls: ["https://cdn.social/e2e.png"],
createdAt: new Date("2026-10-06T09:00:00.000Z"), metrics: { likes: 2 },
}];
const mock = createMockSocialConnector(state);
const registry = new ConnectorRegistry(); registry.register(mock);
const deps: FeedDependencies = {
redis: new MemoryFeedRedis(), registry, workspaces: async () => ["ws-1"],
connections: async () => [{ id: "conn-e2e", provider: "mock-social", channelId: "conn-e2e", fields: {}, health: "healthy", credentialRotatedAt: null, recentPublishes: [], createdAt: new Date().toISOString() }],
connection: async (_ws, id) => ({ id, workspaceId: "ws-1", provider: "mock-social", channelId: id, credentialId: { kind: "credential-ref", id }, fields: {}, createdAt: new Date() }),
visible: async () => ["conn-e2e"], audit: async () => undefined, requestsPerSecond: () => 0.5, random: () => 0.5,
};
const service = new FeedsService(deps);
expect(await service.pollWorkspace("ws-1")).toEqual({ polled: 1, skipped: 0, failed: 0 });
const response = await service.read({ workspaceId: "ws-1", userId: randomUUID(), role: "Owner", authzEpoch: 1, issuedAt: 0, expiresAt: 9_999_999_999 });
expect(response.items).toHaveLength(1);
const item = response.items[0] as NormalizedFeedItem | undefined;
expect(item).toBeDefined();
expect(item).toMatchObject({ remoteId: "e2e", text: "End to end #acampada", authorRef: "mock-social:carol@social", metrics: { likes: 2 } });
expect(item?.content?.type).toBe("doc");
expect(item?.content?.children[0]?.children.map((node) => node.type)).toEqual(["text", "hashtag"]);
expect(item?.plainText).toBe("End to end #acampada");
expect(item?.media?.[0]?.proxyUrl).toBe(`${PROXY.proxyPath}?url=${encodeURIComponent("https://cdn.social/e2e.png")}`);
assertZeroRawRemnants(item?.content as RichTextDocument, item?.plainText as string, item?.media as MediaRef[]);
});
});