Something went wrong. Try again.
Monorepo for Tangled tangled.org
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194import type { Did } from "@atcute/lexicons/syntax";import type { OAuthUserAgent } from "@atcute/oauth-browser-client";import { beforeEach, describe, expect, it, vi } from "vitest";import { getOrCreateDelegatedSession } from "$lib/api/actAs";import { cached, invalidate } from "$lib/api/cache";import type { BobbinContext } from "$lib/api/client";import { listDelegatedAccounts } from "$lib/api/delegation";import { leaveOrganization, listUserOrganizations, viewerControlsOrganization} from "$lib/api/organizations";import { getProfiles } from "$lib/api/records";import { removeController } from "$lib/api/tranquil";import { forgetOrgDid } from "$lib/auth/accounts";
vi.mock("$lib/api/actAs", () => ({ getOrCreateDelegatedSession: vi.fn() }));vi.mock("$lib/api/delegation", () => ({ listDelegatedAccounts: vi.fn() }));vi.mock("$lib/api/tranquil", () => ({ removeController: vi.fn() }));vi.mock("$lib/auth/accounts", () => ({ forgetOrgDid: vi.fn() }));vi.mock("$lib/api/records", () => ({ getProfiles: vi.fn() }));vi.mock("$lib/api/cache", () => ({ cached: vi.fn((_key: string, _ttl: number, fn: () => Promise<unknown>) => fn()), invalidate: vi.fn()}));
const OWNER_SCOPES = "atproto repo:* blob:*/* rpc:* identity:* account:*?action=manage transition:generic";const EDITOR_SCOPES = "atproto repo:*?action=create blob:*/* rpc:*";
const controllerDid = "did:plc:controller" as Did;const controller = { sub: controllerDid } as OAuthUserAgent;const ctx = {} as BobbinContext;const tranquilDid = "did:web:tranquil.example";const tangledDid = "did:plc:tangled" as Did;const microcosmDid = "did:plc:microcosm" as Did;
const profileUri = (did: Did) => `at://${did}/sh.tangled.actor.profile/self`;
beforeEach(() => { vi.resetAllMocks(); vi.mocked(listDelegatedAccounts).mockResolvedValue([ { did: tangledDid, handle: "tangled.org", grantedAt: "2025-01-14T09:00:00.000Z", grantedScopes: OWNER_SCOPES }, { did: microcosmDid, handle: "microcosm.blue", grantedAt: "2025-03-02T09:00:00.000Z", grantedScopes: EDITOR_SCOPES } ]); vi.mocked(getProfiles).mockResolvedValue({ items: [ { uri: profileUri(tangledDid), value: { $type: "sh.tangled.actor.profile", bluesky: false, isOrganization: true } }, { uri: profileUri(microcosmDid), value: { $type: "sh.tangled.actor.profile", bluesky: false, isOrganization: true } } ] } as never);});
describe("listUserOrganizations", () => { it("lists the controlled accounts with the viewer's role", async () => { const organizations = await listUserOrganizations(controller, ctx, tranquilDid);
expect(listDelegatedAccounts).toHaveBeenCalledWith(controller, tranquilDid); expect(getProfiles).toHaveBeenCalledWith( ctx, [profileUri(tangledDid), profileUri(microcosmDid)], undefined ); expect(organizations).toEqual([ { did: tangledDid, handle: "tangled.org", role: "owner", joinedAt: "2025-01-14T09:00:00.000Z" }, { did: microcosmDid, handle: "microcosm.blue", role: "member", joinedAt: "2025-03-02T09:00:00.000Z" } ]); });
it("skips delegated accounts that are not organizations", async () => { vi.mocked(getProfiles).mockResolvedValue({ items: [ { uri: profileUri(microcosmDid), value: { $type: "sh.tangled.actor.profile", bluesky: false, isOrganization: false } } ] } as never);
const organizations = await listUserOrganizations(controller, ctx, tranquilDid);
expect(organizations.map((organization) => organization.did)).toEqual([tangledDid]); });
it("still lists the organizations when the appview is unreachable", async () => { vi.mocked(getProfiles).mockRejectedValue(new Error("bobbin is down"));
const organizations = await listUserOrganizations(controller, ctx, tranquilDid);
expect(organizations).toHaveLength(2); expect(organizations[0]).toMatchObject({ handle: "tangled.org", role: "owner" }); });
it("does not reach for profiles when nothing is controlled", async () => { vi.mocked(listDelegatedAccounts).mockResolvedValue([]);
await expect(listUserOrganizations(controller, ctx, tranquilDid)).resolves.toEqual([]); expect(getProfiles).not.toHaveBeenCalled(); });});
describe("viewerControlsOrganization", () => { it("is true only for organizations tranquil lists for the viewer", async () => { await expect(viewerControlsOrganization(controller, tranquilDid, tangledDid)).resolves.toBe( true ); await expect( viewerControlsOrganization(controller, tranquilDid, "did:plc:stranger") ).resolves.toBe(false); expect(listDelegatedAccounts).toHaveBeenCalledWith(controller, tranquilDid); });
it("shares one cached listing with the organizations list", async () => { await listUserOrganizations(controller, ctx, tranquilDid); await viewerControlsOrganization(controller, tranquilDid, tangledDid);
const keys = vi.mocked(cached).mock.calls.map(([key]) => key); expect(keys).toHaveLength(2); expect(new Set(keys).size).toBe(1); expect(keys[0]).toContain(controllerDid); expect(keys[0]).toContain(tranquilDid); });
it("surfaces a failed listing instead of denying quietly", async () => { vi.mocked(listDelegatedAccounts).mockRejectedValue(new Error("tranquil is down"));
await expect( viewerControlsOrganization(controller, tranquilDid, tangledDid) ).rejects.toThrow("tranquil is down"); });});
describe("leaveOrganization", () => { it("revokes the viewer's grant and forgets the organization", async () => { const signOut = vi.fn().mockResolvedValue(undefined); vi.mocked(getOrCreateDelegatedSession).mockResolvedValue({ signOut } as unknown as OAuthUserAgent); vi.mocked(removeController).mockResolvedValue({ success: true });
await leaveOrganization(controller, tangledDid, tranquilDid);
expect(getOrCreateDelegatedSession).toHaveBeenCalledWith(controller, tangledDid); expect(removeController).toHaveBeenCalledWith(expect.anything(), controllerDid); expect(signOut).toHaveBeenCalled(); expect(forgetOrgDid).toHaveBeenCalledWith(tangledDid); await listUserOrganizations(controller, ctx, tranquilDid); expect(invalidate).toHaveBeenCalledWith(vi.mocked(cached).mock.calls[0][0]); });
it("keeps the organization when the grant cannot be revoked", async () => { vi.mocked(getOrCreateDelegatedSession).mockResolvedValue({ signOut: vi.fn() } as unknown as OAuthUserAgent); vi.mocked(removeController).mockRejectedValue(new Error("Forbidden"));
await expect(leaveOrganization(controller, tangledDid, tranquilDid)).rejects.toThrow( "Forbidden" ); expect(forgetOrgDid).not.toHaveBeenCalled(); expect(invalidate).not.toHaveBeenCalled(); });});