@hypercerts-org/sdk-core 0.4.0-beta.0 → 0.6.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/README.md +459 -79
  2. package/dist/index.cjs +128 -47
  3. package/dist/index.cjs.map +1 -1
  4. package/dist/index.d.ts +28 -9
  5. package/dist/index.mjs +128 -47
  6. package/dist/index.mjs.map +1 -1
  7. package/dist/types.cjs +3 -2
  8. package/dist/types.cjs.map +1 -1
  9. package/dist/types.d.ts +28 -9
  10. package/dist/types.mjs +3 -2
  11. package/dist/types.mjs.map +1 -1
  12. package/package.json +9 -5
  13. package/.turbo/turbo-build.log +0 -328
  14. package/.turbo/turbo-test.log +0 -118
  15. package/CHANGELOG.md +0 -22
  16. package/eslint.config.mjs +0 -22
  17. package/rollup.config.js +0 -75
  18. package/src/auth/OAuthClient.ts +0 -497
  19. package/src/core/SDK.ts +0 -410
  20. package/src/core/config.ts +0 -243
  21. package/src/core/errors.ts +0 -257
  22. package/src/core/interfaces.ts +0 -324
  23. package/src/core/types.ts +0 -281
  24. package/src/errors.ts +0 -57
  25. package/src/index.ts +0 -107
  26. package/src/lexicons.ts +0 -64
  27. package/src/repository/BlobOperationsImpl.ts +0 -199
  28. package/src/repository/CollaboratorOperationsImpl.ts +0 -396
  29. package/src/repository/HypercertOperationsImpl.ts +0 -1146
  30. package/src/repository/LexiconRegistry.ts +0 -332
  31. package/src/repository/OrganizationOperationsImpl.ts +0 -234
  32. package/src/repository/ProfileOperationsImpl.ts +0 -281
  33. package/src/repository/RecordOperationsImpl.ts +0 -340
  34. package/src/repository/Repository.ts +0 -482
  35. package/src/repository/interfaces.ts +0 -897
  36. package/src/repository/types.ts +0 -111
  37. package/src/services/hypercerts/types.ts +0 -87
  38. package/src/storage/InMemorySessionStore.ts +0 -127
  39. package/src/storage/InMemoryStateStore.ts +0 -146
  40. package/src/storage.ts +0 -63
  41. package/src/testing/index.ts +0 -67
  42. package/src/testing/mocks.ts +0 -142
  43. package/src/testing/stores.ts +0 -285
  44. package/src/testing.ts +0 -64
  45. package/src/types.ts +0 -86
  46. package/tests/auth/OAuthClient.test.ts +0 -164
  47. package/tests/core/SDK.test.ts +0 -176
  48. package/tests/core/errors.test.ts +0 -81
  49. package/tests/repository/BlobOperationsImpl.test.ts +0 -154
  50. package/tests/repository/CollaboratorOperationsImpl.test.ts +0 -438
  51. package/tests/repository/HypercertOperationsImpl.test.ts +0 -652
  52. package/tests/repository/LexiconRegistry.test.ts +0 -192
  53. package/tests/repository/OrganizationOperationsImpl.test.ts +0 -242
  54. package/tests/repository/ProfileOperationsImpl.test.ts +0 -254
  55. package/tests/repository/RecordOperationsImpl.test.ts +0 -375
  56. package/tests/repository/Repository.test.ts +0 -149
  57. package/tests/utils/fixtures.ts +0 -117
  58. package/tests/utils/mocks.ts +0 -109
  59. package/tests/utils/repository-fixtures.ts +0 -78
  60. package/tsconfig.json +0 -11
  61. package/tsconfig.tsbuildinfo +0 -1
  62. package/vitest.config.ts +0 -30
