import type { Did } from "@atcute/lexicons/syntax"; import { TokenRefreshError, type Session } from "@atcute/oauth-browser-client"; import { beforeEach, describe, expect, it, vi } from "vitest"; import { orgDidsControlledBy } from "$lib/auth/accounts"; import { revokeAllOrgSessions, revokeOrgSessionsFor, type OrgSessionDependencies } from "$lib/auth/orgSessions"; const alice = "did:plc:alice" as Did; const bob = "did:plc:bob" as Did; const acme = "did:plc:acme" as Did; const globex = "did:plc:globex" as Did; const initech = "did:plc:initech" as Did; const controllers: Record = { [acme]: alice, [globex]: alice, [initech]: bob }; const signOut = vi.fn(async () => {}); const deps: OrgSessionDependencies = { loadOrgDids: vi.fn(() => Object.keys(controllers) as Did[]), loadOrgControllers: vi.fn(() => controllers), getSession: vi.fn(async (sub: Did) => ({ info: { sub } }) as unknown as Session), createAgent: vi.fn(() => ({ signOut })), deleteStoredSession: vi.fn(), forgetOrgDid: vi.fn() }; beforeEach(() => { vi.clearAllMocks(); }); describe("orgDidsControlledBy", () => { it("lists only the orgs minted from the given controller", () => { expect(orgDidsControlledBy(controllers, alice).sort()).toEqual([acme, globex].sort()); expect(orgDidsControlledBy(controllers, "did:plc:nobody" as Did)).toEqual([]); }); }); describe("revokeOrgSessionsFor", () => { it("revokes the controller's org sessions without a refresh and forgets them", async () => { await revokeOrgSessionsFor(alice, deps); expect(deps.getSession).toHaveBeenCalledWith(acme, { allowStale: true }); expect(deps.getSession).toHaveBeenCalledWith(globex, { allowStale: true }); expect(deps.getSession).not.toHaveBeenCalledWith(initech, expect.anything()); expect(signOut).toHaveBeenCalledTimes(2); expect(deps.forgetOrgDid).toHaveBeenCalledWith(acme); expect(deps.forgetOrgDid).toHaveBeenCalledWith(globex); expect(deps.forgetOrgDid).not.toHaveBeenCalledWith(initech); }); it("drops the local copy when there is nothing left to revoke", async () => { vi.mocked(deps.getSession).mockRejectedValueOnce( new TokenRefreshError(acme, "session deleted by another tab") ); await revokeOrgSessionsFor(alice, deps); expect(deps.deleteStoredSession).toHaveBeenCalledWith(acme); expect(deps.deleteStoredSession).not.toHaveBeenCalledWith(globex); expect(deps.forgetOrgDid).toHaveBeenCalledWith(acme); }); it("still forgets an org whose revocation request failed", async () => { signOut.mockRejectedValueOnce(new TypeError("fetch failed")); await revokeOrgSessionsFor(alice, deps); expect(deps.deleteStoredSession).toHaveBeenCalledTimes(1); expect(deps.forgetOrgDid).toHaveBeenCalledTimes(2); }); }); describe("revokeAllOrgSessions", () => { it("revokes every recorded org session and forgets each one", async () => { await revokeAllOrgSessions(deps); expect(signOut).toHaveBeenCalledTimes(3); expect(deps.forgetOrgDid).toHaveBeenCalledTimes(3); }); });