@qnsp/storage-sdk 0.2.1 → 0.3.1
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 +4 -0
- package/CHANGELOG.md +49 -0
- package/LICENSE +21 -7
- package/README.md +22 -72
- package/dist/event-envelope.d.ts.map +1 -1
- package/dist/event-envelope.js +1 -2
- package/dist/event-envelope.js.map +1 -1
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js +0 -1
- package/dist/events.js.map +1 -1
- package/dist/index.d.ts +11 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +204 -22
- package/dist/index.js.map +1 -1
- package/dist/validation.d.ts +10 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +20 -0
- package/dist/validation.js.map +1 -0
- package/package.json +59 -39
- package/src/event-envelope.ts +1 -2
- package/src/events.ts +0 -2
- package/src/index.test.ts +92 -49
- package/src/index.ts +229 -26
- package/src/validation.ts +21 -0
- package/tsconfig.tsbuildinfo +1 -1
package/src/index.test.ts
CHANGED
|
@@ -1,9 +1,34 @@
|
|
|
1
|
+
import { clearActivationCache } from "@qnsp/sdk-activation";
|
|
1
2
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
3
|
import { StorageClient, StorageEventsClient } from "./index.js";
|
|
3
4
|
|
|
4
|
-
const mockTenantId = "
|
|
5
|
+
const mockTenantId = "a1b2c3d4-e5f6-7890-abcd-ef1234567890";
|
|
5
6
|
const baseUrl = "https://storage.qnsp.example/";
|
|
6
7
|
|
|
8
|
+
// Valid UUID test constants
|
|
9
|
+
const mockUploadId = "11111111-1111-4111-a111-111111111111";
|
|
10
|
+
const mockUploadId2 = "22222222-2222-4222-a222-222222222222";
|
|
11
|
+
const mockUploadIdErr = "33333333-3333-4333-a333-333333333333";
|
|
12
|
+
const mockDocumentId = "44444444-4444-4444-a444-444444444444";
|
|
13
|
+
|
|
14
|
+
const MOCK_ACTIVATION_RESPONSE = {
|
|
15
|
+
activated: true,
|
|
16
|
+
tenantId: "a1b2c3d4-e5f6-4789-8abc-def012345678",
|
|
17
|
+
tier: "dev-pro",
|
|
18
|
+
activationToken: "tok_test",
|
|
19
|
+
expiresInSeconds: 3600,
|
|
20
|
+
activatedAt: new Date().toISOString(),
|
|
21
|
+
limits: {
|
|
22
|
+
storageGB: 50,
|
|
23
|
+
apiCalls: 100_000,
|
|
24
|
+
enclavesEnabled: false,
|
|
25
|
+
aiTrainingEnabled: false,
|
|
26
|
+
aiInferenceEnabled: true,
|
|
27
|
+
sseEnabled: true,
|
|
28
|
+
vaultEnabled: true,
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
|
|
7
32
|
function jsonResponse(payload: unknown, init?: ResponseInit): Response {
|
|
8
33
|
return new Response(JSON.stringify(payload), {
|
|
9
34
|
status: 200,
|
|
@@ -43,6 +68,7 @@ async function collectStream(stream: ReadableStream<Uint8Array>): Promise<Uint8A
|
|
|
43
68
|
|
|
44
69
|
describe("StorageClient", () => {
|
|
45
70
|
beforeEach(() => {
|
|
71
|
+
clearActivationCache();
|
|
46
72
|
vi.stubGlobal("fetch", vi.fn());
|
|
47
73
|
});
|
|
48
74
|
|
|
@@ -60,8 +86,8 @@ describe("StorageClient", () => {
|
|
|
60
86
|
});
|
|
61
87
|
|
|
62
88
|
const expectedResponse = {
|
|
63
|
-
uploadId:
|
|
64
|
-
documentId:
|
|
89
|
+
uploadId: mockUploadId,
|
|
90
|
+
documentId: mockDocumentId,
|
|
65
91
|
tenantId: mockTenantId,
|
|
66
92
|
chunkSizeBytes: 16_777_216,
|
|
67
93
|
totalSizeBytes: 42_000_000,
|
|
@@ -76,9 +102,9 @@ describe("StorageClient", () => {
|
|
|
76
102
|
},
|
|
77
103
|
};
|
|
78
104
|
|
|
79
|
-
(globalThis.fetch as unknown as ReturnType<typeof vi.fn>)
|
|
80
|
-
jsonResponse(
|
|
81
|
-
|
|
105
|
+
(globalThis.fetch as unknown as ReturnType<typeof vi.fn>)
|
|
106
|
+
.mockResolvedValueOnce(jsonResponse(MOCK_ACTIVATION_RESPONSE))
|
|
107
|
+
.mockResolvedValueOnce(jsonResponse(expectedResponse));
|
|
82
108
|
|
|
83
109
|
const result = await client.initiateUpload({
|
|
84
110
|
name: "contract.pdf",
|
|
@@ -88,8 +114,8 @@ describe("StorageClient", () => {
|
|
|
88
114
|
});
|
|
89
115
|
|
|
90
116
|
expect(result).toEqual(expectedResponse);
|
|
91
|
-
expect(globalThis.fetch).toHaveBeenCalledTimes(
|
|
92
|
-
const call = (globalThis.fetch as unknown as ReturnType<typeof vi.fn>).mock.calls.at(
|
|
117
|
+
expect(globalThis.fetch).toHaveBeenCalledTimes(2);
|
|
118
|
+
const call = (globalThis.fetch as unknown as ReturnType<typeof vi.fn>).mock.calls.at(1);
|
|
93
119
|
expect(call).toBeDefined();
|
|
94
120
|
if (!call) throw new Error("fetch not invoked");
|
|
95
121
|
const [url, init] = call;
|
|
@@ -118,7 +144,7 @@ describe("StorageClient", () => {
|
|
|
118
144
|
});
|
|
119
145
|
|
|
120
146
|
const expectedPayload = {
|
|
121
|
-
uploadId:
|
|
147
|
+
uploadId: mockUploadId,
|
|
122
148
|
partId: 1,
|
|
123
149
|
status: "uploaded",
|
|
124
150
|
sizeBytes: 3,
|
|
@@ -131,16 +157,16 @@ describe("StorageClient", () => {
|
|
|
131
157
|
resumeToken: null,
|
|
132
158
|
};
|
|
133
159
|
|
|
134
|
-
(globalThis.fetch as unknown as ReturnType<typeof vi.fn>)
|
|
135
|
-
jsonResponse(
|
|
136
|
-
|
|
160
|
+
(globalThis.fetch as unknown as ReturnType<typeof vi.fn>)
|
|
161
|
+
.mockResolvedValueOnce(jsonResponse(MOCK_ACTIVATION_RESPONSE))
|
|
162
|
+
.mockResolvedValueOnce(jsonResponse(expectedPayload));
|
|
137
163
|
|
|
138
164
|
const source = Buffer.from([0xde, 0xad, 0xbe]);
|
|
139
165
|
|
|
140
|
-
const result = await client.uploadPart(
|
|
166
|
+
const result = await client.uploadPart(mockUploadId, 1, source);
|
|
141
167
|
expect(result).toEqual(expectedPayload);
|
|
142
|
-
expect(globalThis.fetch).toHaveBeenCalledTimes(
|
|
143
|
-
const uploadCall = (globalThis.fetch as unknown as ReturnType<typeof vi.fn>).mock.calls.at(
|
|
168
|
+
expect(globalThis.fetch).toHaveBeenCalledTimes(2);
|
|
169
|
+
const uploadCall = (globalThis.fetch as unknown as ReturnType<typeof vi.fn>).mock.calls.at(1);
|
|
144
170
|
expect(uploadCall).toBeDefined();
|
|
145
171
|
if (!uploadCall) throw new Error("fetch not invoked");
|
|
146
172
|
const [, init] = uploadCall;
|
|
@@ -158,6 +184,7 @@ describe("StorageClient", () => {
|
|
|
158
184
|
it("parses ranged download responses and surfaces stream metadata", async () => {
|
|
159
185
|
const client = new StorageClient({
|
|
160
186
|
baseUrl,
|
|
187
|
+
apiKey: "test-api-key",
|
|
161
188
|
tenantId: mockTenantId,
|
|
162
189
|
});
|
|
163
190
|
|
|
@@ -177,14 +204,16 @@ describe("StorageClient", () => {
|
|
|
177
204
|
},
|
|
178
205
|
});
|
|
179
206
|
|
|
180
|
-
(globalThis.fetch as unknown as ReturnType<typeof vi.fn>)
|
|
207
|
+
(globalThis.fetch as unknown as ReturnType<typeof vi.fn>)
|
|
208
|
+
.mockResolvedValueOnce(jsonResponse(MOCK_ACTIVATION_RESPONSE))
|
|
209
|
+
.mockResolvedValueOnce(response);
|
|
181
210
|
|
|
182
|
-
const result = await client.downloadStream(
|
|
211
|
+
const result = await client.downloadStream(mockDocumentId, 2, {
|
|
183
212
|
range: "bytes=0-2",
|
|
184
213
|
});
|
|
185
214
|
|
|
186
215
|
expect(globalThis.fetch).toHaveBeenCalledWith(
|
|
187
|
-
|
|
216
|
+
`https://storage.qnsp.example/storage/v1/documents/${mockDocumentId}/versions/2/content?tenantId=${mockTenantId}&range=bytes%3D0-2`,
|
|
188
217
|
expect.objectContaining({
|
|
189
218
|
method: "GET",
|
|
190
219
|
headers: expect.objectContaining({
|
|
@@ -207,13 +236,14 @@ describe("StorageClient", () => {
|
|
|
207
236
|
const telemetry = { record: vi.fn() };
|
|
208
237
|
const client = new StorageClient({
|
|
209
238
|
baseUrl,
|
|
239
|
+
apiKey: "test-api-key",
|
|
210
240
|
tenantId: mockTenantId,
|
|
211
241
|
telemetry,
|
|
212
242
|
});
|
|
213
243
|
|
|
214
244
|
const statusResponse = {
|
|
215
|
-
uploadId:
|
|
216
|
-
documentId:
|
|
245
|
+
uploadId: mockUploadId2,
|
|
246
|
+
documentId: mockDocumentId,
|
|
217
247
|
tenantId: mockTenantId,
|
|
218
248
|
status: "pending",
|
|
219
249
|
chunkSizeBytes: 4,
|
|
@@ -228,11 +258,11 @@ describe("StorageClient", () => {
|
|
|
228
258
|
lastPartNumber: 1,
|
|
229
259
|
};
|
|
230
260
|
|
|
231
|
-
(globalThis.fetch as unknown as ReturnType<typeof vi.fn>)
|
|
232
|
-
jsonResponse(
|
|
233
|
-
|
|
261
|
+
(globalThis.fetch as unknown as ReturnType<typeof vi.fn>)
|
|
262
|
+
.mockResolvedValueOnce(jsonResponse(MOCK_ACTIVATION_RESPONSE))
|
|
263
|
+
.mockResolvedValueOnce(jsonResponse(statusResponse));
|
|
234
264
|
|
|
235
|
-
await client.getUploadStatus(
|
|
265
|
+
await client.getUploadStatus(mockUploadId2);
|
|
236
266
|
|
|
237
267
|
expect(telemetry.record).toHaveBeenCalled();
|
|
238
268
|
const event = (telemetry.record as ReturnType<typeof vi.fn>).mock.calls.at(-1)?.[0];
|
|
@@ -248,15 +278,16 @@ describe("StorageClient", () => {
|
|
|
248
278
|
const telemetry = { record: vi.fn() };
|
|
249
279
|
const client = new StorageClient({
|
|
250
280
|
baseUrl,
|
|
281
|
+
apiKey: "test-api-key",
|
|
251
282
|
tenantId: mockTenantId,
|
|
252
283
|
telemetry,
|
|
253
284
|
});
|
|
254
285
|
|
|
255
|
-
(globalThis.fetch as unknown as ReturnType<typeof vi.fn>)
|
|
256
|
-
|
|
257
|
-
|
|
286
|
+
(globalThis.fetch as unknown as ReturnType<typeof vi.fn>)
|
|
287
|
+
.mockResolvedValueOnce(jsonResponse(MOCK_ACTIVATION_RESPONSE))
|
|
288
|
+
.mockResolvedValueOnce(new Response("boom", { status: 500, statusText: "error" }));
|
|
258
289
|
|
|
259
|
-
await expect(client.completeUpload(
|
|
290
|
+
await expect(client.completeUpload(mockUploadIdErr)).rejects.toThrow(/Storage API error/);
|
|
260
291
|
|
|
261
292
|
const event = (telemetry.record as ReturnType<typeof vi.fn>).mock.calls.at(-1)?.[0];
|
|
262
293
|
expect(event).toBeDefined();
|
|
@@ -271,12 +302,13 @@ describe("StorageClient", () => {
|
|
|
271
302
|
const telemetry = { record: vi.fn() };
|
|
272
303
|
const client = new StorageClient({
|
|
273
304
|
baseUrl,
|
|
305
|
+
apiKey: "test-api-key",
|
|
274
306
|
tenantId: mockTenantId,
|
|
275
307
|
telemetry,
|
|
276
308
|
});
|
|
277
309
|
|
|
278
310
|
const expectedPayload = {
|
|
279
|
-
uploadId:
|
|
311
|
+
uploadId: mockUploadId,
|
|
280
312
|
partId: 1,
|
|
281
313
|
status: "uploaded",
|
|
282
314
|
sizeBytes: 3,
|
|
@@ -289,11 +321,11 @@ describe("StorageClient", () => {
|
|
|
289
321
|
resumeToken: null,
|
|
290
322
|
};
|
|
291
323
|
|
|
292
|
-
(globalThis.fetch as unknown as ReturnType<typeof vi.fn>)
|
|
293
|
-
jsonResponse(
|
|
294
|
-
|
|
324
|
+
(globalThis.fetch as unknown as ReturnType<typeof vi.fn>)
|
|
325
|
+
.mockResolvedValueOnce(jsonResponse(MOCK_ACTIVATION_RESPONSE))
|
|
326
|
+
.mockResolvedValueOnce(jsonResponse(expectedPayload));
|
|
295
327
|
|
|
296
|
-
await client.uploadPart(
|
|
328
|
+
await client.uploadPart(mockUploadId, 1, Buffer.from([1, 2, 3]));
|
|
297
329
|
|
|
298
330
|
const event = (telemetry.record as ReturnType<typeof vi.fn>).mock.calls.at(-1)?.[0];
|
|
299
331
|
expect(event).toBeDefined();
|
|
@@ -312,10 +344,12 @@ describe("StorageClient", () => {
|
|
|
312
344
|
});
|
|
313
345
|
|
|
314
346
|
(globalThis.fetch as unknown as ReturnType<typeof vi.fn>)
|
|
347
|
+
// activation
|
|
348
|
+
.mockResolvedValueOnce(jsonResponse(MOCK_ACTIVATION_RESPONSE))
|
|
315
349
|
// PATCH response
|
|
316
350
|
.mockResolvedValueOnce(
|
|
317
351
|
jsonResponse({
|
|
318
|
-
documentId:
|
|
352
|
+
documentId: mockDocumentId,
|
|
319
353
|
tenantId: mockTenantId,
|
|
320
354
|
compliance: {
|
|
321
355
|
retentionMode: "compliance",
|
|
@@ -328,7 +362,7 @@ describe("StorageClient", () => {
|
|
|
328
362
|
// GET response
|
|
329
363
|
.mockResolvedValueOnce(
|
|
330
364
|
jsonResponse({
|
|
331
|
-
documentId:
|
|
365
|
+
documentId: mockDocumentId,
|
|
332
366
|
tenantId: mockTenantId,
|
|
333
367
|
compliance: {
|
|
334
368
|
retentionMode: "compliance",
|
|
@@ -344,26 +378,30 @@ describe("StorageClient", () => {
|
|
|
344
378
|
}),
|
|
345
379
|
);
|
|
346
380
|
|
|
347
|
-
const updated = await client.updateDocumentPolicies(
|
|
381
|
+
const updated = await client.updateDocumentPolicies(mockDocumentId, {
|
|
348
382
|
retentionMode: "compliance",
|
|
349
383
|
retainUntil: "2026-01-01T00:00:00Z",
|
|
350
384
|
legalHolds: ["hold-1"],
|
|
351
385
|
});
|
|
352
|
-
expect(updated.documentId).toBe(
|
|
386
|
+
expect(updated.documentId).toBe(mockDocumentId);
|
|
353
387
|
const patchCall = (globalThis.fetch as unknown as ReturnType<typeof vi.fn>).mock
|
|
354
|
-
.calls[
|
|
355
|
-
expect(patchCall[0]).toBe(
|
|
388
|
+
.calls[1] as unknown as [string, RequestInit];
|
|
389
|
+
expect(patchCall[0]).toBe(
|
|
390
|
+
`https://storage.qnsp.example/storage/v1/documents/${mockDocumentId}/policies`,
|
|
391
|
+
);
|
|
356
392
|
expect(patchCall[1]?.headers).toMatchObject({
|
|
357
393
|
"Content-Type": "application/json",
|
|
358
394
|
Authorization: "Bearer key",
|
|
359
395
|
"x-tenant-id": mockTenantId,
|
|
360
396
|
});
|
|
361
397
|
|
|
362
|
-
const policies = await client.getDocumentPolicies(
|
|
398
|
+
const policies = await client.getDocumentPolicies(mockDocumentId);
|
|
363
399
|
expect(policies.tenantId).toBe(mockTenantId);
|
|
364
400
|
const getCall = (globalThis.fetch as unknown as ReturnType<typeof vi.fn>).mock
|
|
365
|
-
.calls[
|
|
366
|
-
expect(getCall[0]).toBe(
|
|
401
|
+
.calls[2] as unknown as [string, RequestInit];
|
|
402
|
+
expect(getCall[0]).toBe(
|
|
403
|
+
`https://storage.qnsp.example/storage/v1/documents/${mockDocumentId}/policies`,
|
|
404
|
+
);
|
|
367
405
|
expect(getCall[1]?.headers).toMatchObject({
|
|
368
406
|
"Content-Type": "application/json",
|
|
369
407
|
Authorization: "Bearer key",
|
|
@@ -379,10 +417,12 @@ describe("StorageClient", () => {
|
|
|
379
417
|
});
|
|
380
418
|
|
|
381
419
|
(globalThis.fetch as unknown as ReturnType<typeof vi.fn>)
|
|
420
|
+
// activation
|
|
421
|
+
.mockResolvedValueOnce(jsonResponse(MOCK_ACTIVATION_RESPONSE))
|
|
382
422
|
// apply
|
|
383
423
|
.mockResolvedValueOnce(
|
|
384
424
|
jsonResponse({
|
|
385
|
-
documentId:
|
|
425
|
+
documentId: mockDocumentId,
|
|
386
426
|
tenantId: mockTenantId,
|
|
387
427
|
legalHolds: ["hold-9"],
|
|
388
428
|
}),
|
|
@@ -390,21 +430,23 @@ describe("StorageClient", () => {
|
|
|
390
430
|
// release (204)
|
|
391
431
|
.mockResolvedValueOnce(new Response(null, { status: 204 }));
|
|
392
432
|
|
|
393
|
-
const applied = await client.applyLegalHold(
|
|
433
|
+
const applied = await client.applyLegalHold(mockDocumentId, { holdId: "hold-9" });
|
|
394
434
|
expect(applied.legalHolds).toContain("hold-9");
|
|
395
435
|
const postCall = (globalThis.fetch as unknown as ReturnType<typeof vi.fn>).mock
|
|
396
|
-
.calls[
|
|
397
|
-
expect(postCall[0]).toBe(
|
|
436
|
+
.calls[1] as unknown as [string, RequestInit];
|
|
437
|
+
expect(postCall[0]).toBe(
|
|
438
|
+
`https://storage.qnsp.example/storage/v1/documents/${mockDocumentId}/legal-holds`,
|
|
439
|
+
);
|
|
398
440
|
expect(postCall[1]?.method).toBe("POST");
|
|
399
441
|
expect(postCall[1]?.headers).toMatchObject({
|
|
400
442
|
"x-tenant-id": mockTenantId,
|
|
401
443
|
});
|
|
402
444
|
|
|
403
|
-
await client.releaseLegalHold(
|
|
445
|
+
await client.releaseLegalHold(mockDocumentId, "hold-9");
|
|
404
446
|
const delCall = (globalThis.fetch as unknown as ReturnType<typeof vi.fn>).mock
|
|
405
|
-
.calls[
|
|
447
|
+
.calls[2] as unknown as [string, RequestInit];
|
|
406
448
|
expect(delCall[0]).toBe(
|
|
407
|
-
|
|
449
|
+
`https://storage.qnsp.example/storage/v1/documents/${mockDocumentId}/legal-holds/hold-9`,
|
|
408
450
|
);
|
|
409
451
|
expect(delCall[1]?.method).toBe("DELETE");
|
|
410
452
|
expect(delCall[1]?.headers).toMatchObject({
|
|
@@ -427,6 +469,7 @@ describe("StorageEventsClient", () => {
|
|
|
427
469
|
const telemetry = { record: vi.fn() };
|
|
428
470
|
const client = new StorageEventsClient({
|
|
429
471
|
baseUrl,
|
|
472
|
+
apiKey: "test-api-key",
|
|
430
473
|
telemetry,
|
|
431
474
|
});
|
|
432
475
|
|