Something went wrong. Try again.
Monorepo for Tangled forked from tangled.org/core
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168import { describe, expect, it, vi } from "vitest";import type { BobbinContext } from "#lib/api/client.ts";import type { MiniDoc } from "#lib/api/identity.ts";import { mockHydrantDoc, mockLiarFetch } from "#lib/migration/mock.ts";import { readLiarRecords } from "#lib/migration/liar.ts";import { migrationPending, sameOrigin, waitForMigration } from "#lib/migration/status.ts";import type { Fetch } from "#lib/fetch.ts";
const liarPdsUrl = "https://liar.test";const did = "did:plc:alice";
const bobbinWith = (pds: () => string | undefined) => { const fetch = vi.fn<Fetch>( async () => new Response(JSON.stringify({ did, handle: "alice.test", pds: pds() }), { headers: { "content-type": "application/json" } }) ); return { fetch, ctx: { serviceUrl: "https://bobbin.test", fetch } as unknown as BobbinContext };};
describe("sameOrigin", () => { it("compares origins, so a trailing slash or a path doesn't matter", () => { expect(sameOrigin("https://liar.test/", liarPdsUrl)).toBe(true); expect(sameOrigin("https://liar.test/xrpc", liarPdsUrl)).toBe(true); expect(sameOrigin("https://pds.test", liarPdsUrl)).toBe(false); expect(sameOrigin(undefined, liarPdsUrl)).toBe(false); });
it("never matches when no liar pds is configured", () => { expect(sameOrigin("https://pds.test", "")).toBe(false); });});
describe("migrationPending", () => { it("is pending while the did doc names the liar pds", async () => { const bobbin = bobbinWith(() => liarPdsUrl); await expect( migrationPending(bobbin.ctx, did, { liarPdsUrl, liarMock: false }) ).resolves.toBe(true); });
it("is done once the did doc names the user's own pds", async () => { const bobbin = bobbinWith(() => "https://pds.test"); await expect( migrationPending(bobbin.ctx, did, { liarPdsUrl, liarMock: false }) ).resolves.toBe(false); });
it("skips the lookup entirely when the migration is off", async () => { const bobbin = bobbinWith(() => liarPdsUrl); await expect( migrationPending(bobbin.ctx, did, { liarPdsUrl: "", liarMock: false }) ).resolves.toBe(false); expect(bobbin.fetch).not.toHaveBeenCalled(); });});
describe("waitForMigration", () => { it("resolves once hydrant's doc flips", async () => { let lookups = 0; const bobbin = bobbinWith(() => (++lookups < 3 ? liarPdsUrl : "https://pds.test"));
await expect( waitForMigration(bobbin.ctx, did, { liarPdsUrl, liarMock: false }, { intervalMs: 1 }) ).resolves.toBe(true); expect(lookups).toBe(3); });
it("keeps polling through a failed lookup", async () => { let lookups = 0; const fetch = vi.fn<Fetch>(async () => { if (++lookups === 1) throw new Error("bobbin down"); return new Response( JSON.stringify({ did, handle: "alice.test", pds: "https://pds.test" }), { headers: { "content-type": "application/json" } } ); }); const ctx = { serviceUrl: "https://bobbin.test", fetch } as unknown as BobbinContext;
await expect( waitForMigration(ctx, did, { liarPdsUrl, liarMock: false }, { intervalMs: 1 }) ).resolves.toBe(true); });
it("stops sleeping when aborted, even before it starts", async () => { const bobbin = bobbinWith(() => liarPdsUrl); const controller = new AbortController(); controller.abort(new Error("stop"));
await expect( waitForMigration( bobbin.ctx, did, { liarPdsUrl, liarMock: false }, { intervalMs: 60_000, signal: controller.signal } ) ).rejects.toThrow("stop"); });
it("gives up at the deadline", async () => { const bobbin = bobbinWith(() => liarPdsUrl); await expect( waitForMigration( bobbin.ctx, did, { liarPdsUrl, liarMock: false }, { intervalMs: 1, timeoutMs: 20 } ) ).resolves.toBe(false); });});
describe("migrationPending with the mock hydrant", () => { it("stays pending until the user's pds has the mock records", async () => { let imported = false; const fetch = vi.fn<Fetch>(async (input) => { const url = new URL(String(input)); if (url.pathname.endsWith("resolveMiniDoc")) { return new Response( JSON.stringify({ did, handle: "alice.test", pds: "https://pds.test" }), { headers: { "content-type": "application/json" } } ); } return new Response("{}", { status: imported ? 200 : 400 }); }); const ctx = { serviceUrl: "https://bobbin.test", fetch } as unknown as BobbinContext; const config = { liarPdsUrl, liarMock: true };
await expect(migrationPending(ctx, did, config)).resolves.toBe(true); imported = true; await expect(migrationPending(ctx, did, config)).resolves.toBe(false); });});
describe("mock hydrant", () => { const realDoc = { did, handle: "alice.test", pds: "https://pds.test" } as unknown as MiniDoc; const pdsHolding = (imported: boolean) => vi.fn<Fetch>(async () => new Response("{}", { status: imported ? 200 : 400 }));
it("shows the liar pds until the user's pds holds the mock records", async () => { const before = pdsHolding(false); expect((await mockHydrantDoc(realDoc, liarPdsUrl, before)).pds).toBe(liarPdsUrl);
const after = pdsHolding(true); expect((await mockHydrantDoc(realDoc, liarPdsUrl, after)).pds).toBe("https://pds.test"); const asked = new URL(String(after.mock.calls[0]?.[0])); expect(asked.origin).toBe("https://pds.test"); expect(asked.searchParams.get("collection")).toBe("org.tangled.feed.star"); });
it("serves a repo the real reader can walk", async () => { const response = await mockLiarFetch( `${liarPdsUrl}/xrpc/com.atproto.sync.getRepo?did=${did}` ); const records = await readLiarRecords(response.body as ReadableStream<Uint8Array>);
expect(records.length).toBeGreaterThan(200); expect(new Set(records.map((record) => record.collection))).toContain( "org.tangled.actor.profile" ); });});