~ knotless Search/
↑↓ move↵ open⇧↵ all results

grain.social/grain

photography-focused photo sharing platform

grain.social
1 collaborator
0
2
145 lines · 5.6 KB Raw History
// 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<string, string>) {
  const calls: Array<{ nsid: string; params?: Record<string, string> }> = [];
  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([]);
  });
});