Something went wrong. Try again.
Monorepo for Tangled forked from tangled.org/core
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184import { is } from "@atcute/lexicons/validations";import type { OAuthUserAgent } from "@atcute/oauth-browser-client";import { describe, expect, it, vi } from "vitest";import { createBobbinClient } from "#lib/api/client.ts";import { createEntitySubscription, ENTITY_SUBSCRIBED, ensureEntitySubscription, getEntitySubscription, listNetworkVouches, setEntitySubscribed} from "#lib/api/graph.ts";import { mainSchema as subscriptionSchema } from "#lib/api/lexicons/types/org/tangled/feed/subscription.ts";import { createRecord, putRecord } from "#lib/api/write.ts";
vi.mock("#lib/api/write.ts", () => ({ createRecord: vi.fn(async () => ({ uri: "at://did:plc:alice/org.tangled.feed.subscription/3l7abc", cid: "bafytest" })), deleteRecord: vi.fn(), putRecord: vi.fn(async () => ({ uri: "at://did:plc:alice/org.tangled.feed.subscription/3l7abc", cid: "bafytest" }))}));
const response = (items: unknown[] = []) => new Response(JSON.stringify({ items }), { status: 200, headers: { "content-type": "application/json" } });
describe("network vouches", () => { it("deduplicates actors and chunks requests at fifty", async () => { const fetch = vi.fn<typeof globalThis.fetch>(async () => response()); const ctx = createBobbinClient({ serviceUrl: "https://bobbin.test", fetch }); const actors = Array.from({ length: 55 }, (_, index) => `did:plc:actor${index}`); await listNetworkVouches(ctx, [...actors, actors[0]]); expect(fetch).toHaveBeenCalledTimes(2); expect(new URL(String(fetch.mock.calls[0][0])).searchParams.getAll("actors")).toEqual( actors.slice(0, 50) ); expect(new URL(String(fetch.mock.calls[1][0])).searchParams.getAll("actors")).toEqual( actors.slice(50) ); });
it("groups tagged items by requested actor", async () => { const alice = "did:plc:alice"; const bob = "did:plc:bob"; const item = (actor: string) => ({ actor, uri: `at://did:plc:viewer/org.tangled.graph.vouch/${actor}`, value: { $type: "org.tangled.graph.vouch", kind: "vouch", createdAt: "2026-01-01T00:00:00.000Z" } }); const ctx = createBobbinClient({ serviceUrl: "https://bobbin.test", fetch: async () => response([item(bob), item(alice), item(bob)]) }); const result = await listNetworkVouches(ctx, [alice, bob]); expect(result.items[alice]).toHaveLength(1); expect(result.items[bob]).toHaveLength(2); });});
describe("entity subscriptions", () => { it("writes a record the subscription lexicon accepts", async () => { const agent = { sub: "did:plc:alice" } as unknown as OAuthUserAgent; await createEntitySubscription(agent, "at://did:plc:bob/org.tangled.track.ticket/3l7xyz"); const [, , record] = vi.mocked(createRecord).mock.calls[0]; expect(is(subscriptionSchema, record)).toBe(true); });
const ticket = "at://did:plc:bob/org.tangled.track.ticket/3l7xyz"; const agent = { sub: "did:plc:alice" } as unknown as OAuthUserAgent; const bobbinWith = (found: { ignore?: boolean } | null) => createBobbinClient({ serviceUrl: "https://bobbin.test", fetch: async () => found ? Response.json({ uri: "at://did:plc:alice/org.tangled.feed.subscription/3lsub", ...found }) : new Response(null, { status: 404 }) });
it("reads the rkey and mute flag, and null when there is no record", async () => { expect(await getEntitySubscription(bobbinWith(null), "did:plc:alice", ticket)).toBeNull(); expect(await getEntitySubscription(bobbinWith({}), "did:plc:alice", ticket)).toEqual({ rkey: "3lsub", ignore: false }); expect( await getEntitySubscription(bobbinWith({ ignore: true }), "did:plc:alice", ticket) ).toEqual({ rkey: "3lsub", ignore: true }); });
it("mutes by putting ignore on the existing record, never deleting", async () => { await setEntitySubscribed( agent, bobbinWith({ ignore: false }), "did:plc:alice", ticket, false ); const [, , rkey, record] = vi.mocked(putRecord).mock.calls.at(-1)!; expect(rkey).toBe("3lsub"); expect(record).toMatchObject({ ignore: true }); expect(is(subscriptionSchema, record)).toBe(true); });
it("creates a mute when there is no record yet", async () => { vi.mocked(createRecord).mockClear(); await setEntitySubscribed(agent, bobbinWith(null), "did:plc:alice", ticket, false); const [, , record] = vi.mocked(createRecord).mock.calls[0]; expect(record).toMatchObject({ ignore: true }); });
it("unmutes by dropping ignore from the record", async () => { await setEntitySubscribed( agent, bobbinWith({ ignore: true }), "did:plc:alice", ticket, true ); const [, , rkey, record] = vi.mocked(putRecord).mock.calls.at(-1)!; expect(rkey).toBe("3lsub"); expect(record).not.toHaveProperty("ignore"); });
it("commenting unmutes a muted thread but leaves a live subscription alone", async () => { vi.mocked(putRecord).mockClear(); await ensureEntitySubscription( agent, bobbinWith({ ignore: false }), "did:plc:alice", ticket ); expect(putRecord).not.toHaveBeenCalled(); await ensureEntitySubscription( agent, bobbinWith({ ignore: true }), "did:plc:alice", ticket ); expect(vi.mocked(putRecord).mock.calls[0][3]).not.toHaveProperty("ignore"); });
it("tells the page when a comment unmutes the thread, and not when nothing changed", async () => { const target = new EventTarget(); const seen: unknown[] = []; target.addEventListener(ENTITY_SUBSCRIBED, (event) => seen.push((event as CustomEvent).detail) ); vi.stubGlobal("window", target); try { await ensureEntitySubscription( agent, bobbinWith({ ignore: true }), "did:plc:alice", ticket ); expect(seen).toEqual([{ entityUri: ticket, rkey: "3lsub" }]); seen.length = 0; await ensureEntitySubscription( agent, bobbinWith({ ignore: false }), "did:plc:alice", ticket ); expect(seen).toEqual([]); } finally { vi.unstubAllGlobals(); } });});