import { describe, expect, it } from "vitest"; import { Identity, NostrSecret, newClaimPayload, signClaim, toCompact, toMarkdown, } from "@kez/core"; import { BlueskyChannel, authorFeedUrl, extractPostTexts, } from "../src/bluesky.js"; function sign(subject: string) { const secret = NostrSecret.generate(); return signClaim( newClaimPayload(Identity.parse(subject), secret.identity(), new Date()), secret, ); } function fakeFetch(url: string, body: unknown, status = 200): typeof fetch { return (async (input: string | URL) => { if (input.toString() === url) { return new Response(typeof body === "string" ? body : JSON.stringify(body), { status }); } return new Response("", { status: 404 }); }) as unknown as typeof fetch; } const APPVIEW = "https://appview.test"; const FEED_URL = `${APPVIEW}/xrpc/app.bsky.feed.getAuthorFeed?actor=jason.bsky.social&limit=100`; describe("BlueskyChannel", () => { it("verifies compact proof in post text", async () => { const signed = sign("bluesky:jason.bsky.social"); const fetch = fakeFetch(FEED_URL, { feed: [ { post: { record: { text: "good morning" } } }, { post: { record: { text: toCompact(signed) } } }, ], }); const channel = new BlueskyChannel({ appviewBase: APPVIEW, fetch }); const hit = await channel.fetchAndVerify(Identity.parse("bluesky:jason.bsky.social")); expect(hit.proof).toEqual(signed); }); it("verifies markdown-fenced proof in post", async () => { const signed = sign("bluesky:jason.bsky.social"); const fetch = fakeFetch(FEED_URL, { feed: [{ post: { record: { text: toMarkdown(signed) } } }], }); const channel = new BlueskyChannel({ appviewBase: APPVIEW, fetch }); const hit = await channel.fetchAndVerify(Identity.parse("bluesky:jason.bsky.social")); expect(hit.proof).toEqual(signed); }); it("rejects proof for wrong handle", async () => { const signed = sign("bluesky:mallory.bsky.social"); const fetch = fakeFetch(FEED_URL, { feed: [{ post: { record: { text: toCompact(signed) } } }], }); const channel = new BlueskyChannel({ appviewBase: APPVIEW, fetch }); await expect( channel.fetchAndVerify(Identity.parse("bluesky:jason.bsky.social")), ).rejects.toMatchObject({ kind: "SubjectMismatch" }); }); it("empty feed yields NotFound", async () => { const fetch = fakeFetch(FEED_URL, { feed: [] }); const channel = new BlueskyChannel({ appviewBase: APPVIEW, fetch }); await expect( channel.fetchAndVerify(Identity.parse("bluesky:jason.bsky.social")), ).rejects.toMatchObject({ kind: "NotFound" }); }); it("AppView 503 is Unreachable", async () => { const fetch = fakeFetch(FEED_URL, "boom", 503); const channel = new BlueskyChannel({ appviewBase: APPVIEW, fetch }); await expect( channel.fetchAndVerify(Identity.parse("bluesky:jason.bsky.social")), ).rejects.toMatchObject({ kind: "Unreachable" }); }); }); describe("bluesky pure helpers", () => { it("authorFeedUrl matches AppView contract", () => { expect(authorFeedUrl("https://public.api.bsky.app", "jason.bsky.social")).toBe( "https://public.api.bsky.app/xrpc/app.bsky.feed.getAuthorFeed?actor=jason.bsky.social&limit=100", ); }); it("extractPostTexts pulls text out of feed items", () => { const body = { feed: [ { post: { record: { text: "hello" } } }, { post: { record: { text: "kez:z1:abc" } } }, { post: { record: {} } }, { no_post: true }, ], }; expect(extractPostTexts(body)).toEqual(["hello", "kez:z1:abc"]); }); it("extractPostTexts handles missing feed", () => { expect(extractPostTexts({})).toEqual([]); expect(extractPostTexts({ feed: "no" })).toEqual([]); }); });