// Two helpers that answer questions the index alone cannot. // // `resolveActor` turns a handle into a DID, asking the index first and the // viewer's own PDS second — the second step is the point of it, since an // account on a PDS that serves permissioned spaces is exactly the kind grain // may never have indexed. // // `country.ts` normalises whatever a third-party client put in // `address.country`, so that clicking "Greece" in the sidebar finds records // stored as "GR". import { afterAll, beforeAll, describe, expect, test } from "vitest"; import { startTestServer } from "@hatk/hatk/test"; import { resolveActor } from "../server/helpers/resolveActor.ts"; import { expandCountryAliases, normalizeCountry } from "../server/helpers/country.ts"; const ALICE = "did:plc:alice"; const REMOTE = "did:plc:remote"; let server: any; /** Stands in for the viewer's PDS. Records what it was asked. */ function pdsThatKnows(handles: Record) { const calls: Array<{ nsid: string; params?: Record }> = []; const pds = async (nsid: string, options?: any) => { calls.push({ nsid, params: options?.params }); const did = handles[options?.params?.handle]; if (!did) throw new Error("Unable to resolve handle"); return { did }; }; return { pds, calls }; } beforeAll(async () => { server = await startTestServer(); await server.db.run(`INSERT INTO _repos (did, status, handle) VALUES ($1, 'active', $2)`, [ ALICE, "alice.test", ]); }); afterAll(async () => await server?.close()); describe("resolveActor", () => { test("passes a did straight through without asking anyone", async () => { const { pds, calls } = pdsThatKnows({}); expect(await resolveActor(server.db, pds, ALICE)).toBe(ALICE); expect(calls).toEqual([]); }); test("answers from the index when grain already knows the handle", async () => { const { pds, calls } = pdsThatKnows({ "alice.test": "did:plc:wrong" }); expect(await resolveActor(server.db, pds, "alice.test")).toBe(ALICE); // The index answered, so the PDS was never troubled. expect(calls).toEqual([]); }); test("asks the viewer's PDS for a handle grain has never seen", async () => { // The case the helper exists for: an account on a permissioned-spaces PDS // that has never reached grain's index. const { pds, calls } = pdsThatKnows({ "remote.test": REMOTE }); expect(await resolveActor(server.db, pds, "remote.test")).toBe(REMOTE); expect(calls).toEqual([ { nsid: "com.atproto.identity.resolveHandle", params: { handle: "remote.test" } }, ]); }); test("strips a leading @ and surrounding whitespace", async () => { const { pds } = pdsThatKnows({ "remote.test": REMOTE }); expect(await resolveActor(server.db, pds, " @alice.test ")).toBe(ALICE); expect(await resolveActor(server.db, pds, "@remote.test")).toBe(REMOTE); }); test("gives up quietly when the PDS cannot resolve it either", async () => { const { pds } = pdsThatKnows({}); expect(await resolveActor(server.db, pds, "nobody.test")).toBeNull(); }); test("gives up when the PDS answers without a did", async () => { const pds = async () => ({ notADid: true }); expect(await resolveActor(server.db, pds as any, "nobody.test")).toBeNull(); }); test("returns null for an empty actor", async () => { const { pds, calls } = pdsThatKnows({}); expect(await resolveActor(server.db, pds, "")).toBeNull(); expect(await resolveActor(server.db, pds, " ")).toBeNull(); expect(await resolveActor(server.db, pds, "@")).toBeNull(); expect(calls).toEqual([]); }); }); describe("normalizeCountry", () => { test("maps an English name to its ISO-2 code", () => { expect(normalizeCountry("Greece")).toBe("GR"); expect(normalizeCountry("United States")).toBe("US"); }); test("maps a known spelling variant", () => { // "USA" is the one non-canonical value actually observed in the index. expect(normalizeCountry("USA")).toBe("US"); }); test("ignores case and surrounding whitespace", () => { expect(normalizeCountry(" greece ")).toBe("GR"); expect(normalizeCountry("uSa")).toBe("US"); }); test("passes an unrecognised string through upper-cased", () => { // Third-party clients can write anything; an unknown value is kept rather // than dropped, so it can still match other records holding the same text. expect(normalizeCountry("Waldport")).toBe("WALDPORT"); }); test("returns null only for nothing at all", () => { expect(normalizeCountry("")).toBeNull(); expect(normalizeCountry(" ")).toBeNull(); expect(normalizeCountry(null)).toBeNull(); expect(normalizeCountry(undefined)).toBeNull(); }); }); describe("expandCountryAliases", () => { test("offers the code, its aliases and the English name together", () => { // A sidebar click says "Greece"; the records say "GR". expect(new Set(expandCountryAliases("Greece"))).toEqual(new Set(["GR", "GREECE"])); }); test("includes a known variant alongside the canonical code", () => { expect(new Set(expandCountryAliases("US"))).toEqual(new Set(["US", "USA", "UNITED STATES"])); // Reached from either spelling. expect(new Set(expandCountryAliases("USA"))).toEqual(new Set(expandCountryAliases("US"))); }); test("offers an unrecognised string as itself", () => { expect(expandCountryAliases("Waldport")).toEqual(["WALDPORT"]); }); test("offers nothing for an empty string", () => { // The caller drops an interpretation that expands to nothing rather than // emitting `IN ()`, which SQLite will not parse. expect(expandCountryAliases("")).toEqual([]); expect(expandCountryAliases(" ")).toEqual([]); }); });