~james/core

core/web/src/lib/api/orgCreate.test.ts -rw-r--r-- 5.6 KiB
452f17b7 — james chore(import): snapshot james.dad.town/core at f70c71c7696a 15 hours ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
import { ClientResponseError } from "@atcute/client";
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 { provisionDelegate } from "#lib/api/delegation.ts";
import { createOrg, createOrgAccount, ORG_PROFILE_MARKER } from "#lib/api/orgCreate.ts";
import { ensureProfile } from "#lib/api/profile.ts";

vi.mock("#lib/api/delegation.ts", () => ({ provisionDelegate: vi.fn() }));
vi.mock("#lib/api/actAs.ts", () => ({ getOrCreateDelegatedSession: vi.fn() }));
vi.mock("#lib/api/profile.ts", () => ({
	PROFILE_COLLECTION: "org.tangled.actor.profile",
	ensureProfile: vi.fn()
}));

const service = { serviceUrl: "https://gate.example", handleDomain: "orgs.example" };
const controllerDid = "did:plc:controller" as Did;
const controller = { sub: controllerDid } as OAuthUserAgent;
const orgAgent = {} as OAuthUserAgent;
const orgDid = "did:plc:org" as Did;
const orgLabel = "acme";
const fullHandle = `${orgLabel}.orgs.example`;

beforeEach(() => {
	vi.resetAllMocks();
	vi.mocked(provisionDelegate).mockResolvedValue({
		did: orgDid,
		handle: fullHandle,
		controllerDid
	});
	vi.mocked(getOrCreateDelegatedSession).mockResolvedValue(orgAgent);
});

describe("createOrgAccount", () => {
	it("provisions through the sidecar", async () => {
		const org = await createOrgAccount(controller, " ACME ", service);

		expect(provisionDelegate).toHaveBeenCalledWith(controller, fullHandle, service);
		expect(org).toEqual({ did: orgDid, handle: fullHandle });
	});

	it("rejects a name that is not a valid subdomain label", async () => {
		await expect(createOrgAccount(controller, "-bad", service)).rejects.toThrow(/name/);
		expect(provisionDelegate).not.toHaveBeenCalled();
	});

	it("refuses to guess a handle domain the deployment does not set", async () => {
		await expect(
			createOrgAccount(controller, orgLabel, { serviceUrl: "https://gate.example" })
		).rejects.toThrow("Organization creation isn't set up on this server.");
		expect(provisionDelegate).not.toHaveBeenCalled();
	});

	it("rejects a name carrying its own domain", async () => {
		await expect(createOrgAccount(controller, "acme.owner.example", service)).rejects.toThrow(
			/name/
		);
		expect(provisionDelegate).not.toHaveBeenCalled();
	});
});

describe("createOrg", () => {
	it("creates the account, then writes the profile marker as the organization", async () => {
		const org = await createOrg(controller, orgLabel, service);

		expect(provisionDelegate).toHaveBeenCalledWith(controller, fullHandle, service);
		expect(getOrCreateDelegatedSession).toHaveBeenCalledWith(controller, orgDid);
		expect(ensureProfile).toHaveBeenCalledWith(orgAgent, ORG_PROFILE_MARKER);
		expect(ORG_PROFILE_MARKER).toEqual({
			$type: "org.tangled.actor.profile",
			isOrganization: true
		});
		expect(org).toEqual({ did: orgDid, handle: fullHandle, warnings: [] });
	});

	it("retries the organization session once before giving up", async () => {
		vi.mocked(getOrCreateDelegatedSession)
			.mockRejectedValueOnce(new Error("grant unavailable"))
			.mockResolvedValueOnce(orgAgent);

		const org = await createOrg(controller, orgLabel, service);

		expect(getOrCreateDelegatedSession).toHaveBeenCalledTimes(2);
		expect(ensureProfile).toHaveBeenCalledWith(orgAgent, ORG_PROFILE_MARKER);
		expect(org.warnings).toEqual([]);
	});

	it("keeps the account and names the failed session step in the warning", async () => {
		const cause = new Error("grant unavailable");
		vi.mocked(getOrCreateDelegatedSession).mockRejectedValue(cause);
		const log = vi.spyOn(console, "error").mockImplementation(() => {});

		const org = await createOrg(controller, orgLabel, service);

		expect(getOrCreateDelegatedSession).toHaveBeenCalledTimes(2);
		expect(org).toEqual({
			did: orgDid,
			handle: fullHandle,
			warnings: ["Unable to sign in to the organization account (grant unavailable)."]
		});
		expect(ensureProfile).not.toHaveBeenCalled();
		expect(log).toHaveBeenCalledWith(
			expect.stringContaining("organization session"),
			orgDid,
			cause
		);
	});

	it("names the profile write when that is what failed, without redoing the session", async () => {
		const cause = new Error("scope missing.");
		vi.mocked(ensureProfile).mockRejectedValue(cause);
		const log = vi.spyOn(console, "error").mockImplementation(() => {});

		const org = await createOrg(controller, orgLabel, service);

		expect(getOrCreateDelegatedSession).toHaveBeenCalledTimes(1);
		expect(org.warnings).toEqual([
			"Unable to save the organization's profile (scope missing)."
		]);
		expect(log).toHaveBeenCalledWith(expect.stringContaining("profile write"), orgDid, cause);
	});

	it("shows a PDS error's description rather than its code", async () => {
		vi.mocked(ensureProfile).mockRejectedValue(
			new ClientResponseError({
				status: 500,
				data: { error: "InternalServerError", message: "Repo is not ready yet" }
			})
		);
		vi.spyOn(console, "error").mockImplementation(() => {});

		const org = await createOrg(controller, orgLabel, service);

		expect(org.warnings).toEqual([
			"Unable to save the organization's profile (Repo is not ready yet)."
		]);
	});

	it("retries with the existing account without consuming another quota slot", async () => {
		await createOrg(controller, orgLabel, service, { did: orgDid, handle: fullHandle });

		expect(provisionDelegate).not.toHaveBeenCalled();
		expect(getOrCreateDelegatedSession).toHaveBeenCalledWith(controller, orgDid);
		expect(ensureProfile).toHaveBeenCalledWith(orgAgent, ORG_PROFILE_MARKER);
	});
});