@@ -1,192 +0,0 @@
1
- import { beforeEach, describe, expect, it, vi } from "vitest";
2
- import { LexiconRegistry } from "../../src/repository/LexiconRegistry.js";
3
- import { ValidationError } from "../../src/core/errors.js";
4
- import { createMockLexicon } from "../utils/repository-fixtures.js";
5
- import type { Agent } from "@atproto/api";
6
- import type { LexiconDoc } from "@atproto/lexicon";
7
-
8
- describe("LexiconRegistry", () => {
9
- let registry: LexiconRegistry;
10
-
11
- beforeEach(() => {
12
- registry = new LexiconRegistry();
13
- });
14
-
15
- describe("register", () => {
16
- it("should register a lexicon", () => {
17
- const lexicon = createMockLexicon("com.example.test");
18
- expect(() => registry.register(lexicon as LexiconDoc)).not.toThrow();
19
- expect(registry.has("com.example.test")).toBe(true);
20
- });
21
-
22
- it("should throw ValidationError for lexicon without id", () => {
23
- const lexicon = { lexicon: 1 } as unknown as LexiconDoc;
24
- expect(() => registry.register(lexicon)).toThrow(ValidationError);
25
- expect(() => registry.register(lexicon)).toThrow("Lexicon must have an 'id' field");
26
- });
27
-
28
- it("should overwrite existing lexicon with same id", () => {
29
- const lexicon1 = createMockLexicon("com.example.test");
30
- const lexicon2 = {
31
- ...lexicon1,
32
- defs: {
33
- record: {
34
- type: "record" as const,
35
- record: {
36
- type: "object" as const,
37
- properties: {},
38
- },
39
- },
40
- },
41
- } as LexiconDoc;
42
-
43
- registry.register(lexicon1);
44
- // Overwriting lexicons is allowed (creates new Lexicons collection)
45
- expect(() => registry.register(lexicon2)).not.toThrow();
46
- expect(registry.get("com.example.test")).toEqual(lexicon2);
47
- });
48
- });
49
-
50
- describe("registerMany", () => {
51
- it("should register multiple lexicons", () => {
52
- const lexicons = [
53
- createMockLexicon("com.example.one"),
54
- createMockLexicon("com.example.two"),
55
- createMockLexicon("com.example.three"),
56
- ] as LexiconDoc[];
57
-
58
- registry.registerMany(lexicons);
59
-
60
- expect(registry.has("com.example.one")).toBe(true);
61
- expect(registry.has("com.example.two")).toBe(true);
62
- expect(registry.has("com.example.three")).toBe(true);
63
- });
64
-
65
- it("should throw ValidationError if any lexicon is invalid", () => {
66
- const lexicons = [createMockLexicon("com.example.one"), { lexicon: 1 } as unknown as LexiconDoc];
67
-
68
- expect(() => registry.registerMany(lexicons)).toThrow(ValidationError);
69
- });
70
- });
71
-
72
- describe("get", () => {
73
- it("should return registered lexicon", () => {
74
- const lexicon = createMockLexicon("com.example.test");
75
- registry.register(lexicon);
76
-
77
- const retrieved = registry.get("com.example.test");
78
- expect(retrieved).toEqual(lexicon);
79
- });
80
-
81
- it("should return undefined for unregistered lexicon", () => {
82
- expect(registry.get("com.example.nonexistent")).toBeUndefined();
83
- });
84
- });
85
-
86
- describe("validate", () => {
87
- it("should validate a valid record", () => {
88
- const lexicon = createMockLexicon("com.example.test");
89
- registry.register(lexicon);
90
-
91
- // The mock lexicon has a record def, so we need to validate against the record type
92
- // Note: Validation may fail if the schema doesn't match exactly
93
- const result = registry.validate("com.example.test", {
94
- $type: "com.example.test",
95
- text: "Hello",
96
- });
97
-
98
- // Validation result depends on lexicon schema - just check it doesn't throw
99
- expect(result).toHaveProperty("valid");
100
- // Error property is optional and only present when validation fails
101
- if (!result.valid) {
102
- expect(result).toHaveProperty("error");
103
- }
104
- });
105
-
106
- it("should return invalid result for invalid record", () => {
107
- const lexicon = createMockLexicon("com.example.test");
108
- registry.register(lexicon);
109
-
110
- const result = registry.validate("com.example.test", {
111
- $type: "com.example.test",
112
- invalidField: "value",
113
- });
114
-
115
- // Validation may pass or fail depending on lexicon schema
116
- // The important thing is that validate() doesn't throw
117
- expect(result).toHaveProperty("valid");
118
- });
119
-
120
- it("should return valid result for unknown collection (no lexicon registered)", () => {
121
- // When no lexicon is registered for a collection, validation passes
122
- // because we can't validate against unknown schemas
123
- const result = registry.validate("com.example.unknown", {
124
- $type: "com.example.unknown",
125
- });
126
-
127
- expect(result.valid).toBe(true);
128
- // No error when lexicon isn't registered
129
- });
130
- });
131
-
132
- describe("addToAgent", () => {
133
- it("should add lexicons to agent", () => {
134
- const lexicon = createMockLexicon("com.example.test");
135
- registry.register(lexicon);
136
-
137
- const mockAgent = {
138
- lex: {
139
- add: vi.fn(),
140
- },
141
- } as unknown as Agent;
142
-
143
- registry.addToAgent(mockAgent);
144
-
145
- expect(mockAgent.lex.add).toHaveBeenCalledWith(lexicon);
146
- });
147
-
148
- it("should add all registered lexicons to agent", () => {
149
- const lexicons = [createMockLexicon("com.example.one"), createMockLexicon("com.example.two")];
150
- registry.registerMany(lexicons);
151
-
152
- const mockAgent = {
153
- lex: {
154
- add: vi.fn(),
155
- },
156
- } as unknown as Agent;
157
-
158
- registry.addToAgent(mockAgent);
159
-
160
- expect(mockAgent.lex.add).toHaveBeenCalledTimes(2);
161
- expect(mockAgent.lex.add).toHaveBeenCalledWith(lexicons[0]);
162
- expect(mockAgent.lex.add).toHaveBeenCalledWith(lexicons[1]);
163
- });
164
- });
165
-
166
- describe("getRegisteredIds", () => {
167
- it("should return empty array for empty registry", () => {
168
- expect(registry.getRegisteredIds()).toEqual([]);
169
- });
170
-
171
- it("should return all registered lexicon IDs", () => {
172
- registry.register(createMockLexicon("com.example.one"));
173
- registry.register(createMockLexicon("com.example.two"));
174
-
175
- const ids = registry.getRegisteredIds();
176
- expect(ids).toContain("com.example.one");
177
- expect(ids).toContain("com.example.two");
178
- expect(ids.length).toBe(2);
179
- });
180
- });
181
-
182
- describe("has", () => {
183
- it("should return true for registered lexicon", () => {
184
- registry.register(createMockLexicon("com.example.test"));
185
- expect(registry.has("com.example.test")).toBe(true);
186
- });
187
-
188
- it("should return false for unregistered lexicon", () => {
189
- expect(registry.has("com.example.nonexistent")).toBe(false);
190
- });
191
- });
192
- });
@@ -1,242 +0,0 @@
1
- import { describe, it, expect, vi, beforeEach } from "vitest";
2
- import { OrganizationOperationsImpl } from "../../src/repository/OrganizationOperationsImpl.js";
3
- import { NetworkError } from "../../src/core/errors.js";
4
-
5
- describe("OrganizationOperationsImpl", () => {
6
- let mockSession: any;
7
- let orgOps: OrganizationOperationsImpl;
8
- const repoDid = "did:plc:testdid123";
9
- const serverUrl = "https://sds.example.com";
10
-
11
- beforeEach(() => {
12
- mockSession = {
13
- did: "did:plc:user123",
14
- sub: "did:plc:user123",
15
- fetchHandler: vi.fn(),
16
- };
17
-
18
- orgOps = new OrganizationOperationsImpl(mockSession, repoDid, serverUrl);
19
- });
20
-
21
- describe("create", () => {
22
- it("should create an organization successfully", async () => {
23
- mockSession.fetchHandler.mockResolvedValue({
24
- ok: true,
25
- json: async () => ({
26
- did: "did:plc:neworg123",
27
- handle: "myorg.example.com",
28
- name: "My Organization",
29
- description: "A test organization",
30
- createdAt: "2024-01-01T00:00:00Z",
31
- }),
32
- });
33
-
34
- const result = await orgOps.create({
35
- name: "My Organization",
36
- description: "A test organization",
37
- handle: "myorg",
38
- });
39
-
40
- expect(result.did).toBe("did:plc:neworg123");
41
- expect(result.handle).toBe("myorg.example.com");
42
- expect(result.name).toBe("My Organization");
43
- expect(result.accessType).toBe("owner");
44
- expect(result.permissions.owner).toBe(true);
45
-
46
- expect(mockSession.fetchHandler).toHaveBeenCalledWith(
47
- `${serverUrl}/xrpc/com.sds.organization.create`,
48
- expect.objectContaining({
49
- method: "POST",
50
- headers: { "Content-Type": "application/json" },
51
- }),
52
- );
53
- });
54
-
55
- it("should create organization with minimal params", async () => {
56
- mockSession.fetchHandler.mockResolvedValue({
57
- ok: true,
58
- json: async () => ({
59
- did: "did:plc:org",
60
- handle: "org.example.com",
61
- name: "Org",
62
- }),
63
- });
64
-
65
- const result = await orgOps.create({ name: "Org" });
66
-
67
- expect(result.name).toBe("Org");
68
- expect(result.createdAt).toBeDefined(); // Should default to current time
69
- });
70
-
71
- it("should throw NetworkError on failure", async () => {
72
- mockSession.fetchHandler.mockResolvedValue({
73
- ok: false,
74
- statusText: "Conflict",
75
- });
76
-
77
- await expect(
78
- orgOps.create({ name: "Test Org" }),
79
- ).rejects.toThrow(NetworkError);
80
- });
81
- });
82
-
83
- describe("get", () => {
84
- it("should get an organization by DID", async () => {
85
- mockSession.fetchHandler.mockResolvedValue({
86
- ok: true,
87
- json: async () => ({
88
- repositories: [
89
- {
90
- did: "did:plc:org1",
91
- handle: "org1.example.com",
92
- name: "Organization 1",
93
- description: "First org",
94
- accessType: "owner",
95
- permissions: { read: true, create: true, update: true, delete: true, admin: true, owner: true },
96
- },
97
- {
98
- did: "did:plc:org2",
99
- handle: "org2.example.com",
100
- name: "Organization 2",
101
- accessType: "collaborator",
102
- permissions: { read: true, create: true, update: true, delete: false, admin: false, owner: false },
103
- },
104
- ],
105
- }),
106
- });
107
-
108
- const result = await orgOps.get("did:plc:org1");
109
-
110
- expect(result).not.toBeNull();
111
- expect(result!.did).toBe("did:plc:org1");
112
- expect(result!.name).toBe("Organization 1");
113
- });
114
-
115
- it("should return null for non-existent organization", async () => {
116
- mockSession.fetchHandler.mockResolvedValue({
117
- ok: true,
118
- json: async () => ({
119
- repositories: [],
120
- }),
121
- });
122
-
123
- const result = await orgOps.get("did:plc:nonexistent");
124
-
125
- expect(result).toBeNull();
126
- });
127
-
128
- it("should return null on error", async () => {
129
- mockSession.fetchHandler.mockRejectedValue(new Error("Network error"));
130
-
131
- const result = await orgOps.get("did:plc:org");
132
-
133
- expect(result).toBeNull();
134
- });
135
- });
136
-
137
- describe("list", () => {
138
- it("should list all accessible organizations", async () => {
139
- mockSession.fetchHandler.mockResolvedValue({
140
- ok: true,
141
- json: async () => ({
142
- repositories: [
143
- {
144
- did: "did:plc:org1",
145
- handle: "org1.example.com",
146
- name: "Organization 1",
147
- accessType: "owner",
148
- permissions: { read: true, create: true, update: true, delete: true, admin: true, owner: true },
149
- },
150
- {
151
- did: "did:plc:org2",
152
- handle: "org2.example.com",
153
- name: "Organization 2",
154
- description: "Second org",
155
- accessType: "collaborator",
156
- permissions: { read: true, create: true, update: false, delete: false, admin: false, owner: false },
157
- },
158
- ],
159
- }),
160
- });
161
-
162
- const result = await orgOps.list();
163
-
164
- expect(result).toHaveLength(2);
165
- expect(result[0].did).toBe("did:plc:org1");
166
- expect(result[0].accessType).toBe("owner");
167
- expect(result[1].did).toBe("did:plc:org2");
168
- expect(result[1].accessType).toBe("collaborator");
169
- });
170
-
171
- it("should handle empty repositories list", async () => {
172
- mockSession.fetchHandler.mockResolvedValue({
173
- ok: true,
174
- json: async () => ({ repositories: [] }),
175
- });
176
-
177
- const result = await orgOps.list();
178
-
179
- expect(result).toHaveLength(0);
180
- });
181
-
182
- it("should use session DID in query", async () => {
183
- mockSession.fetchHandler.mockResolvedValue({
184
- ok: true,
185
- json: async () => ({ repositories: [] }),
186
- });
187
-
188
- await orgOps.list();
189
-
190
- expect(mockSession.fetchHandler).toHaveBeenCalledWith(
191
- expect.stringContaining(`userDid=${encodeURIComponent(mockSession.did)}`),
192
- expect.any(Object),
193
- );
194
- });
195
-
196
- it("should use session.sub if did is not available", async () => {
197
- mockSession.did = undefined;
198
- mockSession.fetchHandler.mockResolvedValue({
199
- ok: true,
200
- json: async () => ({ repositories: [] }),
201
- });
202
-
203
- await orgOps.list();
204
-
205
- expect(mockSession.fetchHandler).toHaveBeenCalledWith(
206
- expect.stringContaining(`userDid=${encodeURIComponent(mockSession.sub)}`),
207
- expect.any(Object),
208
- );
209
- });
210
-
211
- it("should throw NetworkError on failure", async () => {
212
- mockSession.fetchHandler.mockResolvedValue({
213
- ok: false,
214
- statusText: "Internal Server Error",
215
- });
216
-
217
- await expect(orgOps.list()).rejects.toThrow(NetworkError);
218
- });
219
-
220
- it("should add createdAt for organizations without it", async () => {
221
- mockSession.fetchHandler.mockResolvedValue({
222
- ok: true,
223
- json: async () => ({
224
- repositories: [
225
- {
226
- did: "did:plc:org",
227
- handle: "org.example.com",
228
- name: "Org",
229
- accessType: "owner",
230
- permissions: { read: true, create: true, update: true, delete: true, admin: true, owner: true },
231
- // No createdAt field
232
- },
233
- ],
234
- }),
235
- });
236
-
237
- const result = await orgOps.list();
238
-
239
- expect(result[0].createdAt).toBeDefined();
240
- });
241
- });
242
- });
@@ -1,254 +0,0 @@
1
- import { describe, it, expect, vi, beforeEach } from "vitest";
2
- import { ProfileOperationsImpl } from "../../src/repository/ProfileOperationsImpl.js";
3
- import { NetworkError } from "../../src/core/errors.js";
4
-
5
- describe("ProfileOperationsImpl", () => {
6
- let mockAgent: any;
7
- let profileOps: ProfileOperationsImpl;
8
- const repoDid = "did:plc:testdid123";
9
- const serverUrl = "https://pds.example.com";
10
-
11
- beforeEach(() => {
12
- mockAgent = {
13
- getProfile: vi.fn(),
14
- com: {
15
- atproto: {
16
- repo: {
17
- getRecord: vi.fn(),
18
- putRecord: vi.fn(),
19
- uploadBlob: vi.fn(),
20
- },
21
- },
22
- },
23
- };
24
-
25
- profileOps = new ProfileOperationsImpl(mockAgent, repoDid, serverUrl);
26
- });
27
-
28
- describe("get", () => {
29
- it("should get profile successfully", async () => {
30
- mockAgent.getProfile.mockResolvedValue({
31
- success: true,
32
- data: {
33
- handle: "test.bsky.social",
34
- displayName: "Test User",
35
- description: "A test user",
36
- avatar: "https://example.com/avatar.jpg",
37
- banner: "https://example.com/banner.jpg",
38
- },
39
- });
40
-
41
- const result = await profileOps.get();
42
-
43
- expect(result.handle).toBe("test.bsky.social");
44
- expect(result.displayName).toBe("Test User");
45
- expect(result.description).toBe("A test user");
46
- expect(result.avatar).toBe("https://example.com/avatar.jpg");
47
- expect(result.banner).toBe("https://example.com/banner.jpg");
48
- expect(mockAgent.getProfile).toHaveBeenCalledWith({ actor: repoDid });
49
- });
50
-
51
- it("should handle profile without optional fields", async () => {
52
- mockAgent.getProfile.mockResolvedValue({
53
- success: true,
54
- data: {
55
- handle: "minimal.bsky.social",
56
- },
57
- });
58
-
59
- const result = await profileOps.get();
60
-
61
- expect(result.handle).toBe("minimal.bsky.social");
62
- expect(result.displayName).toBeUndefined();
63
- expect(result.description).toBeUndefined();
64
- });
65
-
66
- it("should throw NetworkError when API returns success: false", async () => {
67
- mockAgent.getProfile.mockResolvedValue({
68
- success: false,
69
- });
70
-
71
- await expect(profileOps.get()).rejects.toThrow(NetworkError);
72
- });
73
-
74
- it("should throw NetworkError when API throws", async () => {
75
- mockAgent.getProfile.mockRejectedValue(new Error("Profile not found"));
76
-
77
- await expect(profileOps.get()).rejects.toThrow(NetworkError);
78
- });
79
- });
80
-
81
- describe("update", () => {
82
- beforeEach(() => {
83
- // Default mock for getting existing profile
84
- mockAgent.com.atproto.repo.getRecord.mockResolvedValue({
85
- success: true,
86
- data: {
87
- value: {
88
- displayName: "Old Name",
89
- description: "Old description",
90
- },
91
- },
92
- });
93
-
94
- mockAgent.com.atproto.repo.putRecord.mockResolvedValue({
95
- success: true,
96
- data: {
97
- uri: "at://did:plc:test/app.bsky.actor.profile/self",
98
- cid: "bafyrei123",
99
- },
100
- });
101
- });
102
-
103
- it("should update displayName", async () => {
104
- const result = await profileOps.update({
105
- displayName: "New Name",
106
- });
107
-
108
- expect(result.uri).toBe("at://did:plc:test/app.bsky.actor.profile/self");
109
- expect(mockAgent.com.atproto.repo.putRecord).toHaveBeenCalledWith(
110
- expect.objectContaining({
111
- record: expect.objectContaining({
112
- displayName: "New Name",
113
- description: "Old description",
114
- }),
115
- }),
116
- );
117
- });
118
-
119
- it("should update description", async () => {
120
- await profileOps.update({
121
- description: "New description",
122
- });
123
-
124
- expect(mockAgent.com.atproto.repo.putRecord).toHaveBeenCalledWith(
125
- expect.objectContaining({
126
- record: expect.objectContaining({
127
- displayName: "Old Name",
128
- description: "New description",
129
- }),
130
- }),
131
- );
132
- });
133
-
134
- it("should remove displayName when set to null", async () => {
135
- await profileOps.update({
136
- displayName: null,
137
- });
138
-
139
- const putCall = mockAgent.com.atproto.repo.putRecord.mock.calls[0][0];
140
- expect(putCall.record.displayName).toBeUndefined();
141
- });
142
-
143
- it("should remove description when set to null", async () => {
144
- await profileOps.update({
145
- description: null,
146
- });
147
-
148
- const putCall = mockAgent.com.atproto.repo.putRecord.mock.calls[0][0];
149
- expect(putCall.record.description).toBeUndefined();
150
- });
151
-
152
- it("should upload and set avatar", async () => {
153
- const avatarBlob = new Blob(["avatar data"], { type: "image/jpeg" });
154
- mockAgent.com.atproto.repo.uploadBlob.mockResolvedValue({
155
- success: true,
156
- data: {
157
- blob: { ref: { $link: "avatar-cid" }, mimeType: "image/jpeg", size: 100 },
158
- },
159
- });
160
-
161
- await profileOps.update({
162
- avatar: avatarBlob,
163
- });
164
-
165
- expect(mockAgent.com.atproto.repo.uploadBlob).toHaveBeenCalled();
166
- expect(mockAgent.com.atproto.repo.putRecord).toHaveBeenCalledWith(
167
- expect.objectContaining({
168
- record: expect.objectContaining({
169
- avatar: expect.objectContaining({ ref: { $link: "avatar-cid" } }),
170
- }),
171
- }),
172
- );
173
- });
174
-
175
- it("should remove avatar when set to null", async () => {
176
- mockAgent.com.atproto.repo.getRecord.mockResolvedValue({
177
- success: true,
178
- data: {
179
- value: {
180
- displayName: "Name",
181
- avatar: { ref: { $link: "old-avatar" } },
182
- },
183
- },
184
- });
185
-
186
- await profileOps.update({
187
- avatar: null,
188
- });
189
-
190
- const putCall = mockAgent.com.atproto.repo.putRecord.mock.calls[0][0];
191
- expect(putCall.record.avatar).toBeUndefined();
192
- });
193
-
194
- it("should upload and set banner", async () => {
195
- const bannerBlob = new Blob(["banner data"], { type: "image/jpeg" });
196
- mockAgent.com.atproto.repo.uploadBlob.mockResolvedValue({
197
- success: true,
198
- data: {
199
- blob: { ref: { $link: "banner-cid" }, mimeType: "image/jpeg", size: 200 },
200
- },
201
- });
202
-
203
- await profileOps.update({
204
- banner: bannerBlob,
205
- });
206
-
207
- expect(mockAgent.com.atproto.repo.uploadBlob).toHaveBeenCalled();
208
- expect(mockAgent.com.atproto.repo.putRecord).toHaveBeenCalledWith(
209
- expect.objectContaining({
210
- record: expect.objectContaining({
211
- banner: expect.objectContaining({ ref: { $link: "banner-cid" } }),
212
- }),
213
- }),
214
- );
215
- });
216
-
217
- it("should remove banner when set to null", async () => {
218
- mockAgent.com.atproto.repo.getRecord.mockResolvedValue({
219
- success: true,
220
- data: {
221
- value: {
222
- displayName: "Name",
223
- banner: { ref: { $link: "old-banner" } },
224
- },
225
- },
226
- });
227
-
228
- await profileOps.update({
229
- banner: null,
230
- });
231
-
232
- const putCall = mockAgent.com.atproto.repo.putRecord.mock.calls[0][0];
233
- expect(putCall.record.banner).toBeUndefined();
234
- });
235
-
236
- it("should throw NetworkError when putRecord returns success: false", async () => {
237
- mockAgent.com.atproto.repo.putRecord.mockResolvedValue({
238
- success: false,
239
- });
240
-
241
- await expect(
242
- profileOps.update({ displayName: "New Name" }),
243
- ).rejects.toThrow(NetworkError);
244
- });
245
-
246
- it("should throw NetworkError when API throws", async () => {
247
- mockAgent.com.atproto.repo.putRecord.mockRejectedValue(new Error("Update failed"));
248
-
249
- await expect(
250
- profileOps.update({ displayName: "New Name" }),
251
- ).rejects.toThrow(NetworkError);
252
- });
253
- });
254
- });