Something went wrong. Try again.
Monorepo for Tangled tangled.org
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220import { describe, expect, it, vi } from "vitest";import type { OAuthUserAgent } from "@atcute/oauth-browser-client";import type { BobbinContext } from "$lib/api/client";
const agent = { sub: "did:plc:alice" } as unknown as OAuthUserAgent;
const load = async () => { vi.resetModules(); const writes: Record<string, unknown>[] = []; const waits: Record<string, unknown>[] = []; vi.doMock("$app/environment", () => ({ browser: true })); vi.doMock("$lib/auth/agent", () => ({ createClient: () => ({ call: async (_schema: unknown, options: { input: Record<string, unknown> }) => { writes.push(options.input); return { ok: true, data: { uri: "at://did:plc:alice/sh.tangled.string/3l7abc", cid: "bafytest" } }; } }) })); const strings = await import("./strings"); const graph = await import("./graph"); const records = await import("./records"); const bobbin = { xrpc: { call: async (_schema: unknown, options: { params: Record<string, unknown> }) => { waits.push(options.params); return { ok: true, data: options.params.cid ? { status: "indexed", cid: options.params.cid } : { status: "deleted" } }; } } } as unknown as BobbinContext; return { strings, graph, records, writes, waits, bobbin };};
describe("strings api", () => { it("puts a string record into sh.tangled.string collection with optional swapRecord", async () => { const { strings, writes, waits, bobbin } = await load(); await strings.putString( agent, bobbin, "3l7abc", { $type: "sh.tangled.string", filename: "main.rs", description: "test snippet", contents: "fn main() {}", createdAt: "2026-05-01T00:00:00Z" }, "bafyswap" );
expect(writes).toHaveLength(1); expect(writes[0]).toMatchObject({ repo: "did:plc:alice", collection: "sh.tangled.string", rkey: "3l7abc", swapRecord: "bafyswap", record: { $type: "sh.tangled.string", filename: "main.rs", contents: "fn main() {}" } }); expect(waits).toEqual([ { uri: "at://did:plc:alice/sh.tangled.string/3l7abc", cid: "bafytest" } ]); });
it("surfaces a write the appview did not index", async () => { const { strings, bobbin } = await load(); (bobbin as unknown as { xrpc: { call: unknown } }).xrpc.call = async () => ({ ok: true, data: { status: "rejected", detail: "unknown collection" } });
await expect( strings.putString(agent, bobbin, "3l7abc", { $type: "sh.tangled.string", filename: "main.rs", description: "", contents: "fn main() {}", createdAt: "2026-05-01T00:00:00Z" }) ).rejects.toThrow("unknown collection"); });
it("deletes a string record from sh.tangled.string collection with optional swapRecord", async () => { const { strings, writes, waits, bobbin } = await load(); await strings.deleteString( agent, bobbin, "at://did:plc:alice/sh.tangled.string/3l7abc", "bafyswap" );
expect(writes).toHaveLength(1); expect(writes[0]).toMatchObject({ repo: "did:plc:alice", collection: "sh.tangled.string", rkey: "3l7abc", swapRecord: "bafyswap" }); expect(waits).toEqual([{ uri: "at://did:plc:alice/sh.tangled.string/3l7abc" }]); });
it("reports an unconfirmed delete instead of failing the delete", async () => { vi.useFakeTimers(); try { const { strings, bobbin } = await load(); (bobbin as unknown as { xrpc: { call: unknown } }).xrpc.call = async () => ({ ok: true, data: { status: "indexed", cid: "bafytest" } });
const deleted = strings.deleteString( agent, bobbin, "at://did:plc:alice/sh.tangled.string/3l7abc" ); await vi.advanceTimersByTimeAsync(6000); await expect(deleted).resolves.toBe(false); } finally { vi.useRealTimers(); } });
it("confirms a delete once the remembered write stops answering", async () => { const { strings, bobbin } = await load(); let asks = 0; (bobbin as unknown as { xrpc: { call: unknown } }).xrpc.call = async () => { asks += 1; return asks === 1 ? { ok: true, data: { status: "indexed", cid: "bafytest" } } : { ok: true, data: { status: "deleted" } }; };
await expect( strings.deleteString(agent, bobbin, "at://did:plc:alice/sh.tangled.string/3l7abc") ).resolves.toBe(true); expect(asks).toBe(2); });
it("stops asking after the budget when bobbin keeps answering with the write", async () => { const { bobbin } = await load(); let asks = 0; (bobbin as unknown as { xrpc: { call: unknown } }).xrpc.call = async () => { asks += 1; return { ok: true, data: { status: "indexed", cid: "bafytest" } }; };
const { awaitDeletedRecord } = await import("./awaitRecord"); await expect( awaitDeletedRecord(bobbin, "at://did:plc:alice/sh.tangled.string/3l7abc", { budgetMs: 5, pollMs: 1 }) ).resolves.toBe(false); expect(asks).toBeGreaterThan(1); });
it("creates a star on a string AT-URI", async () => { const { graph, writes } = await load(); const { rkey } = await graph.createStar(agent, { kind: "string", uri: "at://did:plc:bob/sh.tangled.string/3l7xyz" });
expect(writes).toHaveLength(1); expect(writes[0]).toMatchObject({ repo: "did:plc:alice", collection: "sh.tangled.feed.star", record: { $type: "sh.tangled.feed.star", subject: { $type: "sh.tangled.feed.star#string", uri: "at://did:plc:bob/sh.tangled.string/3l7xyz" } } }); expect(rkey).toBe("3l7abc"); });
it("fetches a string via getString", async () => { const { records } = await load(); const mockFetch = vi.fn().mockResolvedValue({ ok: true, json: async () => ({ uri: "at://did:plc:alice/sh.tangled.string/3l7abc", cid: "bafytest", value: { $type: "sh.tangled.string", filename: "main.rs", description: "test snippet", contents: "fn main() {}", createdAt: "2026-05-01T00:00:00Z" } }) }); const ctx: BobbinContext = { serviceUrl: "https://bobbin.tangled.local", fetch: mockFetch, xrpc: {} as unknown as BobbinContext["xrpc"] };
const result = await records.getString(ctx, "at://did:plc:alice/sh.tangled.string/3l7abc"); expect(mockFetch).toHaveBeenCalledTimes(1); const url = new URL(mockFetch.mock.calls[0][0]); expect(url.pathname).toBe("/xrpc/sh.tangled.string.getString"); expect(url.searchParams.get("uri")).toBe("at://did:plc:alice/sh.tangled.string/3l7abc"); expect(result.value.filename).toBe("main.rs"); });});