Something went wrong. Try again.
Monorepo for Tangled tangled.org
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166import { describe, expect, it, vi } from "vitest";import { ClientResponseError } from "$lib/api/client";import { classifyRepoAvailability, withAuthorHandles, withImportAvailability, type RepoAvailability} from "$lib/api/repoIndex";import type { ImportState } from "$lib/api/repo";import type { CommitSummary } from "$lib/components/repo/types";
const unsupported = () => new ClientResponseError({ status: 404, data: { error: "XRPCNotSupported" } });
describe("classifyRepoAvailability", () => { it("recognizes a legacy knot that does not expose repository endpoints", () => { const result = classifyRepoAvailability( [unsupported(), unsupported(), unsupported()].map((cause) => ({ value: null, error: cause })) ); expect(result).toBe("needs-upgrade"); });
it("keeps transport failures as unreachable", () => { const result = classifyRepoAvailability( ["tree", "log", "branches"].map(() => ({ value: null, error: new TypeError("offline") })) ); expect(result).toBe("unreachable"); });
it("does not mistake a missing ref for an unavailable knot", () => { const result = classifyRepoAvailability([ { value: null, error: unsupported() }, { value: null, error: unsupported() }, { value: { branches: [] }, error: null } ]); expect(result).toBe("ok"); });});
describe("withImportAvailability", () => { it.each([ ["ok", { content: "fetching" as const }, "ok"], ["ok", { content: "partial" as const }, "ok"], ["ok", { content: "failed" as const, reason: "err" }, "ok"], ["needs-upgrade", { content: "fetching" as const }, "needs-upgrade"], ["needs-upgrade", { content: "partial" as const }, "needs-upgrade"], ["needs-upgrade", { content: "failed" as const }, "needs-upgrade"], ["empty", null, "empty"], ["unreachable", null, "unreachable"], ["empty", { content: "failed" as const, reason: "clone error" }, "import-failed"], ["unreachable", { content: "failed" as const, reason: "timeout" }, "import-failed"], ["empty", { content: "fetching" as const }, "importing"], ["empty", { content: "partial" as const }, "importing"], ["unreachable", { content: "fetching" as const }, "importing"], ["unreachable", { content: "partial" as const }, "importing"] ] satisfies [RepoAvailability, ImportState | null, RepoAvailability][])( "maps (%s, %j) -> %s", (availability, status, expected) => { expect(withImportAvailability(availability, status)).toBe(expected); } );});
const commit = (c: Partial<CommitSummary>): CommitSummary => ({ hash: "h", shortHash: "h", subject: "s", body: "", authorName: "", authorEmail: "", when: "2026-01-01T00:00:00Z", ...c});
// dispatches the two upstreams withAuthorHandles touches: the internal kv// route (relative) and bobbin's resolveMiniDoc.const fetchMock = vi.fn(async (input: string | URL, _init?: RequestInit) => { const url = String(input); if (url.startsWith("/_internal/email-did")) { return new Response( JSON.stringify({ "alice@example.com": "did:plc:alice", "bob@example.com": "did:plc:bob", "MIXED@Example.com": "did:plc:mixed", "ghost@example.com": null }) ); } if (url.includes("blue.microcosm.identity.resolveMiniDoc")) { const did = new URL(url).searchParams.get("identifier") ?? ""; return new Response(JSON.stringify({ did, handle: `${did.replace("did:plc:", "")}.test` })); } return new Response("unexpected", { status: 404 });});
describe("withAuthorHandles", () => { it("resolves verified emails to a did before hydrating handles", async () => { const commits = [ commit({ authorName: "Alice", authorEmail: "alice@example.com" }), commit({ authorName: "Signed", authorEmail: "did:plc:signed", authorDid: "did:plc:signed" }) ]; const out = await withAuthorHandles( commits, "https://bobbin.test", fetchMock as typeof globalThis.fetch ); expect(out[0]).toMatchObject({ authorDid: "did:plc:alice", authorHandle: "alice.test" }); expect(out[1]).toMatchObject({ authorDid: "did:plc:signed", authorHandle: "signed.test" }); });
it("keeps the requested email case when looking up the kv result", async () => { const out = await withAuthorHandles( [commit({ authorEmail: "MIXED@Example.com" })], "https://bobbin.test", fetchMock as typeof globalThis.fetch ); expect(out[0]).toMatchObject({ authorDid: "did:plc:mixed", authorHandle: "mixed.test" }); });
it("leaves unresolved emails as plain authors", async () => { const out = await withAuthorHandles( [commit({ authorEmail: "ghost@example.com" })], "https://bobbin.test", fetchMock as typeof globalThis.fetch ); expect(out[0].authorDid).toBeUndefined(); expect(out[0].authorHandle).toBeUndefined(); });
it("fails open when the kv route errors", async () => { const broken = vi.fn(async () => new Response("nope", { status: 500 })); const out = await withAuthorHandles( [commit({ authorEmail: "alice@example.com" })], "https://bobbin.test", broken as typeof globalThis.fetch ); expect(out[0].authorDid).toBeUndefined(); });
it("keeps a did from the email pass when handle resolution fails", async () => { const brokenBobbin = vi.fn(async (input: string | URL) => { if (String(input).startsWith("/_internal/email-did")) { return new Response(JSON.stringify({ "alice@example.com": "did:plc:alice" })); } return new Response("nope", { status: 500 }); }); const out = await withAuthorHandles( [commit({ authorEmail: "alice@example.com" })], "https://bobbin.test", brokenBobbin as typeof globalThis.fetch ); expect(out[0]).toMatchObject({ authorDid: "did:plc:alice" }); expect(out[0].authorHandle).toBeUndefined(); });});