dataspace-client-sdk-node 0.1.1 → 0.1.2
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/CHANGELOG.md +28 -0
- package/README.md +40 -0
- package/dist/client.d.ts +114 -32
- package/dist/client.js +482 -30
- package/dist/types.d.ts +92 -0
- package/docs/BACKEND_NODE_INTEGRATION.md +122 -0
- package/docs/DATA_MODEL_ALIGNMENT.md +37 -13
- package/docs/PORTAL_BACKEND_INTEGRATION_HANDOVER.md +335 -0
- package/docs/REACT_WEB_INTEGRATION.md +72 -0
- package/examples/smoke-legal-org-local.mjs +40 -0
- package/package.json +1 -1
- package/src/client.ts +628 -66
- package/src/types.ts +101 -0
- package/tests/client.test.mjs +491 -0
- package/SDK_PARITY_MAP.md +0 -120
package/src/types.ts
CHANGED
|
@@ -195,6 +195,17 @@ export type FamilyOrganizationSummary = {
|
|
|
195
195
|
updatedAt?: string;
|
|
196
196
|
};
|
|
197
197
|
|
|
198
|
+
export type OfferPreview = {
|
|
199
|
+
offerId?: string;
|
|
200
|
+
amount?: string;
|
|
201
|
+
currency?: string;
|
|
202
|
+
seats?: number;
|
|
203
|
+
planName?: string;
|
|
204
|
+
sku?: string;
|
|
205
|
+
paymentMethod?: string;
|
|
206
|
+
checkoutUrl?: string;
|
|
207
|
+
};
|
|
208
|
+
|
|
198
209
|
/**
|
|
199
210
|
* Input for organization activation in GW using ICA-derived proof material.
|
|
200
211
|
*
|
|
@@ -203,12 +214,43 @@ export type FamilyOrganizationSummary = {
|
|
|
203
214
|
*/
|
|
204
215
|
export type GatewayOrganizationActivationInput = {
|
|
205
216
|
vpToken: string;
|
|
217
|
+
/** Generic requested seats/members for initial offer sizing. Defaults to 2. */
|
|
218
|
+
numberOfMembers?: number;
|
|
219
|
+
organizationVc?: string;
|
|
220
|
+
legalRepresentativeVc?: string;
|
|
221
|
+
regulatoryEvidence?: Record<string, unknown>;
|
|
222
|
+
/** @deprecated Prefer `numberOfMembers` and explicit input fields. */
|
|
223
|
+
additionalClaims?: Record<string, unknown>;
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
export type GatewayOrganizationActivationSimpleInput = {
|
|
227
|
+
jurisdiction?: string;
|
|
228
|
+
sector?: string;
|
|
229
|
+
vpToken: string;
|
|
230
|
+
serviceProviderDidWeb?: string;
|
|
231
|
+
serviceProviderUrl?: string;
|
|
232
|
+
controllerEmail?: string;
|
|
233
|
+
controllerTelephone?: string;
|
|
234
|
+
controllerRole: string;
|
|
235
|
+
numberOfMembers?: number;
|
|
236
|
+
timeoutSeconds?: number;
|
|
237
|
+
intervalSeconds?: number;
|
|
206
238
|
organizationVc?: string;
|
|
207
239
|
legalRepresentativeVc?: string;
|
|
208
240
|
regulatoryEvidence?: Record<string, unknown>;
|
|
209
241
|
additionalClaims?: Record<string, unknown>;
|
|
210
242
|
};
|
|
211
243
|
|
|
244
|
+
export type LegalOrganizationOrderSimpleInput = {
|
|
245
|
+
jurisdiction?: string;
|
|
246
|
+
sector?: string;
|
|
247
|
+
offerId: string;
|
|
248
|
+
timeoutSeconds?: number;
|
|
249
|
+
intervalSeconds?: number;
|
|
250
|
+
dataType?: string;
|
|
251
|
+
additionalClaims?: Record<string, unknown>;
|
|
252
|
+
};
|
|
253
|
+
|
|
212
254
|
/**
|
|
213
255
|
* Input for device activation based on activation code exchange + DCR.
|
|
214
256
|
*/
|
|
@@ -219,6 +261,17 @@ export type EmployeeDeviceActivationInput = {
|
|
|
219
261
|
pollOptions?: PollOptions;
|
|
220
262
|
};
|
|
221
263
|
|
|
264
|
+
export type EmployeeDeviceActivationSimpleInput = {
|
|
265
|
+
tenantId?: string;
|
|
266
|
+
jurisdiction?: string;
|
|
267
|
+
sector?: string;
|
|
268
|
+
activationCode: string;
|
|
269
|
+
idToken: string;
|
|
270
|
+
dcrPayload: Record<string, unknown>;
|
|
271
|
+
timeoutSeconds?: number;
|
|
272
|
+
intervalSeconds?: number;
|
|
273
|
+
};
|
|
274
|
+
|
|
222
275
|
/**
|
|
223
276
|
* Result of device activation flow.
|
|
224
277
|
*
|
|
@@ -256,6 +309,40 @@ export type SubjectOrganizationBootstrapResult = {
|
|
|
256
309
|
confirmation?: SubmitAndPollResult;
|
|
257
310
|
};
|
|
258
311
|
|
|
312
|
+
export type IndividualOrganizationBootstrapSimpleInput = {
|
|
313
|
+
tenantId?: string;
|
|
314
|
+
jurisdiction?: string;
|
|
315
|
+
sector?: string;
|
|
316
|
+
alternateName: string;
|
|
317
|
+
controllerEmail?: string;
|
|
318
|
+
controllerTelephone?: string;
|
|
319
|
+
controllerRole?: string; // default org.hl7.v3.RoleCode|RESPRSN
|
|
320
|
+
timeoutSeconds?: number;
|
|
321
|
+
intervalSeconds?: number;
|
|
322
|
+
additionalClaims?: Record<string, unknown>;
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
export type IndividualOrganizationBootstrapSimpleResult = {
|
|
326
|
+
registration: SubmitAndPollResult;
|
|
327
|
+
offerId: string;
|
|
328
|
+
confirmation: SubmitAndPollResult;
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
export type IndividualOrganizationStartSimpleResult = {
|
|
332
|
+
registration: SubmitAndPollResult;
|
|
333
|
+
offerId: string;
|
|
334
|
+
offerPreview: OfferPreview;
|
|
335
|
+
};
|
|
336
|
+
|
|
337
|
+
export type IndividualOrganizationConfirmOrderSimpleInput = {
|
|
338
|
+
tenantId?: string;
|
|
339
|
+
jurisdiction?: string;
|
|
340
|
+
sector?: string;
|
|
341
|
+
offerId: string;
|
|
342
|
+
timeoutSeconds?: number;
|
|
343
|
+
intervalSeconds?: number;
|
|
344
|
+
};
|
|
345
|
+
|
|
259
346
|
/**
|
|
260
347
|
* Input for UC 5.5 IPS/FHIR import and index update.
|
|
261
348
|
*/
|
|
@@ -413,6 +500,8 @@ export type ClientOptions = {
|
|
|
413
500
|
bearerToken?: string;
|
|
414
501
|
defaultHeaders?: Record<string, string>;
|
|
415
502
|
wallet?: WalletProvider;
|
|
503
|
+
/** Optional default tenant context so calls can omit ctx repeatedly. */
|
|
504
|
+
ctx?: RouteContext;
|
|
416
505
|
};
|
|
417
506
|
|
|
418
507
|
/**
|
|
@@ -487,6 +576,18 @@ export type SmartTokenExchangeInput = {
|
|
|
487
576
|
path?: string;
|
|
488
577
|
};
|
|
489
578
|
|
|
579
|
+
export type SmartTokenRequestSimpleInput = {
|
|
580
|
+
tenantId?: string;
|
|
581
|
+
jurisdiction?: string;
|
|
582
|
+
sector?: string;
|
|
583
|
+
idToken: string;
|
|
584
|
+
scopes: string[];
|
|
585
|
+
endpointId?: string;
|
|
586
|
+
timeoutSeconds?: number;
|
|
587
|
+
intervalSeconds?: number;
|
|
588
|
+
additionalClaims?: Record<string, unknown>;
|
|
589
|
+
};
|
|
590
|
+
|
|
490
591
|
export type SmartTokenExchangeResult = {
|
|
491
592
|
status: 'fetched' | 'cached' | 'failed';
|
|
492
593
|
accessToken?: string;
|
package/tests/client.test.mjs
CHANGED
|
@@ -268,6 +268,10 @@ test('activateOrganizationInGatewayFromIcaProof submits activation and polls to
|
|
|
268
268
|
assert.equal(result.submit.status, 202);
|
|
269
269
|
assert.equal(result.poll.status, 200);
|
|
270
270
|
assert.equal(calls[0].url, 'http://localhost:3000/host/cds-ES/v1/test/registry/org.schema/Organization/_activate');
|
|
271
|
+
const submitPayload = JSON.parse(calls[0].options.body);
|
|
272
|
+
assert.equal(submitPayload.body.vp_token, 'vp-token-001');
|
|
273
|
+
assert.equal(submitPayload.body.organizationCredential, 'org-vc-jwt');
|
|
274
|
+
assert.equal(submitPayload.body.representativeCredential, 'legal-vc-jwt');
|
|
271
275
|
} finally {
|
|
272
276
|
globalThis.fetch = originalFetch;
|
|
273
277
|
}
|
|
@@ -388,6 +392,90 @@ test('bootstrapSubjectOrganizationIndex runs registration and confirmation flow'
|
|
|
388
392
|
}
|
|
389
393
|
});
|
|
390
394
|
|
|
395
|
+
test('bootstrapIndividualOrganizationSimple registers individual org and confirms family order', async () => {
|
|
396
|
+
const calls = [];
|
|
397
|
+
const originalFetch = globalThis.fetch;
|
|
398
|
+
|
|
399
|
+
globalThis.fetch = async (url, options) => {
|
|
400
|
+
calls.push({ url: String(url), options, body: options?.body ? JSON.parse(options.body) : undefined });
|
|
401
|
+
if (calls.length === 1 || calls.length === 3) return jsonResponse({ accepted: true }, 202);
|
|
402
|
+
if (calls.length === 2) {
|
|
403
|
+
return jsonResponse({
|
|
404
|
+
body: { data: [{ meta: { claims: { 'org.schema.Offer.identifier': 'urn:offer:family-001' } } }] },
|
|
405
|
+
}, 200);
|
|
406
|
+
}
|
|
407
|
+
return jsonResponse({ status: 'COMPLETED' }, 200);
|
|
408
|
+
};
|
|
409
|
+
|
|
410
|
+
try {
|
|
411
|
+
const client = new DataspaceNodeClient({
|
|
412
|
+
baseUrl: 'http://localhost:3000',
|
|
413
|
+
bearerToken: 'controller-token',
|
|
414
|
+
ctx: { tenantId: 'acme', jurisdiction: 'ES', sector: 'health-care' },
|
|
415
|
+
});
|
|
416
|
+
const result = await client.bootstrapIndividualOrganizationSimple({
|
|
417
|
+
alternateName: 'ana',
|
|
418
|
+
controllerTelephone: 'tel:+34600111222',
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
assert.equal(result.offerId, 'urn:offer:family-001');
|
|
422
|
+
assert.equal(result.registration.poll.status, 200);
|
|
423
|
+
assert.equal(result.confirmation.poll.status, 200);
|
|
424
|
+
assert.equal(
|
|
425
|
+
calls[0].url,
|
|
426
|
+
'http://localhost:3000/acme/cds-ES/v1/health-care/individual/org.schema/Organization/_batch',
|
|
427
|
+
);
|
|
428
|
+
assert.equal(
|
|
429
|
+
calls[2].url,
|
|
430
|
+
'http://localhost:3000/acme/cds-ES/v1/health-care/individual/org.schema/Order/_batch',
|
|
431
|
+
);
|
|
432
|
+
const registrationClaims = calls[0].body?.body?.data?.[0]?.meta?.claims || {};
|
|
433
|
+
assert.equal(registrationClaims['org.schema.Organization.alternateName'], 'ana');
|
|
434
|
+
assert.equal(registrationClaims['org.schema.Service.category'], 'health-care');
|
|
435
|
+
assert.equal(registrationClaims['org.schema.Person.telephone'], 'tel:+34600111222');
|
|
436
|
+
assert.equal(registrationClaims['org.schema.Person.hasOccupation'], 'org.hl7.v3.RoleCode|RESPRSN');
|
|
437
|
+
} finally {
|
|
438
|
+
globalThis.fetch = originalFetch;
|
|
439
|
+
}
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
test('startIndividualOrganizationSimple + confirmIndividualOrganizationOrderSimple support explicit offer acceptance UX', async () => {
|
|
443
|
+
const calls = [];
|
|
444
|
+
const originalFetch = globalThis.fetch;
|
|
445
|
+
|
|
446
|
+
globalThis.fetch = async (url, options) => {
|
|
447
|
+
calls.push({ url: String(url), options, body: options?.body ? JSON.parse(options.body) : undefined });
|
|
448
|
+
if (calls.length === 1 || calls.length === 3) return jsonResponse({ accepted: true }, 202);
|
|
449
|
+
if (calls.length === 2) {
|
|
450
|
+
return jsonResponse({
|
|
451
|
+
body: { data: [{ meta: { claims: { 'org.schema.Offer.identifier': 'urn:offer:family-002', 'org.schema.Offer.price': '0.00' } } }] },
|
|
452
|
+
}, 200);
|
|
453
|
+
}
|
|
454
|
+
return jsonResponse({ status: 'COMPLETED' }, 200);
|
|
455
|
+
};
|
|
456
|
+
|
|
457
|
+
try {
|
|
458
|
+
const client = new DataspaceNodeClient({
|
|
459
|
+
baseUrl: 'http://localhost:3000',
|
|
460
|
+
bearerToken: 'controller-token',
|
|
461
|
+
ctx: { tenantId: 'acme', jurisdiction: 'ES', sector: 'health-care' },
|
|
462
|
+
});
|
|
463
|
+
const started = await client.startIndividualOrganizationSimple({
|
|
464
|
+
alternateName: 'maria',
|
|
465
|
+
controllerEmail: 'maria@example.com',
|
|
466
|
+
});
|
|
467
|
+
assert.equal(started.offerId, 'urn:offer:family-002');
|
|
468
|
+
assert.equal(started.offerPreview.amount, '0.00');
|
|
469
|
+
|
|
470
|
+
const confirmed = await client.confirmIndividualOrganizationOrderSimple({
|
|
471
|
+
offerId: started.offerId,
|
|
472
|
+
});
|
|
473
|
+
assert.equal(confirmed.poll.status, 200);
|
|
474
|
+
} finally {
|
|
475
|
+
globalThis.fetch = originalFetch;
|
|
476
|
+
}
|
|
477
|
+
});
|
|
478
|
+
|
|
391
479
|
test('importIpsOrFhirAndUpdateIndex submits Composition in individual section', async () => {
|
|
392
480
|
const calls = [];
|
|
393
481
|
const originalFetch = globalThis.fetch;
|
|
@@ -523,6 +611,88 @@ test('requestSmartToken exchanges token in separate step', async () => {
|
|
|
523
611
|
}
|
|
524
612
|
});
|
|
525
613
|
|
|
614
|
+
test('requestSmartTokenSimple uses identity auth exchange async flow with default ctx', async () => {
|
|
615
|
+
const calls = [];
|
|
616
|
+
const originalFetch = globalThis.fetch;
|
|
617
|
+
|
|
618
|
+
globalThis.fetch = async (url, options) => {
|
|
619
|
+
calls.push({ url: String(url), options });
|
|
620
|
+
if (calls.length === 1) return jsonResponse({ accepted: true }, 202);
|
|
621
|
+
return jsonResponse({
|
|
622
|
+
access_token: 'smart-token-ctx-001',
|
|
623
|
+
token_type: 'Bearer',
|
|
624
|
+
scope: 'employee.healthcare.getIndexComposition',
|
|
625
|
+
expires_in: 3600,
|
|
626
|
+
}, 200);
|
|
627
|
+
};
|
|
628
|
+
|
|
629
|
+
try {
|
|
630
|
+
const client = new DataspaceNodeClient({
|
|
631
|
+
baseUrl: 'http://localhost:3000',
|
|
632
|
+
ctx: { tenantId: 'acme', jurisdiction: 'ES', sector: 'health-care' },
|
|
633
|
+
});
|
|
634
|
+
|
|
635
|
+
const result = await client.requestSmartTokenSimple({
|
|
636
|
+
idToken: 'employee-id-token-001',
|
|
637
|
+
scopes: ['employee.healthcare.getIndexComposition'],
|
|
638
|
+
timeoutSeconds: 5,
|
|
639
|
+
intervalSeconds: 0.001,
|
|
640
|
+
});
|
|
641
|
+
|
|
642
|
+
assert.equal(result.status, 'fetched');
|
|
643
|
+
assert.equal(result.accessToken, 'smart-token-ctx-001');
|
|
644
|
+
assert.equal(calls.length, 2);
|
|
645
|
+
assert.equal(calls[0].url, 'http://localhost:3000/host/cds-ES/v1/health-care/acme/identity/auth/_exchange');
|
|
646
|
+
assert.equal(calls[1].url, 'http://localhost:3000/host/cds-ES/v1/health-care/acme/identity/auth/_exchange-response');
|
|
647
|
+
} finally {
|
|
648
|
+
globalThis.fetch = originalFetch;
|
|
649
|
+
}
|
|
650
|
+
});
|
|
651
|
+
|
|
652
|
+
test('activateEmployeeDeviceWithActivationCodeSimple uses one-object input and default ctx', async () => {
|
|
653
|
+
const calls = [];
|
|
654
|
+
const originalFetch = globalThis.fetch;
|
|
655
|
+
|
|
656
|
+
globalThis.fetch = async (url, options) => {
|
|
657
|
+
calls.push({ url: String(url), options });
|
|
658
|
+
switch (calls.length) {
|
|
659
|
+
case 1:
|
|
660
|
+
return jsonResponse({ accepted: true }, 202); // exchange submit
|
|
661
|
+
case 2:
|
|
662
|
+
return jsonResponse({ body: { initial_access_token: 'initial-access-simple-001' } }, 200); // exchange poll
|
|
663
|
+
case 3:
|
|
664
|
+
return jsonResponse({ accepted: true }, 202); // dcr submit
|
|
665
|
+
default:
|
|
666
|
+
return jsonResponse({ body: { client_id: 'did:web:device-simple-001' } }, 200); // dcr poll
|
|
667
|
+
}
|
|
668
|
+
};
|
|
669
|
+
|
|
670
|
+
try {
|
|
671
|
+
const client = new DataspaceNodeClient({
|
|
672
|
+
baseUrl: 'http://localhost:3000',
|
|
673
|
+
ctx: { tenantId: 'acme', jurisdiction: 'ES', sector: 'health-care' },
|
|
674
|
+
});
|
|
675
|
+
const result = await client.activateEmployeeDeviceWithActivationCodeSimple({
|
|
676
|
+
activationCode: 'ACT-SIMPLE-001',
|
|
677
|
+
idToken: 'user-id-token-001',
|
|
678
|
+
dcrPayload: {
|
|
679
|
+
application_type: 'web',
|
|
680
|
+
client_name: 'Acme Portal',
|
|
681
|
+
token_endpoint_auth_method: 'private_key_jwt',
|
|
682
|
+
},
|
|
683
|
+
timeoutSeconds: 5,
|
|
684
|
+
intervalSeconds: 0.001,
|
|
685
|
+
});
|
|
686
|
+
|
|
687
|
+
assert.equal(result.initialAccessToken, 'initial-access-simple-001');
|
|
688
|
+
assert.equal(calls.length, 4);
|
|
689
|
+
assert.equal(calls[0].url, 'http://localhost:3000/host/cds-ES/v1/health-care/acme/identity/auth/_exchange');
|
|
690
|
+
assert.equal(calls[2].url, 'http://localhost:3000/host/cds-ES/v1/health-care/acme/identity/auth/_dcr');
|
|
691
|
+
} finally {
|
|
692
|
+
globalThis.fetch = originalFetch;
|
|
693
|
+
}
|
|
694
|
+
});
|
|
695
|
+
|
|
526
696
|
test('generateDigitalTwinFromSubjectData submits digital twin Composition', async () => {
|
|
527
697
|
const calls = [];
|
|
528
698
|
const originalFetch = globalThis.fetch;
|
|
@@ -890,3 +1060,324 @@ test('searchFamilyOrganization returns null for not_found status', async () => {
|
|
|
890
1060
|
globalThis.fetch = originalFetch;
|
|
891
1061
|
}
|
|
892
1062
|
});
|
|
1063
|
+
|
|
1064
|
+
test('offer helpers extract offerId and preview from activation-like response', () => {
|
|
1065
|
+
const client = new DataspaceNodeClient({ baseUrl: 'http://localhost:3000' });
|
|
1066
|
+
const result = {
|
|
1067
|
+
poll: {
|
|
1068
|
+
body: {
|
|
1069
|
+
body: {
|
|
1070
|
+
data: [{
|
|
1071
|
+
meta: {
|
|
1072
|
+
claims: {
|
|
1073
|
+
'org.schema.Offer.identifier': 'urn:offer:abc',
|
|
1074
|
+
'org.schema.Offer.price': '0.00',
|
|
1075
|
+
'org.schema.Offer.priceCurrency': 'EUR',
|
|
1076
|
+
'org.schema.Offer.eligibleQuantity.value': 2,
|
|
1077
|
+
'org.schema.Offer.itemOffered.name': 'License Tier XS',
|
|
1078
|
+
'org.schema.Offer.itemOffered.sku': 'portal-web',
|
|
1079
|
+
'org.schema.Offer.acceptedPaymentMethod': 'Stripe',
|
|
1080
|
+
'org.schema.Offer.checkoutPageURLTemplate': 'https://pay.example.com/checkout/123',
|
|
1081
|
+
},
|
|
1082
|
+
},
|
|
1083
|
+
}],
|
|
1084
|
+
},
|
|
1085
|
+
},
|
|
1086
|
+
},
|
|
1087
|
+
};
|
|
1088
|
+
|
|
1089
|
+
assert.equal(client.getOfferIdFromResponse(result), 'urn:offer:abc');
|
|
1090
|
+
const preview = client.getOfferPreviewFromResponse(result);
|
|
1091
|
+
assert.equal(preview.offerId, 'urn:offer:abc');
|
|
1092
|
+
assert.equal(preview.amount, '0.00');
|
|
1093
|
+
assert.equal(preview.currency, 'EUR');
|
|
1094
|
+
assert.equal(preview.seats, 2);
|
|
1095
|
+
assert.equal(preview.planName, 'License Tier XS');
|
|
1096
|
+
assert.equal(preview.sku, 'portal-web');
|
|
1097
|
+
assert.equal(preview.paymentMethod, 'Stripe');
|
|
1098
|
+
assert.equal(preview.checkoutUrl, 'https://pay.example.com/checkout/123');
|
|
1099
|
+
});
|
|
1100
|
+
|
|
1101
|
+
test('activateOrganizationInGatewaySimple maps single-object input and seconds options', async () => {
|
|
1102
|
+
const calls = [];
|
|
1103
|
+
const originalFetch = globalThis.fetch;
|
|
1104
|
+
|
|
1105
|
+
globalThis.fetch = async (url, options) => {
|
|
1106
|
+
calls.push({ url: String(url), options, body: options?.body ? JSON.parse(options.body) : undefined });
|
|
1107
|
+
if (calls.length === 1) return jsonResponse({ accepted: true }, 202);
|
|
1108
|
+
return jsonResponse({ status: 'COMPLETED' }, 200);
|
|
1109
|
+
};
|
|
1110
|
+
|
|
1111
|
+
try {
|
|
1112
|
+
const client = new DataspaceNodeClient({ baseUrl: 'http://localhost:3000', bearerToken: 'tok' });
|
|
1113
|
+
const result = await client.activateOrganizationInGatewaySimple({
|
|
1114
|
+
jurisdiction: 'ES',
|
|
1115
|
+
sector: 'health-care',
|
|
1116
|
+
vpToken: 'vp-001',
|
|
1117
|
+
serviceProviderUrl: 'https://api.acme.org',
|
|
1118
|
+
controllerEmail: 'admin@acme.org',
|
|
1119
|
+
controllerRole: 'ISCO-08|1112',
|
|
1120
|
+
numberOfMembers: 3,
|
|
1121
|
+
timeoutSeconds: 5,
|
|
1122
|
+
intervalSeconds: 1,
|
|
1123
|
+
});
|
|
1124
|
+
|
|
1125
|
+
assert.equal(result.poll.status, 200);
|
|
1126
|
+
assert.equal(calls[0].url, 'http://localhost:3000/host/cds-ES/v1/health-care/registry/org.schema/Organization/_activate');
|
|
1127
|
+
const claims = calls[0].body?.body?.data?.[0]?.meta?.claims || {};
|
|
1128
|
+
assert.equal(claims.vp_token, 'vp-001');
|
|
1129
|
+
assert.equal(claims['org.schema.Organization.numberOfEmployees'], 3);
|
|
1130
|
+
assert.equal(claims['org.schema.Service.category'], 'health-care');
|
|
1131
|
+
assert.equal(claims['org.schema.Service.identifier'], 'did:web:api.acme.org');
|
|
1132
|
+
assert.equal(claims['org.schema.Service.url'], 'https://api.acme.org');
|
|
1133
|
+
assert.equal(claims['org.schema.Person.email'], 'admin@acme.org');
|
|
1134
|
+
assert.equal(claims['org.schema.Person.hasOccupation'], 'ISCO-08|1112');
|
|
1135
|
+
} finally {
|
|
1136
|
+
globalThis.fetch = originalFetch;
|
|
1137
|
+
}
|
|
1138
|
+
});
|
|
1139
|
+
|
|
1140
|
+
test('confirmLegalOrganizationOrderSimple builds order payload from single object', async () => {
|
|
1141
|
+
const calls = [];
|
|
1142
|
+
const originalFetch = globalThis.fetch;
|
|
1143
|
+
|
|
1144
|
+
globalThis.fetch = async (url, options) => {
|
|
1145
|
+
calls.push({ url: String(url), options, body: options?.body ? JSON.parse(options.body) : undefined });
|
|
1146
|
+
if (calls.length === 1) return jsonResponse({ accepted: true }, 202);
|
|
1147
|
+
return jsonResponse({ status: 'COMPLETED' }, 200);
|
|
1148
|
+
};
|
|
1149
|
+
|
|
1150
|
+
try {
|
|
1151
|
+
const client = new DataspaceNodeClient({ baseUrl: 'http://localhost:3000', bearerToken: 'tok' });
|
|
1152
|
+
const result = await client.confirmLegalOrganizationOrderSimple({
|
|
1153
|
+
jurisdiction: 'ES',
|
|
1154
|
+
sector: 'health-care',
|
|
1155
|
+
offerId: 'urn:offer:xyz',
|
|
1156
|
+
timeoutSeconds: 5,
|
|
1157
|
+
intervalSeconds: 1,
|
|
1158
|
+
});
|
|
1159
|
+
|
|
1160
|
+
assert.equal(result.poll.status, 200);
|
|
1161
|
+
assert.equal(calls[0].url, 'http://localhost:3000/host/cds-ES/v1/health-care/registry/org.schema/Order/_batch');
|
|
1162
|
+
const claims = calls[0].body?.body?.data?.[0]?.meta?.claims || {};
|
|
1163
|
+
assert.equal(claims['Order.acceptedOffer.identifier'], 'urn:offer:xyz');
|
|
1164
|
+
} finally {
|
|
1165
|
+
globalThis.fetch = originalFetch;
|
|
1166
|
+
}
|
|
1167
|
+
});
|
|
1168
|
+
|
|
1169
|
+
test('constructor default ctx allows onboarding calls without passing ctx each time', async () => {
|
|
1170
|
+
const calls = [];
|
|
1171
|
+
const originalFetch = globalThis.fetch;
|
|
1172
|
+
globalThis.fetch = async (url, options) => {
|
|
1173
|
+
calls.push({ url: String(url), options, body: options?.body ? JSON.parse(options.body) : undefined });
|
|
1174
|
+
if (calls.length === 1) return jsonResponse({ accepted: true }, 202); // activate submit
|
|
1175
|
+
if (calls.length === 2) {
|
|
1176
|
+
return jsonResponse({
|
|
1177
|
+
body: { data: [{ meta: { claims: { 'org.schema.Offer.identifier': 'urn:offer:def' } } }] },
|
|
1178
|
+
}, 200); // activate poll
|
|
1179
|
+
}
|
|
1180
|
+
if (calls.length === 3) return jsonResponse({ accepted: true }, 202); // order submit
|
|
1181
|
+
return jsonResponse({ status: 'COMPLETED' }, 200); // order poll
|
|
1182
|
+
};
|
|
1183
|
+
|
|
1184
|
+
try {
|
|
1185
|
+
const client = new DataspaceNodeClient({
|
|
1186
|
+
baseUrl: 'http://localhost:3000',
|
|
1187
|
+
bearerToken: 'tok',
|
|
1188
|
+
ctx: { tenantId: 'acme', jurisdiction: 'ES', sector: 'health-care' },
|
|
1189
|
+
});
|
|
1190
|
+
const activation = await client.activateOrganizationInGatewayFromIcaProof(undefined, { vpToken: 'vp-ctx-001' }, { timeoutMs: 5000, intervalMs: 1 });
|
|
1191
|
+
const offerId = client.getOfferIdFromResponse(activation);
|
|
1192
|
+
assert.equal(offerId, 'urn:offer:def');
|
|
1193
|
+
const legalOrgOrder = await client.confirmLegalOrganizationOrderSimple({
|
|
1194
|
+
jurisdiction: 'ES',
|
|
1195
|
+
sector: 'health-care',
|
|
1196
|
+
offerId: offerId,
|
|
1197
|
+
timeoutSeconds: 5,
|
|
1198
|
+
intervalSeconds: 1,
|
|
1199
|
+
});
|
|
1200
|
+
assert.equal(legalOrgOrder.poll.status, 200);
|
|
1201
|
+
assert.equal(calls[0].url, 'http://localhost:3000/host/cds-ES/v1/health-care/registry/org.schema/Organization/_activate');
|
|
1202
|
+
assert.equal(calls[2].url, 'http://localhost:3000/host/cds-ES/v1/health-care/registry/org.schema/Order/_batch');
|
|
1203
|
+
} finally {
|
|
1204
|
+
globalThis.fetch = originalFetch;
|
|
1205
|
+
}
|
|
1206
|
+
});
|
|
1207
|
+
|
|
1208
|
+
test('setTenantId/setJurisdiction/setSector configure default ctx for simple methods', async () => {
|
|
1209
|
+
const calls = [];
|
|
1210
|
+
const originalFetch = globalThis.fetch;
|
|
1211
|
+
|
|
1212
|
+
globalThis.fetch = async (url, options) => {
|
|
1213
|
+
calls.push({ url: String(url), options });
|
|
1214
|
+
if (calls.length === 1) return jsonResponse({ accepted: true }, 202);
|
|
1215
|
+
if (calls.length === 2) {
|
|
1216
|
+
return jsonResponse({
|
|
1217
|
+
body: {
|
|
1218
|
+
data: [{
|
|
1219
|
+
meta: { claims: { 'org.schema.Offer.identifier': 'offer-setter-001' } },
|
|
1220
|
+
}],
|
|
1221
|
+
},
|
|
1222
|
+
}, 200);
|
|
1223
|
+
}
|
|
1224
|
+
if (calls.length === 3) return jsonResponse({ accepted: true }, 202);
|
|
1225
|
+
return jsonResponse({ status: 'COMPLETED' }, 200);
|
|
1226
|
+
};
|
|
1227
|
+
|
|
1228
|
+
try {
|
|
1229
|
+
const client = new DataspaceNodeClient({ baseUrl: 'http://localhost:3000', bearerToken: 'id-token' });
|
|
1230
|
+
client.setTenantId('acme').setJurisdiction('ES').setSector('health-care');
|
|
1231
|
+
|
|
1232
|
+
const activation = await client.activateOrganizationInGatewaySimple({
|
|
1233
|
+
vpToken: 'vp-token-001',
|
|
1234
|
+
serviceProviderDidWeb: 'did:web:api.acme.org',
|
|
1235
|
+
controllerEmail: 'owner@acme.org',
|
|
1236
|
+
controllerRole: 'ISCO-08|1112',
|
|
1237
|
+
});
|
|
1238
|
+
const offerId = client.getOfferIdFromResponse(activation);
|
|
1239
|
+
assert.equal(offerId, 'offer-setter-001');
|
|
1240
|
+
|
|
1241
|
+
const order = await client.confirmLegalOrganizationOrderSimple({
|
|
1242
|
+
offerId,
|
|
1243
|
+
});
|
|
1244
|
+
assert.equal(order.poll.status, 200);
|
|
1245
|
+
assert.equal(calls[0].url, 'http://localhost:3000/host/cds-ES/v1/health-care/registry/org.schema/Organization/_activate');
|
|
1246
|
+
assert.equal(calls[2].url, 'http://localhost:3000/host/cds-ES/v1/health-care/registry/org.schema/Order/_batch');
|
|
1247
|
+
} finally {
|
|
1248
|
+
globalThis.fetch = originalFetch;
|
|
1249
|
+
}
|
|
1250
|
+
});
|
|
1251
|
+
|
|
1252
|
+
test('setContextOrg configures default ctx for simple methods', async () => {
|
|
1253
|
+
const calls = [];
|
|
1254
|
+
const originalFetch = globalThis.fetch;
|
|
1255
|
+
|
|
1256
|
+
globalThis.fetch = async (url, options) => {
|
|
1257
|
+
calls.push({ url: String(url), options });
|
|
1258
|
+
if (calls.length === 1) return jsonResponse({ accepted: true }, 202);
|
|
1259
|
+
if (calls.length === 2) return jsonResponse({ body: { data: [{ meta: { claims: { 'org.schema.Offer.identifier': 'offer-orgctx-001' } } }] } }, 200);
|
|
1260
|
+
return jsonResponse({ status: 'COMPLETED' }, 200);
|
|
1261
|
+
};
|
|
1262
|
+
|
|
1263
|
+
try {
|
|
1264
|
+
const client = new DataspaceNodeClient({ baseUrl: 'http://localhost:3000', bearerToken: 'id-token' });
|
|
1265
|
+
client.setContextOrg({ tenantId: 'acme', jurisdiction: 'ES', sector: 'health-care' });
|
|
1266
|
+
const activation = await client.activateOrganizationInGatewaySimple({
|
|
1267
|
+
vpToken: 'vp-token-001',
|
|
1268
|
+
serviceProviderUrl: 'portal.acme.org',
|
|
1269
|
+
controllerTelephone: 'tel:+34600111222',
|
|
1270
|
+
controllerRole: 'ISCO-08|1112',
|
|
1271
|
+
});
|
|
1272
|
+
assert.equal(client.getOfferIdFromResponse(activation), 'offer-orgctx-001');
|
|
1273
|
+
assert.equal(calls[0].url, 'http://localhost:3000/host/cds-ES/v1/health-care/registry/org.schema/Organization/_activate');
|
|
1274
|
+
} finally {
|
|
1275
|
+
globalThis.fetch = originalFetch;
|
|
1276
|
+
}
|
|
1277
|
+
});
|
|
1278
|
+
|
|
1279
|
+
test('setDefaultTimeoutSeconds and setDefaultIntervalSeconds apply to simple methods', async () => {
|
|
1280
|
+
const calls = [];
|
|
1281
|
+
const originalFetch = globalThis.fetch;
|
|
1282
|
+
|
|
1283
|
+
globalThis.fetch = async (url, options) => {
|
|
1284
|
+
calls.push({ url: String(url), options });
|
|
1285
|
+
if (calls.length === 1) return jsonResponse({ accepted: true }, 202);
|
|
1286
|
+
if (calls.length === 2) return jsonResponse({ status: 'PENDING' }, 202);
|
|
1287
|
+
return jsonResponse({ status: 'COMPLETED' }, 200);
|
|
1288
|
+
};
|
|
1289
|
+
|
|
1290
|
+
try {
|
|
1291
|
+
const client = new DataspaceNodeClient({ baseUrl: 'http://localhost:3000', bearerToken: 'id-token' });
|
|
1292
|
+
client
|
|
1293
|
+
.setContextOrg({ tenantId: 'acme', jurisdiction: 'ES', sector: 'health-care' })
|
|
1294
|
+
.setDefaultTimeoutSeconds(5)
|
|
1295
|
+
.setDefaultIntervalSeconds(0.001);
|
|
1296
|
+
|
|
1297
|
+
const result = await client.activateOrganizationInGatewaySimple({
|
|
1298
|
+
vpToken: 'vp-token-001',
|
|
1299
|
+
serviceProviderDidWeb: 'did:web:api.acme.org',
|
|
1300
|
+
controllerEmail: 'owner@acme.org',
|
|
1301
|
+
controllerRole: 'ISCO-08|1112',
|
|
1302
|
+
});
|
|
1303
|
+
|
|
1304
|
+
assert.equal(result.poll.status, 200);
|
|
1305
|
+
assert.equal(calls.length, 3);
|
|
1306
|
+
} finally {
|
|
1307
|
+
globalThis.fetch = originalFetch;
|
|
1308
|
+
}
|
|
1309
|
+
});
|
|
1310
|
+
|
|
1311
|
+
test('activateOrganizationInGatewaySimple throws on business-level error in DIDComm entry', async () => {
|
|
1312
|
+
const calls = [];
|
|
1313
|
+
const originalFetch = globalThis.fetch;
|
|
1314
|
+
|
|
1315
|
+
globalThis.fetch = async (url, options) => {
|
|
1316
|
+
calls.push({ url: String(url), options });
|
|
1317
|
+
if (calls.length === 1) return jsonResponse({ accepted: true }, 202);
|
|
1318
|
+
return jsonResponse({
|
|
1319
|
+
data: [{
|
|
1320
|
+
type: 'Organization-activation-request-v1.0',
|
|
1321
|
+
response: {
|
|
1322
|
+
status: '400',
|
|
1323
|
+
outcome: { issue: [{ diagnostics: 'Missing ICA-issued organization credential.' }] },
|
|
1324
|
+
},
|
|
1325
|
+
}],
|
|
1326
|
+
resourceType: 'Bundle',
|
|
1327
|
+
type: 'batch-response',
|
|
1328
|
+
total: 1,
|
|
1329
|
+
}, 200);
|
|
1330
|
+
};
|
|
1331
|
+
|
|
1332
|
+
try {
|
|
1333
|
+
const client = new DataspaceNodeClient({ baseUrl: 'http://localhost:3000' });
|
|
1334
|
+
await assert.rejects(
|
|
1335
|
+
() => client.activateOrganizationInGatewaySimple({
|
|
1336
|
+
jurisdiction: 'ES',
|
|
1337
|
+
sector: 'test',
|
|
1338
|
+
vpToken: 'vp-token-001',
|
|
1339
|
+
serviceProviderDidWeb: 'did:web:api.acme.org',
|
|
1340
|
+
controllerEmail: 'owner@acme.org',
|
|
1341
|
+
controllerRole: 'ISCO-08|1112',
|
|
1342
|
+
}),
|
|
1343
|
+
/Missing ICA-issued organization credential\./,
|
|
1344
|
+
);
|
|
1345
|
+
} finally {
|
|
1346
|
+
globalThis.fetch = originalFetch;
|
|
1347
|
+
}
|
|
1348
|
+
});
|
|
1349
|
+
|
|
1350
|
+
test('activateOrganizationInGatewaySimple requires controller identity and role', async () => {
|
|
1351
|
+
const client = new DataspaceNodeClient({ baseUrl: 'http://localhost:3000' });
|
|
1352
|
+
await assert.rejects(
|
|
1353
|
+
() => client.activateOrganizationInGatewaySimple({
|
|
1354
|
+
jurisdiction: 'ES',
|
|
1355
|
+
sector: 'test',
|
|
1356
|
+
vpToken: 'vp-token-001',
|
|
1357
|
+
serviceProviderDidWeb: 'did:web:api.acme.org',
|
|
1358
|
+
controllerRole: 'ISCO-08|1112',
|
|
1359
|
+
}),
|
|
1360
|
+
/requires controllerEmail or controllerTelephone/i,
|
|
1361
|
+
);
|
|
1362
|
+
await assert.rejects(
|
|
1363
|
+
() => client.activateOrganizationInGatewaySimple({
|
|
1364
|
+
jurisdiction: 'ES',
|
|
1365
|
+
sector: 'test',
|
|
1366
|
+
vpToken: 'vp-token-001',
|
|
1367
|
+
serviceProviderDidWeb: 'did:web:api.acme.org',
|
|
1368
|
+
controllerEmail: 'admin@acme.org',
|
|
1369
|
+
controllerRole: '',
|
|
1370
|
+
}),
|
|
1371
|
+
/requires controllerRole/i,
|
|
1372
|
+
);
|
|
1373
|
+
await assert.rejects(
|
|
1374
|
+
() => client.activateOrganizationInGatewaySimple({
|
|
1375
|
+
jurisdiction: 'ES',
|
|
1376
|
+
sector: 'test',
|
|
1377
|
+
vpToken: 'vp-token-001',
|
|
1378
|
+
controllerEmail: 'admin@acme.org',
|
|
1379
|
+
controllerRole: 'ISCO-08|1112',
|
|
1380
|
+
}),
|
|
1381
|
+
/requires serviceProviderDidWeb or serviceProviderUrl/i,
|
|
1382
|
+
);
|
|
1383
|
+
});
|