@hypercerts-org/sdk-core 0.2.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.
- package/.turbo/turbo-build.log +328 -0
- package/.turbo/turbo-test.log +118 -0
- package/CHANGELOG.md +16 -0
- package/LICENSE +21 -0
- package/README.md +100 -0
- package/dist/errors.cjs +260 -0
- package/dist/errors.cjs.map +1 -0
- package/dist/errors.d.ts +233 -0
- package/dist/errors.mjs +253 -0
- package/dist/errors.mjs.map +1 -0
- package/dist/index.cjs +4531 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +3430 -0
- package/dist/index.mjs +4448 -0
- package/dist/index.mjs.map +1 -0
- package/dist/lexicons.cjs +420 -0
- package/dist/lexicons.cjs.map +1 -0
- package/dist/lexicons.d.ts +227 -0
- package/dist/lexicons.mjs +410 -0
- package/dist/lexicons.mjs.map +1 -0
- package/dist/storage.cjs +270 -0
- package/dist/storage.cjs.map +1 -0
- package/dist/storage.d.ts +474 -0
- package/dist/storage.mjs +267 -0
- package/dist/storage.mjs.map +1 -0
- package/dist/testing.cjs +415 -0
- package/dist/testing.cjs.map +1 -0
- package/dist/testing.d.ts +928 -0
- package/dist/testing.mjs +410 -0
- package/dist/testing.mjs.map +1 -0
- package/dist/types.cjs +220 -0
- package/dist/types.cjs.map +1 -0
- package/dist/types.d.ts +2118 -0
- package/dist/types.mjs +212 -0
- package/dist/types.mjs.map +1 -0
- package/eslint.config.mjs +22 -0
- package/package.json +90 -0
- package/rollup.config.js +75 -0
- package/src/auth/OAuthClient.ts +497 -0
- package/src/core/SDK.ts +410 -0
- package/src/core/config.ts +243 -0
- package/src/core/errors.ts +257 -0
- package/src/core/interfaces.ts +324 -0
- package/src/core/types.ts +281 -0
- package/src/errors.ts +57 -0
- package/src/index.ts +107 -0
- package/src/lexicons.ts +64 -0
- package/src/repository/BlobOperationsImpl.ts +199 -0
- package/src/repository/CollaboratorOperationsImpl.ts +288 -0
- package/src/repository/HypercertOperationsImpl.ts +1146 -0
- package/src/repository/LexiconRegistry.ts +332 -0
- package/src/repository/OrganizationOperationsImpl.ts +234 -0
- package/src/repository/ProfileOperationsImpl.ts +281 -0
- package/src/repository/RecordOperationsImpl.ts +340 -0
- package/src/repository/Repository.ts +482 -0
- package/src/repository/interfaces.ts +868 -0
- package/src/repository/types.ts +111 -0
- package/src/services/hypercerts/types.ts +87 -0
- package/src/storage/InMemorySessionStore.ts +127 -0
- package/src/storage/InMemoryStateStore.ts +146 -0
- package/src/storage.ts +63 -0
- package/src/testing/index.ts +67 -0
- package/src/testing/mocks.ts +142 -0
- package/src/testing/stores.ts +285 -0
- package/src/testing.ts +64 -0
- package/src/types.ts +86 -0
- package/tests/auth/OAuthClient.test.ts +164 -0
- package/tests/core/SDK.test.ts +176 -0
- package/tests/core/errors.test.ts +81 -0
- package/tests/repository/BlobOperationsImpl.test.ts +154 -0
- package/tests/repository/CollaboratorOperationsImpl.test.ts +323 -0
- package/tests/repository/HypercertOperationsImpl.test.ts +652 -0
- package/tests/repository/LexiconRegistry.test.ts +192 -0
- package/tests/repository/OrganizationOperationsImpl.test.ts +242 -0
- package/tests/repository/ProfileOperationsImpl.test.ts +254 -0
- package/tests/repository/RecordOperationsImpl.test.ts +375 -0
- package/tests/repository/Repository.test.ts +149 -0
- package/tests/utils/fixtures.ts +117 -0
- package/tests/utils/mocks.ts +109 -0
- package/tests/utils/repository-fixtures.ts +78 -0
- package/tsconfig.json +11 -0
- package/tsconfig.tsbuildinfo +1 -0
- package/vitest.config.ts +30 -0
|
@@ -0,0 +1,652 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
2
|
+
import { HypercertOperationsImpl } from "../../src/repository/HypercertOperationsImpl.js";
|
|
3
|
+
import { LexiconRegistry } from "../../src/repository/LexiconRegistry.js";
|
|
4
|
+
import { NetworkError, ValidationError } from "../../src/core/errors.js";
|
|
5
|
+
import { HYPERCERT_LEXICONS } from "@hypercerts-org/lexicon";
|
|
6
|
+
|
|
7
|
+
describe("HypercertOperationsImpl", () => {
|
|
8
|
+
let mockAgent: any;
|
|
9
|
+
let lexiconRegistry: LexiconRegistry;
|
|
10
|
+
let hypercertOps: HypercertOperationsImpl;
|
|
11
|
+
const repoDid = "did:plc:testdid123";
|
|
12
|
+
const serverUrl = "https://pds.example.com";
|
|
13
|
+
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
mockAgent = {
|
|
16
|
+
com: {
|
|
17
|
+
atproto: {
|
|
18
|
+
repo: {
|
|
19
|
+
createRecord: vi.fn(),
|
|
20
|
+
putRecord: vi.fn(),
|
|
21
|
+
getRecord: vi.fn(),
|
|
22
|
+
listRecords: vi.fn(),
|
|
23
|
+
deleteRecord: vi.fn(),
|
|
24
|
+
uploadBlob: vi.fn(),
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
lexiconRegistry = new LexiconRegistry();
|
|
31
|
+
lexiconRegistry.registerMany(HYPERCERT_LEXICONS);
|
|
32
|
+
// Mock validate to always return valid - we test LexiconRegistry separately
|
|
33
|
+
vi.spyOn(lexiconRegistry, "validate").mockReturnValue({ valid: true });
|
|
34
|
+
hypercertOps = new HypercertOperationsImpl(mockAgent, repoDid, serverUrl, lexiconRegistry);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
describe("create", () => {
|
|
38
|
+
const validParams = {
|
|
39
|
+
title: "Test Hypercert",
|
|
40
|
+
description: "A test hypercert for unit testing",
|
|
41
|
+
workScope: "Testing",
|
|
42
|
+
workTimeframeFrom: "2024-01-01T00:00:00Z",
|
|
43
|
+
workTimeframeTo: "2024-12-31T23:59:59Z",
|
|
44
|
+
rights: {
|
|
45
|
+
name: "Attribution",
|
|
46
|
+
type: "CC-BY-4.0",
|
|
47
|
+
description: "Creative Commons Attribution",
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
beforeEach(() => {
|
|
52
|
+
// Mock successful rights creation
|
|
53
|
+
mockAgent.com.atproto.repo.createRecord.mockResolvedValueOnce({
|
|
54
|
+
success: true,
|
|
55
|
+
data: { uri: "at://did:plc:test/org.hypercerts.claim.rights/abc123", cid: "rights-cid" },
|
|
56
|
+
});
|
|
57
|
+
// Mock successful hypercert creation
|
|
58
|
+
mockAgent.com.atproto.repo.createRecord.mockResolvedValueOnce({
|
|
59
|
+
success: true,
|
|
60
|
+
data: { uri: "at://did:plc:test/org.hypercerts.claim.record/def456", cid: "hypercert-cid" },
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("should create a hypercert with rights successfully", async () => {
|
|
65
|
+
const result = await hypercertOps.create(validParams);
|
|
66
|
+
|
|
67
|
+
expect(result.rightsUri).toBe("at://did:plc:test/org.hypercerts.claim.rights/abc123");
|
|
68
|
+
expect(result.rightsCid).toBe("rights-cid");
|
|
69
|
+
expect(result.hypercertUri).toBe("at://did:plc:test/org.hypercerts.claim.record/def456");
|
|
70
|
+
expect(result.hypercertCid).toBe("hypercert-cid");
|
|
71
|
+
expect(mockAgent.com.atproto.repo.createRecord).toHaveBeenCalledTimes(2);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("should upload image and include in hypercert", async () => {
|
|
75
|
+
const imageBlob = new Blob(["image data"], { type: "image/png" });
|
|
76
|
+
mockAgent.com.atproto.repo.uploadBlob.mockResolvedValue({
|
|
77
|
+
success: true,
|
|
78
|
+
data: {
|
|
79
|
+
blob: { ref: { $link: "image-cid" }, mimeType: "image/png", size: 100 },
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
await hypercertOps.create({
|
|
84
|
+
...validParams,
|
|
85
|
+
image: imageBlob,
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
expect(mockAgent.com.atproto.repo.uploadBlob).toHaveBeenCalled();
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it("should include shortDescription when provided", async () => {
|
|
92
|
+
await hypercertOps.create({
|
|
93
|
+
...validParams,
|
|
94
|
+
shortDescription: "Short desc",
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
const hypercertCall = mockAgent.com.atproto.repo.createRecord.mock.calls[1][0];
|
|
98
|
+
expect(hypercertCall.record.shortDescription).toBe("Short desc");
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it("should include evidence when provided", async () => {
|
|
102
|
+
const evidence = [{ uri: "https://example.com/evidence", title: "Evidence" }];
|
|
103
|
+
|
|
104
|
+
await hypercertOps.create({
|
|
105
|
+
...validParams,
|
|
106
|
+
evidence,
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
const hypercertCall = mockAgent.com.atproto.repo.createRecord.mock.calls[1][0];
|
|
110
|
+
expect(hypercertCall.record.evidence).toEqual(evidence);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it("should attach location when provided", async () => {
|
|
114
|
+
// Reset mocks and set up for location
|
|
115
|
+
mockAgent.com.atproto.repo.createRecord.mockReset();
|
|
116
|
+
mockAgent.com.atproto.repo.createRecord
|
|
117
|
+
.mockResolvedValueOnce({
|
|
118
|
+
success: true,
|
|
119
|
+
data: { uri: "at://did:plc:test/org.hypercerts.claim.rights/abc", cid: "rights-cid" },
|
|
120
|
+
})
|
|
121
|
+
.mockResolvedValueOnce({
|
|
122
|
+
success: true,
|
|
123
|
+
data: { uri: "at://did:plc:test/org.hypercerts.claim.record/def", cid: "hypercert-cid" },
|
|
124
|
+
})
|
|
125
|
+
.mockResolvedValueOnce({
|
|
126
|
+
success: true,
|
|
127
|
+
data: { uri: "at://did:plc:test/org.hypercerts.claim.location/ghi", cid: "location-cid" },
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
// Mock getRecord for attachLocation's internal get call
|
|
131
|
+
mockAgent.com.atproto.repo.getRecord.mockResolvedValue({
|
|
132
|
+
success: true,
|
|
133
|
+
data: {
|
|
134
|
+
uri: "at://did:plc:test/org.hypercerts.claim.record/def",
|
|
135
|
+
cid: "hypercert-cid",
|
|
136
|
+
value: {
|
|
137
|
+
title: "Test",
|
|
138
|
+
description: "Test",
|
|
139
|
+
workScope: "Test",
|
|
140
|
+
workTimeframeFrom: "2024-01-01",
|
|
141
|
+
workTimeframeTo: "2024-12-31",
|
|
142
|
+
createdAt: "2024-01-01",
|
|
143
|
+
},
|
|
144
|
+
},
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
const result = await hypercertOps.create({
|
|
148
|
+
...validParams,
|
|
149
|
+
location: { value: "New York, NY" },
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
expect(result.locationUri).toBe("at://did:plc:test/org.hypercerts.claim.location/ghi");
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it("should create contributions when provided", async () => {
|
|
156
|
+
mockAgent.com.atproto.repo.createRecord.mockReset();
|
|
157
|
+
mockAgent.com.atproto.repo.createRecord
|
|
158
|
+
.mockResolvedValueOnce({
|
|
159
|
+
success: true,
|
|
160
|
+
data: { uri: "at://did:plc:test/org.hypercerts.claim.rights/abc", cid: "rights-cid" },
|
|
161
|
+
})
|
|
162
|
+
.mockResolvedValueOnce({
|
|
163
|
+
success: true,
|
|
164
|
+
data: { uri: "at://did:plc:test/org.hypercerts.claim.record/def", cid: "hypercert-cid" },
|
|
165
|
+
})
|
|
166
|
+
.mockResolvedValueOnce({
|
|
167
|
+
success: true,
|
|
168
|
+
data: { uri: "at://did:plc:test/org.hypercerts.claim.contribution/contrib1", cid: "contrib-cid" },
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
mockAgent.com.atproto.repo.getRecord.mockResolvedValue({
|
|
172
|
+
success: true,
|
|
173
|
+
data: {
|
|
174
|
+
uri: "at://did:plc:test/org.hypercerts.claim.record/def",
|
|
175
|
+
cid: "hypercert-cid",
|
|
176
|
+
value: {
|
|
177
|
+
title: "Test",
|
|
178
|
+
description: "Test",
|
|
179
|
+
workScope: "Test",
|
|
180
|
+
workTimeframeFrom: "2024-01-01",
|
|
181
|
+
workTimeframeTo: "2024-12-31",
|
|
182
|
+
createdAt: "2024-01-01",
|
|
183
|
+
},
|
|
184
|
+
},
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
const result = await hypercertOps.create({
|
|
188
|
+
...validParams,
|
|
189
|
+
contributions: [{ contributors: ["did:plc:contrib1"], role: "Developer" }],
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
expect(result.contributionUris).toHaveLength(1);
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it("should call onProgress callback", async () => {
|
|
196
|
+
const onProgress = vi.fn();
|
|
197
|
+
|
|
198
|
+
await hypercertOps.create({
|
|
199
|
+
...validParams,
|
|
200
|
+
onProgress,
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
expect(onProgress).toHaveBeenCalled();
|
|
204
|
+
const calls = onProgress.mock.calls.map((c: any[]) => c[0].name);
|
|
205
|
+
expect(calls).toContain("createRights");
|
|
206
|
+
expect(calls).toContain("createHypercert");
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
it("should emit events", async () => {
|
|
210
|
+
const rightsCreatedHandler = vi.fn();
|
|
211
|
+
const recordCreatedHandler = vi.fn();
|
|
212
|
+
|
|
213
|
+
hypercertOps.on("rightsCreated", rightsCreatedHandler);
|
|
214
|
+
hypercertOps.on("recordCreated", recordCreatedHandler);
|
|
215
|
+
|
|
216
|
+
await hypercertOps.create(validParams);
|
|
217
|
+
|
|
218
|
+
expect(rightsCreatedHandler).toHaveBeenCalledWith({ uri: expect.any(String), cid: "rights-cid" });
|
|
219
|
+
expect(recordCreatedHandler).toHaveBeenCalledWith({ uri: expect.any(String), cid: "hypercert-cid" });
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
it("should throw NetworkError when rights creation fails", async () => {
|
|
223
|
+
mockAgent.com.atproto.repo.createRecord.mockReset();
|
|
224
|
+
mockAgent.com.atproto.repo.createRecord.mockResolvedValue({
|
|
225
|
+
success: false,
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
await expect(hypercertOps.create(validParams)).rejects.toThrow(NetworkError);
|
|
229
|
+
});
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
describe("get", () => {
|
|
233
|
+
it("should get a hypercert successfully", async () => {
|
|
234
|
+
const mockRecord = {
|
|
235
|
+
title: "Test",
|
|
236
|
+
description: "Test description",
|
|
237
|
+
workScope: "Testing",
|
|
238
|
+
workTimeframeFrom: "2024-01-01",
|
|
239
|
+
workTimeframeTo: "2024-12-31",
|
|
240
|
+
createdAt: "2024-01-01T00:00:00Z",
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
mockAgent.com.atproto.repo.getRecord.mockResolvedValue({
|
|
244
|
+
success: true,
|
|
245
|
+
data: {
|
|
246
|
+
uri: "at://did:plc:test/org.hypercerts.claim.record/abc123",
|
|
247
|
+
cid: "test-cid",
|
|
248
|
+
value: mockRecord,
|
|
249
|
+
},
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
const result = await hypercertOps.get("at://did:plc:test/org.hypercerts.claim.record/abc123");
|
|
253
|
+
|
|
254
|
+
expect(result.uri).toBe("at://did:plc:test/org.hypercerts.claim.record/abc123");
|
|
255
|
+
expect(result.cid).toBe("test-cid");
|
|
256
|
+
expect(result.record.title).toBe("Test");
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
it("should throw ValidationError for invalid URI format", async () => {
|
|
260
|
+
await expect(hypercertOps.get("invalid-uri")).rejects.toThrow(ValidationError);
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
it("should throw NetworkError when API returns success: false", async () => {
|
|
264
|
+
mockAgent.com.atproto.repo.getRecord.mockResolvedValue({
|
|
265
|
+
success: false,
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
await expect(hypercertOps.get("at://did:plc:test/org.hypercerts.claim.record/abc123")).rejects.toThrow(
|
|
269
|
+
NetworkError,
|
|
270
|
+
);
|
|
271
|
+
});
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
describe("list", () => {
|
|
275
|
+
it("should list hypercerts successfully", async () => {
|
|
276
|
+
mockAgent.com.atproto.repo.listRecords.mockResolvedValue({
|
|
277
|
+
success: true,
|
|
278
|
+
data: {
|
|
279
|
+
records: [
|
|
280
|
+
{
|
|
281
|
+
uri: "at://did:plc:test/org.hypercerts.claim.record/1",
|
|
282
|
+
cid: "cid1",
|
|
283
|
+
value: {
|
|
284
|
+
title: "First",
|
|
285
|
+
description: "Desc",
|
|
286
|
+
workScope: "Scope",
|
|
287
|
+
workTimeframeFrom: "2024-01-01",
|
|
288
|
+
workTimeframeTo: "2024-12-31",
|
|
289
|
+
createdAt: "2024-01-01",
|
|
290
|
+
},
|
|
291
|
+
},
|
|
292
|
+
{
|
|
293
|
+
uri: "at://did:plc:test/org.hypercerts.claim.record/2",
|
|
294
|
+
cid: "cid2",
|
|
295
|
+
value: {
|
|
296
|
+
title: "Second",
|
|
297
|
+
description: "Desc",
|
|
298
|
+
workScope: "Scope",
|
|
299
|
+
workTimeframeFrom: "2024-01-01",
|
|
300
|
+
workTimeframeTo: "2024-12-31",
|
|
301
|
+
createdAt: "2024-01-01",
|
|
302
|
+
},
|
|
303
|
+
},
|
|
304
|
+
],
|
|
305
|
+
cursor: "next",
|
|
306
|
+
},
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
const result = await hypercertOps.list({ limit: 10 });
|
|
310
|
+
|
|
311
|
+
expect(result.records).toHaveLength(2);
|
|
312
|
+
expect(result.cursor).toBe("next");
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
it("should handle empty results", async () => {
|
|
316
|
+
mockAgent.com.atproto.repo.listRecords.mockResolvedValue({
|
|
317
|
+
success: true,
|
|
318
|
+
data: { records: [], cursor: undefined },
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
const result = await hypercertOps.list();
|
|
322
|
+
|
|
323
|
+
expect(result.records).toHaveLength(0);
|
|
324
|
+
expect(result.cursor).toBeUndefined();
|
|
325
|
+
});
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
describe("update", () => {
|
|
329
|
+
beforeEach(() => {
|
|
330
|
+
mockAgent.com.atproto.repo.getRecord.mockResolvedValue({
|
|
331
|
+
success: true,
|
|
332
|
+
data: {
|
|
333
|
+
uri: "at://did:plc:test/org.hypercerts.claim.record/abc123",
|
|
334
|
+
cid: "old-cid",
|
|
335
|
+
value: {
|
|
336
|
+
title: "Old Title",
|
|
337
|
+
description: "Old description",
|
|
338
|
+
workScope: "Old scope",
|
|
339
|
+
workTimeframeFrom: "2024-01-01",
|
|
340
|
+
workTimeframeTo: "2024-12-31",
|
|
341
|
+
createdAt: "2024-01-01T00:00:00Z",
|
|
342
|
+
rights: { uri: "at://rights", cid: "rights-cid" },
|
|
343
|
+
},
|
|
344
|
+
},
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
mockAgent.com.atproto.repo.putRecord.mockResolvedValue({
|
|
348
|
+
success: true,
|
|
349
|
+
data: { uri: "at://did:plc:test/org.hypercerts.claim.record/abc123", cid: "new-cid" },
|
|
350
|
+
});
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
it("should update a hypercert successfully", async () => {
|
|
354
|
+
const result = await hypercertOps.update({
|
|
355
|
+
uri: "at://did:plc:test/org.hypercerts.claim.record/abc123",
|
|
356
|
+
updates: { title: "New Title" },
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
expect(result.cid).toBe("new-cid");
|
|
360
|
+
expect(mockAgent.com.atproto.repo.putRecord).toHaveBeenCalledWith(
|
|
361
|
+
expect.objectContaining({
|
|
362
|
+
record: expect.objectContaining({
|
|
363
|
+
title: "New Title",
|
|
364
|
+
description: "Old description", // Preserved
|
|
365
|
+
}),
|
|
366
|
+
}),
|
|
367
|
+
);
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
it("should preserve createdAt and rights", async () => {
|
|
371
|
+
await hypercertOps.update({
|
|
372
|
+
uri: "at://did:plc:test/org.hypercerts.claim.record/abc123",
|
|
373
|
+
updates: { title: "New Title" },
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
const putCall = mockAgent.com.atproto.repo.putRecord.mock.calls[0][0];
|
|
377
|
+
expect(putCall.record.createdAt).toBe("2024-01-01T00:00:00Z");
|
|
378
|
+
expect(putCall.record.rights).toEqual({ uri: "at://rights", cid: "rights-cid" });
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
it("should upload new image", async () => {
|
|
382
|
+
const imageBlob = new Blob(["new image"], { type: "image/png" });
|
|
383
|
+
mockAgent.com.atproto.repo.uploadBlob.mockResolvedValue({
|
|
384
|
+
success: true,
|
|
385
|
+
data: { blob: { ref: { $link: "new-image-cid" }, mimeType: "image/png", size: 100 } },
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
await hypercertOps.update({
|
|
389
|
+
uri: "at://did:plc:test/org.hypercerts.claim.record/abc123",
|
|
390
|
+
updates: {},
|
|
391
|
+
image: imageBlob,
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
expect(mockAgent.com.atproto.repo.uploadBlob).toHaveBeenCalled();
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
it("should remove image when set to null", async () => {
|
|
398
|
+
mockAgent.com.atproto.repo.getRecord.mockResolvedValue({
|
|
399
|
+
success: true,
|
|
400
|
+
data: {
|
|
401
|
+
value: {
|
|
402
|
+
title: "Title",
|
|
403
|
+
description: "Desc",
|
|
404
|
+
workScope: "Scope",
|
|
405
|
+
workTimeframeFrom: "2024-01-01",
|
|
406
|
+
workTimeframeTo: "2024-12-31",
|
|
407
|
+
createdAt: "2024-01-01",
|
|
408
|
+
rights: { uri: "at://rights", cid: "cid" },
|
|
409
|
+
image: { ref: { $link: "old-image" } },
|
|
410
|
+
},
|
|
411
|
+
},
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
await hypercertOps.update({
|
|
415
|
+
uri: "at://did:plc:test/org.hypercerts.claim.record/abc123",
|
|
416
|
+
updates: {},
|
|
417
|
+
image: null,
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
const putCall = mockAgent.com.atproto.repo.putRecord.mock.calls[0][0];
|
|
421
|
+
expect(putCall.record.image).toBeUndefined();
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
it("should emit recordUpdated event", async () => {
|
|
425
|
+
const handler = vi.fn();
|
|
426
|
+
hypercertOps.on("recordUpdated", handler);
|
|
427
|
+
|
|
428
|
+
await hypercertOps.update({
|
|
429
|
+
uri: "at://did:plc:test/org.hypercerts.claim.record/abc123",
|
|
430
|
+
updates: { title: "New" },
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
expect(handler).toHaveBeenCalled();
|
|
434
|
+
});
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
describe("delete", () => {
|
|
438
|
+
it("should delete a hypercert successfully", async () => {
|
|
439
|
+
mockAgent.com.atproto.repo.deleteRecord.mockResolvedValue({
|
|
440
|
+
success: true,
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
await expect(
|
|
444
|
+
hypercertOps.delete("at://did:plc:test/org.hypercerts.claim.record/abc123"),
|
|
445
|
+
).resolves.toBeUndefined();
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
it("should throw ValidationError for invalid URI", async () => {
|
|
449
|
+
await expect(hypercertOps.delete("invalid")).rejects.toThrow(ValidationError);
|
|
450
|
+
});
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
describe("addContribution", () => {
|
|
454
|
+
beforeEach(() => {
|
|
455
|
+
mockAgent.com.atproto.repo.getRecord.mockResolvedValue({
|
|
456
|
+
success: true,
|
|
457
|
+
data: {
|
|
458
|
+
uri: "at://did:plc:test/org.hypercerts.claim.record/abc",
|
|
459
|
+
cid: "hypercert-cid",
|
|
460
|
+
value: {
|
|
461
|
+
title: "Test",
|
|
462
|
+
description: "Test",
|
|
463
|
+
workScope: "Test",
|
|
464
|
+
workTimeframeFrom: "2024-01-01",
|
|
465
|
+
workTimeframeTo: "2024-12-31",
|
|
466
|
+
createdAt: "2024-01-01",
|
|
467
|
+
},
|
|
468
|
+
},
|
|
469
|
+
});
|
|
470
|
+
|
|
471
|
+
mockAgent.com.atproto.repo.createRecord.mockResolvedValue({
|
|
472
|
+
success: true,
|
|
473
|
+
data: { uri: "at://did:plc:test/org.hypercerts.claim.contribution/xyz", cid: "contrib-cid" },
|
|
474
|
+
});
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
it("should create a contribution linked to hypercert", async () => {
|
|
478
|
+
const result = await hypercertOps.addContribution({
|
|
479
|
+
hypercertUri: "at://did:plc:test/org.hypercerts.claim.record/abc",
|
|
480
|
+
contributors: ["did:plc:contributor1"],
|
|
481
|
+
role: "Developer",
|
|
482
|
+
description: "Built the thing",
|
|
483
|
+
});
|
|
484
|
+
|
|
485
|
+
expect(result.uri).toContain("contribution");
|
|
486
|
+
});
|
|
487
|
+
|
|
488
|
+
it("should create standalone contribution without hypercert", async () => {
|
|
489
|
+
const result = await hypercertOps.addContribution({
|
|
490
|
+
contributors: ["did:plc:contributor1"],
|
|
491
|
+
role: "Developer",
|
|
492
|
+
});
|
|
493
|
+
|
|
494
|
+
expect(result.uri).toBeDefined();
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
it("should emit contributionCreated event", async () => {
|
|
498
|
+
const handler = vi.fn();
|
|
499
|
+
hypercertOps.on("contributionCreated", handler);
|
|
500
|
+
|
|
501
|
+
await hypercertOps.addContribution({
|
|
502
|
+
contributors: ["did:plc:test"],
|
|
503
|
+
role: "Tester",
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
expect(handler).toHaveBeenCalled();
|
|
507
|
+
});
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
describe("addMeasurement", () => {
|
|
511
|
+
beforeEach(() => {
|
|
512
|
+
mockAgent.com.atproto.repo.getRecord.mockResolvedValue({
|
|
513
|
+
success: true,
|
|
514
|
+
data: {
|
|
515
|
+
uri: "at://did:plc:test/org.hypercerts.claim.record/abc",
|
|
516
|
+
cid: "hypercert-cid",
|
|
517
|
+
value: {
|
|
518
|
+
title: "Test",
|
|
519
|
+
description: "Test",
|
|
520
|
+
workScope: "Test",
|
|
521
|
+
workTimeframeFrom: "2024-01-01",
|
|
522
|
+
workTimeframeTo: "2024-12-31",
|
|
523
|
+
createdAt: "2024-01-01",
|
|
524
|
+
},
|
|
525
|
+
},
|
|
526
|
+
});
|
|
527
|
+
|
|
528
|
+
mockAgent.com.atproto.repo.createRecord.mockResolvedValue({
|
|
529
|
+
success: true,
|
|
530
|
+
data: { uri: "at://did:plc:test/org.hypercerts.claim.measurement/xyz", cid: "measurement-cid" },
|
|
531
|
+
});
|
|
532
|
+
});
|
|
533
|
+
|
|
534
|
+
it("should create a measurement", async () => {
|
|
535
|
+
const result = await hypercertOps.addMeasurement({
|
|
536
|
+
hypercertUri: "at://did:plc:test/org.hypercerts.claim.record/abc",
|
|
537
|
+
measurers: ["did:plc:measurer1"],
|
|
538
|
+
metric: "CO2 Reduced",
|
|
539
|
+
value: "100 tons",
|
|
540
|
+
});
|
|
541
|
+
|
|
542
|
+
expect(result.uri).toContain("measurement");
|
|
543
|
+
});
|
|
544
|
+
});
|
|
545
|
+
|
|
546
|
+
describe("addEvaluation", () => {
|
|
547
|
+
beforeEach(() => {
|
|
548
|
+
mockAgent.com.atproto.repo.getRecord.mockResolvedValue({
|
|
549
|
+
success: true,
|
|
550
|
+
data: {
|
|
551
|
+
uri: "at://did:plc:test/org.hypercerts.claim.record/abc",
|
|
552
|
+
cid: "hypercert-cid",
|
|
553
|
+
value: {
|
|
554
|
+
title: "Test",
|
|
555
|
+
description: "Test",
|
|
556
|
+
workScope: "Test",
|
|
557
|
+
workTimeframeFrom: "2024-01-01",
|
|
558
|
+
workTimeframeTo: "2024-12-31",
|
|
559
|
+
createdAt: "2024-01-01",
|
|
560
|
+
},
|
|
561
|
+
},
|
|
562
|
+
});
|
|
563
|
+
|
|
564
|
+
mockAgent.com.atproto.repo.createRecord.mockResolvedValue({
|
|
565
|
+
success: true,
|
|
566
|
+
data: { uri: "at://did:plc:test/org.hypercerts.claim.evaluation/xyz", cid: "eval-cid" },
|
|
567
|
+
});
|
|
568
|
+
});
|
|
569
|
+
|
|
570
|
+
it("should create an evaluation", async () => {
|
|
571
|
+
const result = await hypercertOps.addEvaluation({
|
|
572
|
+
subjectUri: "at://did:plc:test/org.hypercerts.claim.record/abc",
|
|
573
|
+
evaluators: ["did:plc:evaluator1"],
|
|
574
|
+
summary: "Excellent work",
|
|
575
|
+
});
|
|
576
|
+
|
|
577
|
+
expect(result.uri).toContain("evaluation");
|
|
578
|
+
});
|
|
579
|
+
});
|
|
580
|
+
|
|
581
|
+
describe("createCollection", () => {
|
|
582
|
+
it("should create a collection", async () => {
|
|
583
|
+
mockAgent.com.atproto.repo.createRecord.mockResolvedValue({
|
|
584
|
+
success: true,
|
|
585
|
+
data: { uri: "at://did:plc:test/org.hypercerts.collection/xyz", cid: "collection-cid" },
|
|
586
|
+
});
|
|
587
|
+
|
|
588
|
+
const result = await hypercertOps.createCollection({
|
|
589
|
+
title: "My Collection",
|
|
590
|
+
claims: [{ uri: "at://claim1", cid: "cid1", weight: "50" }],
|
|
591
|
+
});
|
|
592
|
+
|
|
593
|
+
expect(result.uri).toContain("collection");
|
|
594
|
+
});
|
|
595
|
+
|
|
596
|
+
it("should emit collectionCreated event", async () => {
|
|
597
|
+
mockAgent.com.atproto.repo.createRecord.mockResolvedValue({
|
|
598
|
+
success: true,
|
|
599
|
+
data: { uri: "at://did:plc:test/org.hypercerts.collection/xyz", cid: "cid" },
|
|
600
|
+
});
|
|
601
|
+
|
|
602
|
+
const handler = vi.fn();
|
|
603
|
+
hypercertOps.on("collectionCreated", handler);
|
|
604
|
+
|
|
605
|
+
await hypercertOps.createCollection({
|
|
606
|
+
title: "Collection",
|
|
607
|
+
claims: [],
|
|
608
|
+
});
|
|
609
|
+
|
|
610
|
+
expect(handler).toHaveBeenCalled();
|
|
611
|
+
});
|
|
612
|
+
});
|
|
613
|
+
|
|
614
|
+
describe("getCollection", () => {
|
|
615
|
+
it("should get a collection", async () => {
|
|
616
|
+
mockAgent.com.atproto.repo.getRecord.mockResolvedValue({
|
|
617
|
+
success: true,
|
|
618
|
+
data: {
|
|
619
|
+
uri: "at://did:plc:test/org.hypercerts.collection/abc",
|
|
620
|
+
cid: "cid",
|
|
621
|
+
value: { title: "Collection", claims: [], createdAt: "2024-01-01" },
|
|
622
|
+
},
|
|
623
|
+
});
|
|
624
|
+
|
|
625
|
+
const result = await hypercertOps.getCollection("at://did:plc:test/org.hypercerts.collection/abc");
|
|
626
|
+
|
|
627
|
+
expect(result.record.title).toBe("Collection");
|
|
628
|
+
});
|
|
629
|
+
});
|
|
630
|
+
|
|
631
|
+
describe("listCollections", () => {
|
|
632
|
+
it("should list collections", async () => {
|
|
633
|
+
mockAgent.com.atproto.repo.listRecords.mockResolvedValue({
|
|
634
|
+
success: true,
|
|
635
|
+
data: {
|
|
636
|
+
records: [
|
|
637
|
+
{
|
|
638
|
+
uri: "at://test/org.hypercerts.collection/1",
|
|
639
|
+
cid: "cid",
|
|
640
|
+
value: { title: "Col", claims: [], createdAt: "2024-01-01" },
|
|
641
|
+
},
|
|
642
|
+
],
|
|
643
|
+
cursor: undefined,
|
|
644
|
+
},
|
|
645
|
+
});
|
|
646
|
+
|
|
647
|
+
const result = await hypercertOps.listCollections();
|
|
648
|
+
|
|
649
|
+
expect(result.records).toHaveLength(1);
|
|
650
|
+
});
|
|
651
|
+
});
|
|
652
|
+
});
|