import 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.ts";
import { awaitIndexedOrSaved } from "#lib/api/awaitRecord.ts";
import { createBobbinClient } from "#lib/api/client.ts";
import {
organizationHandleAgent,
saveOrganizationAvatar,
saveOrganizationProfile
} from "#lib/api/orgProfile.ts";
import { putProfile, uploadProfileAvatar } from "#lib/api/profile.ts";
vi.mock("#lib/api/actAs.ts", () => ({ getOrCreateDelegatedSession: vi.fn() }));
vi.mock("#lib/api/profile.ts", () => ({
PROFILE_COLLECTION: "org.tangled.actor.profile",
putProfile: vi.fn(),
uploadProfileAvatar: vi.fn()
}));
vi.mock("#lib/api/awaitRecord.ts", () => ({ awaitIndexedOrSaved: vi.fn() }));
const orgDid = "did:plc:org" as Did;
const controller = { sub: "did:plc:controller" } as unknown as OAuthUserAgent;
const orgAgent = { sub: orgDid } as unknown as OAuthUserAgent;
const bobbin = createBobbinClient({ serviceUrl: "https://bobbin.test" });
const written = { uri: `at://${orgDid}/org.tangled.actor.profile/self`, cid: "bafynew" };
beforeEach(() => {
vi.resetAllMocks();
vi.mocked(getOrCreateDelegatedSession).mockResolvedValue(orgAgent);
vi.mocked(putProfile).mockResolvedValue(written as never);
});
describe("saveOrganizationProfile", () => {
it("writes through a delegated session and keeps the organization flag", async () => {
const result = await saveOrganizationProfile(bobbin, controller, orgDid, {
$type: "org.tangled.actor.profile",
description: "Updated."
});
expect(getOrCreateDelegatedSession).toHaveBeenCalledWith(controller, orgDid);
expect(putProfile).toHaveBeenCalledWith(orgAgent, {
$type: "org.tangled.actor.profile",
description: "Updated.",
isOrganization: true
});
expect(awaitIndexedOrSaved).toHaveBeenCalledWith(bobbin, written);
expect(result).toEqual(expect.objectContaining({ isOrganization: true }));
});
});
describe("organizationHandleAgent", () => {
const withScope = (sub: string, scope: string) =>
({ sub, session: { token: { scope } }, signOut: vi.fn() }) as unknown as OAuthUserAgent;
const granted = "atproto identity:handle";
it("uses the stored organization session when it can change the handle", async () => {
const stored = withScope(orgDid, "atproto identity:*");
vi.mocked(getOrCreateDelegatedSession).mockResolvedValue(stored);
await expect(
organizationHandleAgent(withScope("did:plc:controller", granted), orgDid)
).resolves.toBe(stored);
expect(stored.signOut).not.toHaveBeenCalled();
});
it("replaces an organization session from before the owner allowed handle changes", async () => {
const stale = withScope(orgDid, "atproto repo:*");
const fresh = withScope(orgDid, "atproto identity:*");
vi.mocked(getOrCreateDelegatedSession)
.mockResolvedValueOnce(stale)
.mockResolvedValueOnce(fresh);
vi.mocked(stale.signOut).mockResolvedValue();
await expect(
organizationHandleAgent(withScope("did:plc:controller", granted), orgDid)
).resolves.toBe(fresh);
expect(stale.signOut).toHaveBeenCalled();
});
it("keeps the organization session while the owner's own session can't change handles", async () => {
const stale = withScope(orgDid, "atproto repo:*");
vi.mocked(getOrCreateDelegatedSession).mockResolvedValue(stale);
await expect(
organizationHandleAgent(withScope("did:plc:controller", "atproto repo:*"), orgDid)
).resolves.toBe(stale);
expect(stale.signOut).not.toHaveBeenCalled();
});
});
describe("saveOrganizationAvatar", () => {
it("uploads with the organization's session, starting a record if there is none", async () => {
const image = new Blob(["png"], { type: "image/png" });
const avatar = {
$type: "blob",
ref: { $link: "bafyreidfayvfuwqa7qlnopdjiqrxzs6blmoeu4rujcjtnci5beludirz2a" },
mimeType: "image/png",
size: 3
} as const;
vi.mocked(uploadProfileAvatar).mockResolvedValue(avatar);
const result = await saveOrganizationAvatar(bobbin, controller, orgDid, image, null);
expect(uploadProfileAvatar).toHaveBeenCalledWith(orgAgent, image);
expect(putProfile).toHaveBeenCalledWith(orgAgent, {
$type: "org.tangled.actor.profile",
isOrganization: true,
avatar
});
expect(result).toEqual(expect.objectContaining({ avatar, isOrganization: true }));
});
});