@learncard/network-brain-service 3.19.0 → 3.19.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/.env.example +34 -2
- package/CHANGELOG.md +38 -0
- package/README.md +42 -0
- package/dist/accesslayer/consentflowcontract/relationships/create.d.ts.map +1 -1
- package/dist/accesslayer/consentflowcontract/relationships/update.d.ts.map +1 -1
- package/dist/accesslayer/presentation/read.d.ts.map +1 -1
- package/dist/accesslayer/presentation/relationships/create.d.ts +2 -2
- package/dist/accesslayer/presentation/relationships/create.d.ts.map +1 -1
- package/dist/accesslayer/presentation/relationships/read.d.ts.map +1 -1
- package/dist/accesslayer/share-link/abandon.d.ts +3 -0
- package/dist/accesslayer/share-link/abandon.d.ts.map +1 -0
- package/dist/accesslayer/share-link/cleanup.d.ts +6 -0
- package/dist/accesslayer/share-link/cleanup.d.ts.map +1 -0
- package/dist/accesslayer/share-link/errors.d.ts +8 -0
- package/dist/accesslayer/share-link/errors.d.ts.map +1 -0
- package/dist/accesslayer/share-link/finalize.d.ts +3 -0
- package/dist/accesslayer/share-link/finalize.d.ts.map +1 -0
- package/dist/accesslayer/share-link/helpers.d.ts +56 -0
- package/dist/accesslayer/share-link/helpers.d.ts.map +1 -0
- package/dist/accesslayer/share-link/index.d.ts +12 -0
- package/dist/accesslayer/share-link/index.d.ts.map +1 -0
- package/dist/accesslayer/share-link/list.d.ts +5 -0
- package/dist/accesslayer/share-link/list.d.ts.map +1 -0
- package/dist/accesslayer/share-link/read.d.ts +5 -0
- package/dist/accesslayer/share-link/read.d.ts.map +1 -0
- package/dist/accesslayer/share-link/receipt.d.ts +11 -0
- package/dist/accesslayer/share-link/receipt.d.ts.map +1 -0
- package/dist/accesslayer/share-link/recovery.d.ts +6 -0
- package/dist/accesslayer/share-link/recovery.d.ts.map +1 -0
- package/dist/accesslayer/share-link/reserve.d.ts +4 -0
- package/dist/accesslayer/share-link/reserve.d.ts.map +1 -0
- package/dist/accesslayer/share-link/revoke.d.ts +3 -0
- package/dist/accesslayer/share-link/revoke.d.ts.map +1 -0
- package/dist/accesslayer/share-link/transaction-execution.d.ts +18 -0
- package/dist/accesslayer/share-link/transaction-execution.d.ts.map +1 -0
- package/dist/accesslayer/share-link/transaction.d.ts +15 -0
- package/dist/accesslayer/share-link/transaction.d.ts.map +1 -0
- package/dist/accesslayer/share-link/types.d.ts +370 -0
- package/dist/accesslayer/share-link/types.d.ts.map +1 -0
- package/dist/app.d.ts +4 -0
- package/dist/app.d.ts.map +1 -1
- package/dist/config/environment.d.ts +9 -0
- package/dist/config/environment.d.ts.map +1 -1
- package/dist/constants/auth-grant.d.ts +2 -0
- package/dist/constants/auth-grant.d.ts.map +1 -1
- package/dist/helpers/boost.helpers.d.ts.map +1 -1
- package/dist/helpers/credentialSubject.helpers.d.ts +3 -0
- package/dist/helpers/credentialSubject.helpers.d.ts.map +1 -0
- package/dist/helpers/credentialSubject.helpers.test.d.ts +2 -0
- package/dist/helpers/credentialSubject.helpers.test.d.ts.map +1 -0
- package/dist/helpers/inbox.helpers.d.ts.map +1 -1
- package/dist/helpers/notificationMessages.d.ts +1 -1
- package/dist/helpers/notificationMessages.d.ts.map +1 -1
- package/dist/helpers/presentation-metadata.d.ts +4 -0
- package/dist/helpers/presentation-metadata.d.ts.map +1 -0
- package/dist/helpers/presentation.helpers.d.ts +1 -1
- package/dist/helpers/presentation.helpers.d.ts.map +1 -1
- package/dist/helpers/profile.helpers.d.ts +1 -0
- package/dist/helpers/profile.helpers.d.ts.map +1 -1
- package/dist/helpers/share-content-client/adapters.d.ts +10 -0
- package/dist/helpers/share-content-client/adapters.d.ts.map +1 -0
- package/dist/helpers/share-content-client/canonical.d.ts +7 -0
- package/dist/helpers/share-content-client/canonical.d.ts.map +1 -0
- package/dist/helpers/share-content-client/client.d.ts +5 -0
- package/dist/helpers/share-content-client/client.d.ts.map +1 -0
- package/dist/helpers/share-content-client/config.d.ts +11 -0
- package/dist/helpers/share-content-client/config.d.ts.map +1 -0
- package/dist/helpers/share-content-client/index.d.ts +6 -0
- package/dist/helpers/share-content-client/index.d.ts.map +1 -0
- package/dist/helpers/share-content-client/responses.d.ts +16 -0
- package/dist/helpers/share-content-client/responses.d.ts.map +1 -0
- package/dist/helpers/share-content-client/types.d.ts +145 -0
- package/dist/helpers/share-content-client/types.d.ts.map +1 -0
- package/dist/helpers/share-link-coordinator/budget-helpers.d.ts +5 -0
- package/dist/helpers/share-link-coordinator/budget-helpers.d.ts.map +1 -0
- package/dist/helpers/share-link-coordinator/cleanup-runner.d.ts +3 -0
- package/dist/helpers/share-link-coordinator/cleanup-runner.d.ts.map +1 -0
- package/dist/helpers/share-link-coordinator/coordinator.d.ts +4 -0
- package/dist/helpers/share-link-coordinator/coordinator.d.ts.map +1 -0
- package/dist/helpers/share-link-coordinator/index.d.ts +5 -0
- package/dist/helpers/share-link-coordinator/index.d.ts.map +1 -0
- package/dist/helpers/share-link-coordinator/recovery-runner.d.ts +9 -0
- package/dist/helpers/share-link-coordinator/recovery-runner.d.ts.map +1 -0
- package/dist/helpers/share-link-coordinator/runtime.d.ts +11 -0
- package/dist/helpers/share-link-coordinator/runtime.d.ts.map +1 -0
- package/dist/helpers/share-link-coordinator/stat.d.ts +4 -0
- package/dist/helpers/share-link-coordinator/stat.d.ts.map +1 -0
- package/dist/helpers/share-link-coordinator/types.d.ts +160 -0
- package/dist/helpers/share-link-coordinator/types.d.ts.map +1 -0
- package/dist/helpers/share-link-lifecycle/index.d.ts +5 -0
- package/dist/helpers/share-link-lifecycle/index.d.ts.map +1 -0
- package/dist/helpers/share-link-lifecycle/lease.helpers.d.ts +4 -0
- package/dist/helpers/share-link-lifecycle/lease.helpers.d.ts.map +1 -0
- package/dist/helpers/share-link-lifecycle/recovery.helpers.d.ts +21 -0
- package/dist/helpers/share-link-lifecycle/recovery.helpers.d.ts.map +1 -0
- package/dist/helpers/share-link-lifecycle/request-hash.helpers.d.ts +9 -0
- package/dist/helpers/share-link-lifecycle/request-hash.helpers.d.ts.map +1 -0
- package/dist/helpers/share-link-lifecycle/types.d.ts +14 -0
- package/dist/helpers/share-link-lifecycle/types.d.ts.map +1 -0
- package/dist/helpers/share-link-maintenance/budget.d.ts +12 -0
- package/dist/helpers/share-link-maintenance/budget.d.ts.map +1 -0
- package/dist/helpers/share-link-maintenance/config.d.ts +48 -0
- package/dist/helpers/share-link-maintenance/config.d.ts.map +1 -0
- package/dist/helpers/share-link-maintenance/index.d.ts +8 -0
- package/dist/helpers/share-link-maintenance/index.d.ts.map +1 -0
- package/dist/helpers/share-link-maintenance/runner.d.ts +19 -0
- package/dist/helpers/share-link-maintenance/runner.d.ts.map +1 -0
- package/dist/helpers/share-link-maintenance/runtime.d.ts +36 -0
- package/dist/helpers/share-link-maintenance/runtime.d.ts.map +1 -0
- package/dist/helpers/share-link-maintenance/scheduler.d.ts +18 -0
- package/dist/helpers/share-link-maintenance/scheduler.d.ts.map +1 -0
- package/dist/helpers/share-link-maintenance/telemetry.d.ts +5 -0
- package/dist/helpers/share-link-maintenance/telemetry.d.ts.map +1 -0
- package/dist/helpers/share-link-maintenance/types.d.ts +53 -0
- package/dist/helpers/share-link-maintenance/types.d.ts.map +1 -0
- package/dist/helpers/share-link-owner/config.d.ts +16 -0
- package/dist/helpers/share-link-owner/config.d.ts.map +1 -0
- package/dist/helpers/share-link-owner/diagnostics.d.ts +26 -0
- package/dist/helpers/share-link-owner/diagnostics.d.ts.map +1 -0
- package/dist/helpers/share-link-owner/lazy-initializer.d.ts +2 -0
- package/dist/helpers/share-link-owner/lazy-initializer.d.ts.map +1 -0
- package/dist/helpers/share-link-owner-projection.d.ts +4 -0
- package/dist/helpers/share-link-owner-projection.d.ts.map +1 -0
- package/dist/helpers/share-link-passcode-abuse.d.ts +2 -0
- package/dist/helpers/share-link-passcode-abuse.d.ts.map +1 -0
- package/dist/helpers/share-link-passcode.d.ts +6 -0
- package/dist/helpers/share-link-passcode.d.ts.map +1 -0
- package/dist/helpers/share-link-policy/index.d.ts +4 -0
- package/dist/helpers/share-link-policy/index.d.ts.map +1 -0
- package/dist/helpers/share-link-policy/production.d.ts +13 -0
- package/dist/helpers/share-link-policy/production.d.ts.map +1 -0
- package/dist/helpers/share-link-policy/resolver.d.ts +6 -0
- package/dist/helpers/share-link-policy/resolver.d.ts.map +1 -0
- package/dist/helpers/share-link-policy/types.d.ts +16 -0
- package/dist/helpers/share-link-policy/types.d.ts.map +1 -0
- package/dist/helpers/share-link-public/config.d.ts +13 -0
- package/dist/helpers/share-link-public/config.d.ts.map +1 -0
- package/dist/helpers/share-link-view-notification.d.ts +2 -0
- package/dist/helpers/share-link-view-notification.d.ts.map +1 -0
- package/dist/index.js +2 -2
- package/dist/models/Presentation.d.ts +2 -0
- package/dist/models/Presentation.d.ts.map +1 -1
- package/dist/models/Profile.d.ts +2 -0
- package/dist/models/Profile.d.ts.map +1 -1
- package/dist/models/ShareLink.d.ts +44 -0
- package/dist/models/ShareLink.d.ts.map +1 -0
- package/dist/models/ShareViewReceipt.d.ts +22 -0
- package/dist/models/ShareViewReceipt.d.ts.map +1 -0
- package/dist/models/index.d.ts +1 -0
- package/dist/models/index.d.ts.map +1 -1
- package/dist/models/share-link-constraints.d.ts +3 -0
- package/dist/models/share-link-constraints.d.ts.map +1 -0
- package/dist/public-share-link-http.d.ts +4 -0
- package/dist/public-share-link-http.d.ts.map +1 -0
- package/dist/routes/app-store.d.ts.map +1 -1
- package/dist/routes/boosts.d.ts.map +1 -1
- package/dist/routes/contracts.d.ts +2 -2
- package/dist/routes/contracts.d.ts.map +1 -1
- package/dist/routes/credentials.d.ts +1 -1
- package/dist/routes/index.d.ts +153 -0
- package/dist/routes/index.d.ts.map +1 -1
- package/dist/routes/presentations.d.ts +1 -0
- package/dist/routes/presentations.d.ts.map +1 -1
- package/dist/routes/profile-manager.d.ts.map +1 -1
- package/dist/routes/profiles.d.ts.map +1 -1
- package/dist/routes/public-share-links.d.ts +230 -0
- package/dist/routes/public-share-links.d.ts.map +1 -0
- package/dist/routes/share-links.d.ts +844 -0
- package/dist/routes/share-links.d.ts.map +1 -0
- package/dist/services/delivery/adapters/postmark.adapter.d.ts.map +1 -1
- package/dist/types/consentflowcontract.d.ts +1 -1
- package/lambda.ts +4 -1
- package/package.json +22 -16
- package/scripts/seed-demo-persona.ts +425 -0
- package/serverless.yml +36 -0
- package/shareLinkMaintenanceLambda.ts +49 -0
- package/src/accesslayer/consentflowcontract/relationships/create.ts +9 -16
- package/src/accesslayer/consentflowcontract/relationships/update.ts +15 -28
- package/src/accesslayer/presentation/read.ts +57 -25
- package/src/accesslayer/presentation/relationships/create.ts +54 -11
- package/src/accesslayer/presentation/relationships/read.ts +21 -6
- package/src/accesslayer/share-link/abandon.ts +100 -0
- package/src/accesslayer/share-link/cleanup.ts +271 -0
- package/src/accesslayer/share-link/errors.ts +32 -0
- package/src/accesslayer/share-link/finalize.ts +265 -0
- package/src/accesslayer/share-link/helpers.ts +419 -0
- package/src/accesslayer/share-link/index.ts +11 -0
- package/src/accesslayer/share-link/list.ts +162 -0
- package/src/accesslayer/share-link/read.ts +107 -0
- package/src/accesslayer/share-link/receipt.ts +548 -0
- package/src/accesslayer/share-link/recovery.ts +545 -0
- package/src/accesslayer/share-link/reserve.ts +684 -0
- package/src/accesslayer/share-link/revoke.ts +197 -0
- package/src/accesslayer/share-link/transaction-execution.ts +72 -0
- package/src/accesslayer/share-link/transaction.ts +126 -0
- package/src/accesslayer/share-link/types.ts +601 -0
- package/src/app.ts +6 -0
- package/src/config/environment.ts +14 -0
- package/src/constants/auth-grant.ts +5 -0
- package/src/docker-entry.ts +57 -0
- package/src/helpers/boost.helpers.ts +4 -31
- package/src/helpers/credentialSubject.helpers.test.ts +114 -0
- package/src/helpers/credentialSubject.helpers.ts +40 -0
- package/src/helpers/inbox.helpers.ts +7 -4
- package/src/helpers/notificationMessages.ts +18 -1
- package/src/helpers/presentation-metadata.ts +41 -0
- package/src/helpers/presentation.helpers.ts +27 -20
- package/src/helpers/profile.helpers.ts +7 -0
- package/src/helpers/share-content-client/adapters.ts +55 -0
- package/src/helpers/share-content-client/canonical.ts +25 -0
- package/src/helpers/share-content-client/client.ts +440 -0
- package/src/helpers/share-content-client/config.ts +189 -0
- package/src/helpers/share-content-client/index.ts +27 -0
- package/src/helpers/share-content-client/responses.ts +273 -0
- package/src/helpers/share-content-client/types.ts +253 -0
- package/src/helpers/share-link-coordinator/budget-helpers.ts +53 -0
- package/src/helpers/share-link-coordinator/cleanup-runner.ts +286 -0
- package/src/helpers/share-link-coordinator/coordinator.ts +714 -0
- package/src/helpers/share-link-coordinator/index.ts +8 -0
- package/src/helpers/share-link-coordinator/recovery-runner.ts +428 -0
- package/src/helpers/share-link-coordinator/runtime.ts +88 -0
- package/src/helpers/share-link-coordinator/stat.ts +30 -0
- package/src/helpers/share-link-coordinator/types.ts +353 -0
- package/src/helpers/share-link-lifecycle/index.ts +4 -0
- package/src/helpers/share-link-lifecycle/lease.helpers.ts +35 -0
- package/src/helpers/share-link-lifecycle/recovery.helpers.ts +173 -0
- package/src/helpers/share-link-lifecycle/request-hash.helpers.ts +130 -0
- package/src/helpers/share-link-lifecycle/types.ts +58 -0
- package/src/helpers/share-link-maintenance/budget.ts +63 -0
- package/src/helpers/share-link-maintenance/config.ts +312 -0
- package/src/helpers/share-link-maintenance/index.ts +24 -0
- package/src/helpers/share-link-maintenance/runner.ts +223 -0
- package/src/helpers/share-link-maintenance/runtime.ts +334 -0
- package/src/helpers/share-link-maintenance/scheduler.ts +126 -0
- package/src/helpers/share-link-maintenance/telemetry.ts +70 -0
- package/src/helpers/share-link-maintenance/types.ts +89 -0
- package/src/helpers/share-link-owner/config.ts +48 -0
- package/src/helpers/share-link-owner/diagnostics.ts +89 -0
- package/src/helpers/share-link-owner/lazy-initializer.ts +29 -0
- package/src/helpers/share-link-owner-projection.ts +52 -0
- package/src/helpers/share-link-passcode-abuse.ts +36 -0
- package/src/helpers/share-link-passcode.ts +47 -0
- package/src/helpers/share-link-policy/index.ts +7 -0
- package/src/helpers/share-link-policy/production.ts +88 -0
- package/src/helpers/share-link-policy/resolver.ts +108 -0
- package/src/helpers/share-link-policy/types.ts +48 -0
- package/src/helpers/share-link-public/config.ts +41 -0
- package/src/helpers/share-link-view-notification.ts +18 -0
- package/src/models/Presentation.ts +2 -2
- package/src/models/Profile.ts +3 -2
- package/src/models/ShareLink.ts +130 -0
- package/src/models/ShareViewReceipt.ts +69 -0
- package/src/models/index.ts +2 -0
- package/src/models/share-link-constraints.ts +93 -0
- package/src/public-share-link-http.ts +49 -0
- package/src/routes/app-store.ts +2 -11
- package/src/routes/boosts.ts +2 -11
- package/src/routes/contracts.ts +2 -11
- package/src/routes/index.ts +139 -44
- package/src/routes/presentations.ts +19 -3
- package/src/routes/profile-manager.ts +10 -2
- package/src/routes/profiles.ts +17 -6
- package/src/routes/public-share-links.ts +814 -0
- package/src/routes/share-links.ts +640 -0
- package/src/services/delivery/adapters/postmark.adapter.ts +4 -1
- package/test/dids.spec.ts +10 -1
- package/test/guardian-gated-routes.spec.ts +13 -0
- package/test/helpers/share-link-fixtures.ts +94 -0
- package/test/managed-recipient-inbox.spec.ts +130 -0
- package/test/notificationMessages.spec.ts +13 -0
- package/test/openapi.spec.ts +20 -0
- package/test/presentations.spec.ts +54 -6
- package/test/profiles.spec.ts +67 -1
- package/test/share-content-client.contract.spec.ts +365 -0
- package/test/share-content-client.unit.spec.ts +462 -0
- package/test/share-link-abuse.unit.spec.ts +65 -0
- package/test/share-link-coordinator.neo4j.spec.ts +684 -0
- package/test/share-link-coordinator.unit.spec.ts +990 -0
- package/test/share-link-dependency-diagnostics.unit.spec.ts +127 -0
- package/test/share-link-lifecycle.neo4j.spec.ts +1268 -0
- package/test/share-link-lifecycle.unit.spec.ts +205 -0
- package/test/share-link-list-cursor.unit.spec.ts +86 -0
- package/test/share-link-list.neo4j.spec.ts +324 -0
- package/test/share-link-maintenance.neo4j.spec.ts +355 -0
- package/test/share-link-maintenance.unit.spec.ts +1113 -0
- package/test/share-link-owner-policy.unit.spec.ts +210 -0
- package/test/share-link-owner-route.unit.spec.ts +752 -0
- package/test/share-link-owner-runtime.unit.spec.ts +102 -0
- package/test/share-link-passcode.unit.spec.ts +29 -0
- package/test/share-link-policy.unit.spec.ts +135 -0
- package/test/share-link-presentation-metadata.unit.spec.ts +49 -0
- package/test/share-link-production-policy.unit.spec.ts +77 -0
- package/test/share-link-public-config.unit.spec.ts +64 -0
- package/test/share-link-public-http-adapter.unit.spec.ts +315 -0
- package/test/share-link-public-route.unit.spec.ts +800 -0
- package/test/share-link-receipt.neo4j.spec.ts +943 -0
- package/test/share-link-recovery.neo4j.spec.ts +935 -0
- package/test/share-link-recovery.unit.spec.ts +726 -0
- package/vitest.config.ts +1 -0
- package/vitest.share-links-neo4j.config.ts +19 -0
- package/vitest.share-links-unit.config.ts +28 -0
|
@@ -225,6 +225,19 @@ describe('Guardian-approved consent mutations', () => {
|
|
|
225
225
|
expect(await ConsentFlowTransaction.findMany({ where: {} })).toEqual([]);
|
|
226
226
|
});
|
|
227
227
|
|
|
228
|
+
it('does not guardian-gate a managed service profile', async () => {
|
|
229
|
+
await Profile.update({ isServiceProfile: true }, { where: { profileId: 'child-user' } });
|
|
230
|
+
await manageChild();
|
|
231
|
+
|
|
232
|
+
const { termsUri } = await childCaller().contracts.consentToContract({
|
|
233
|
+
contractUri,
|
|
234
|
+
terms: minimalTerms,
|
|
235
|
+
});
|
|
236
|
+
const record = await getContractTermsByUri(termsUri);
|
|
237
|
+
expect(record?.terms.status).toBe('live');
|
|
238
|
+
expect(record?.terms.guardianApproval).toBeUndefined();
|
|
239
|
+
});
|
|
240
|
+
|
|
228
241
|
it('records the verified guardian and contract version on consent and permission updates', async () => {
|
|
229
242
|
await manageChild();
|
|
230
243
|
const beforeApproval = Date.now();
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { createHash, randomBytes } from 'node:crypto';
|
|
2
|
+
import { v4 as uuid } from 'uuid';
|
|
3
|
+
import { neogma } from '@instance';
|
|
4
|
+
import { computeShareLinkRequestHash } from '@helpers/share-link-lifecycle';
|
|
5
|
+
import { reserveCreate } from '../../src/accesslayer/share-link';
|
|
6
|
+
|
|
7
|
+
export const nextShareId = (): string => randomBytes(16).toString('base64url');
|
|
8
|
+
|
|
9
|
+
export const contentBinding = (seed: string) => ({
|
|
10
|
+
contentHash: createHash('sha256').update(`content:${seed}`).digest('hex'),
|
|
11
|
+
contentBytes: 128,
|
|
12
|
+
recoveryHash: createHash('sha256').update(`recovery:${seed}`).digest('hex'),
|
|
13
|
+
recoveryBytes: 256,
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
export const clearLifecycleGraph = async (): Promise<void> => {
|
|
17
|
+
await neogma.queryRunner.run(
|
|
18
|
+
`MATCH (n)
|
|
19
|
+
WHERE n:ShareLink OR n:ShareLinkReservation OR n:ShareLinkOperation OR n:ShareContentCleanupJob
|
|
20
|
+
DETACH DELETE n`
|
|
21
|
+
);
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export type ReservedCreate = {
|
|
25
|
+
namespace: string;
|
|
26
|
+
ownerProfileId: string;
|
|
27
|
+
shareId: string;
|
|
28
|
+
clientRequestId: string;
|
|
29
|
+
requestHash: string;
|
|
30
|
+
operationId: string;
|
|
31
|
+
objectRef: string;
|
|
32
|
+
generation: number;
|
|
33
|
+
leaseOwner: string;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export const reserveShareFixture = async (options: {
|
|
37
|
+
ownerProfileId: string;
|
|
38
|
+
shareId?: string;
|
|
39
|
+
expiresAt?: string | null;
|
|
40
|
+
selectedCount?: number;
|
|
41
|
+
title?: string;
|
|
42
|
+
leaseMs?: number;
|
|
43
|
+
now: Date;
|
|
44
|
+
clientRequestId?: string;
|
|
45
|
+
requestHash?: string;
|
|
46
|
+
namespace: string;
|
|
47
|
+
}): Promise<ReservedCreate> => {
|
|
48
|
+
const namespace = options.namespace;
|
|
49
|
+
const ownerProfileId = options.ownerProfileId;
|
|
50
|
+
const shareId = options.shareId ?? nextShareId();
|
|
51
|
+
const clientRequestId = options.clientRequestId ?? uuid();
|
|
52
|
+
const selectedCount = options.selectedCount ?? 1;
|
|
53
|
+
const content = contentBinding(shareId);
|
|
54
|
+
const requestHash =
|
|
55
|
+
options.requestHash ??
|
|
56
|
+
computeShareLinkRequestHash('create', {
|
|
57
|
+
id: shareId,
|
|
58
|
+
title: options.title ?? 'Shared credentials',
|
|
59
|
+
selectedCount,
|
|
60
|
+
...content,
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const result = await reserveCreate({
|
|
64
|
+
namespace,
|
|
65
|
+
ownerProfileId,
|
|
66
|
+
clientRequestId,
|
|
67
|
+
shareId,
|
|
68
|
+
title: options.title ?? 'Shared credentials',
|
|
69
|
+
note: null,
|
|
70
|
+
expiresAt: options.expiresAt ?? null,
|
|
71
|
+
selectedCount,
|
|
72
|
+
content,
|
|
73
|
+
requestHash,
|
|
74
|
+
leaseOwner: 'worker-1',
|
|
75
|
+
leaseMs: options.leaseMs,
|
|
76
|
+
now: options.now,
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
if (result.outcome !== 'reserved' || !result.reservation.objectRef) {
|
|
80
|
+
throw new Error(`expected reserved create, got ${result.outcome}`);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
namespace,
|
|
85
|
+
ownerProfileId,
|
|
86
|
+
shareId,
|
|
87
|
+
clientRequestId,
|
|
88
|
+
requestHash,
|
|
89
|
+
operationId: result.reservation.operationId,
|
|
90
|
+
objectRef: result.reservation.objectRef,
|
|
91
|
+
generation: result.reservation.generation,
|
|
92
|
+
leaseOwner: result.reservation.leaseOwner,
|
|
93
|
+
};
|
|
94
|
+
};
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { vi, describe, it, expect, beforeEach, afterAll, beforeAll } from 'vitest';
|
|
2
|
+
import { getClient, getUser } from './helpers/getClient';
|
|
3
|
+
import { testUnsignedBoost } from './helpers/send';
|
|
4
|
+
import {
|
|
5
|
+
Profile,
|
|
6
|
+
InboxCredential,
|
|
7
|
+
ContactMethod,
|
|
8
|
+
SigningAuthority,
|
|
9
|
+
Boost,
|
|
10
|
+
ProfileManager,
|
|
11
|
+
} from '@models';
|
|
12
|
+
import { createContactMethod } from '@accesslayer/contact-method/create';
|
|
13
|
+
import { createProfileContactMethodRelationship } from '@accesslayer/contact-method/relationships/create';
|
|
14
|
+
import { sendSpy } from './helpers/spies';
|
|
15
|
+
|
|
16
|
+
vi.mock('@services/delivery/delivery.factory', () => ({
|
|
17
|
+
getDeliveryService: () => ({ send: sendSpy }),
|
|
18
|
+
}));
|
|
19
|
+
|
|
20
|
+
type SentNotification = {
|
|
21
|
+
contactMethod: { value: string };
|
|
22
|
+
templateId: string;
|
|
23
|
+
templateModel?: { claimUrl?: string };
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
let userA: Awaited<ReturnType<typeof getUser>>;
|
|
27
|
+
|
|
28
|
+
const clearDb = async (): Promise<void> => {
|
|
29
|
+
await ProfileManager.delete({ detach: true, where: {} });
|
|
30
|
+
await Profile.delete({ detach: true, where: {} });
|
|
31
|
+
await InboxCredential.delete({ detach: true, where: {} });
|
|
32
|
+
await ContactMethod.delete({ detach: true, where: {} });
|
|
33
|
+
await SigningAuthority.delete({ detach: true, where: {} });
|
|
34
|
+
await Boost.delete({ detach: true, where: {} });
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const createManagedRecipient = async (
|
|
38
|
+
profileId: string,
|
|
39
|
+
email: string,
|
|
40
|
+
isServiceProfile: boolean
|
|
41
|
+
): Promise<void> => {
|
|
42
|
+
const boostUri = await userA.clients.fullAuth.boost.createBoost({
|
|
43
|
+
credential: testUnsignedBoost,
|
|
44
|
+
});
|
|
45
|
+
const managerDid = await userA.clients.fullAuth.profileManager.createChildProfileManager({
|
|
46
|
+
parentUri: boostUri,
|
|
47
|
+
profile: {},
|
|
48
|
+
});
|
|
49
|
+
const managerClient = getClient({ did: managerDid, isChallengeValid: true });
|
|
50
|
+
await managerClient.profileManager.createManagedProfile({
|
|
51
|
+
profileId,
|
|
52
|
+
displayName: profileId,
|
|
53
|
+
isServiceProfile,
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
const contactMethod = await createContactMethod({
|
|
57
|
+
type: 'email',
|
|
58
|
+
value: email,
|
|
59
|
+
isVerified: true,
|
|
60
|
+
isPrimary: true,
|
|
61
|
+
});
|
|
62
|
+
await createProfileContactMethodRelationship(profileId, contactMethod.id);
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const sendTo = async (recipient: string) => {
|
|
66
|
+
const signedVc = await userA.learnCard.invoke.issueCredential({
|
|
67
|
+
...testUnsignedBoost,
|
|
68
|
+
issuer: userA.learnCard.id.did(),
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
return userA.clients.fullAuth.boost.send({
|
|
72
|
+
type: 'boost',
|
|
73
|
+
recipient,
|
|
74
|
+
template: { credential: testUnsignedBoost },
|
|
75
|
+
signedCredential: signedVc,
|
|
76
|
+
});
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
describe('Inbox send to managed recipients', () => {
|
|
80
|
+
beforeAll(async () => {
|
|
81
|
+
userA = await getUser('a'.repeat(64));
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
beforeEach(async () => {
|
|
85
|
+
sendSpy.mockClear();
|
|
86
|
+
await clearDb();
|
|
87
|
+
await userA.clients.fullAuth.profile.createProfile({
|
|
88
|
+
profileId: 'usera',
|
|
89
|
+
displayName: 'User A',
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
afterAll(async () => {
|
|
94
|
+
sendSpy.mockClear();
|
|
95
|
+
await clearDb();
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('delivers directly to a managed service profile without a guardian gate', async () => {
|
|
99
|
+
await createManagedRecipient('managed-org', 'org@example.com', true);
|
|
100
|
+
|
|
101
|
+
const result = await sendTo('org@example.com');
|
|
102
|
+
|
|
103
|
+
expect(result.inbox?.status).toBe('ISSUED');
|
|
104
|
+
expect(result.inbox?.guardianStatus).toBeUndefined();
|
|
105
|
+
|
|
106
|
+
const credentials = await InboxCredential.findMany({ where: {} });
|
|
107
|
+
expect(credentials).toHaveLength(1);
|
|
108
|
+
expect(credentials[0]!.guardianStatus).toBeFalsy();
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('gates a managed child and sends the claim email with a valid template', async () => {
|
|
112
|
+
await createManagedRecipient('managed-child', 'child@example.com', false);
|
|
113
|
+
|
|
114
|
+
const result = await sendTo('child@example.com');
|
|
115
|
+
|
|
116
|
+
expect(result.inbox?.status).toBe('PENDING');
|
|
117
|
+
expect(result.inbox?.guardianStatus).toBe('AWAITING_GUARDIAN');
|
|
118
|
+
|
|
119
|
+
const credentials = await InboxCredential.findMany({ where: {} });
|
|
120
|
+
expect(credentials).toHaveLength(1);
|
|
121
|
+
expect(credentials[0]!.guardianStatus).toBe('AWAITING_GUARDIAN');
|
|
122
|
+
|
|
123
|
+
const childEmail = sendSpy.mock.calls
|
|
124
|
+
.map(call => call[0] as SentNotification)
|
|
125
|
+
.find(notification => notification.contactMethod.value === 'child@example.com');
|
|
126
|
+
|
|
127
|
+
expect(childEmail?.templateId).toBe('universal-inbox-claim');
|
|
128
|
+
expect(childEmail?.templateModel?.claimUrl).toBeTruthy();
|
|
129
|
+
});
|
|
130
|
+
});
|
|
@@ -45,6 +45,7 @@ const SUPPORTED_KEYS = [
|
|
|
45
45
|
'credentialSuspendedUnnamed',
|
|
46
46
|
'credentialRestoredNamed',
|
|
47
47
|
'credentialRestoredUnnamed',
|
|
48
|
+
'shareViewed',
|
|
48
49
|
] as NotificationMessageKey[];
|
|
49
50
|
|
|
50
51
|
const LOCALES = ['en', 'es', 'fr', 'ar'];
|
|
@@ -72,6 +73,18 @@ describe('notificationMessages catalog', () => {
|
|
|
72
73
|
});
|
|
73
74
|
|
|
74
75
|
describe('getNotificationMessage — interpolation', () => {
|
|
76
|
+
it('localizes private-share view alerts in every supported locale', () => {
|
|
77
|
+
const titles = ['Share viewed', 'Enlace visto', 'Partage consulté', 'تمت مشاهدة المشاركة'];
|
|
78
|
+
for (const [index, locale] of LOCALES.entries()) {
|
|
79
|
+
const message = getNotificationMessage('shareViewed', locale, {
|
|
80
|
+
title: 'Career highlights',
|
|
81
|
+
count: '3',
|
|
82
|
+
});
|
|
83
|
+
expect(message.title).toBe(titles[index]);
|
|
84
|
+
expect(message.body).toContain('Career highlights');
|
|
85
|
+
expect(message.body).toContain('3');
|
|
86
|
+
}
|
|
87
|
+
});
|
|
75
88
|
it.each([
|
|
76
89
|
['en', 'Ada claimed your credential — connect?'],
|
|
77
90
|
['es', 'Ada reclamó tu credencial. ¿Conectar?'],
|
package/test/openapi.spec.ts
CHANGED
|
@@ -32,6 +32,26 @@ describe('OpenAPI generation', () => {
|
|
|
32
32
|
expect(Object.keys(openApiDocument.paths ?? {}).length).toBeGreaterThan(0);
|
|
33
33
|
});
|
|
34
34
|
|
|
35
|
+
it('mounts the anonymous public share routes on paths distinct from the owner route', () => {
|
|
36
|
+
const paths = Object.keys(openApiDocument.paths ?? {});
|
|
37
|
+
|
|
38
|
+
// The import above would have thrown Duplicate procedure for the old
|
|
39
|
+
// colliding GET /share-links/{id} registration.
|
|
40
|
+
expect(paths.filter(path => path === '/share-links/{id}')).toHaveLength(1);
|
|
41
|
+
expect(paths).toContain('/public/share-links/{id}');
|
|
42
|
+
expect(paths).toContain('/public/share-links/{id}/content');
|
|
43
|
+
expect(paths).toContain('/public/share-links/acknowledge-view');
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('mounts the bounded owner list on GET /share-links, distinct from /share-links/{id}', () => {
|
|
47
|
+
const paths = Object.keys(openApiDocument.paths ?? {});
|
|
48
|
+
|
|
49
|
+
// A collection path with no trailing segment cannot be captured by the
|
|
50
|
+
// `{id}` param route, so /list -> /{id} shadowing is impossible.
|
|
51
|
+
expect(paths.filter(path => path === '/share-links')).toHaveLength(1);
|
|
52
|
+
expect(openApiDocument.paths?.['/share-links']?.get).toBeDefined();
|
|
53
|
+
});
|
|
54
|
+
|
|
35
55
|
it('includes the skill-search route whose $regex query previously broke generation', () => {
|
|
36
56
|
const paths = Object.keys(openApiDocument.paths ?? {});
|
|
37
57
|
|
|
@@ -2,6 +2,9 @@ import { vi } from 'vitest';
|
|
|
2
2
|
import { getClient, getUser } from './helpers/getClient';
|
|
3
3
|
import { testVp, sendPresentation } from './helpers/send';
|
|
4
4
|
import { Profile, Credential, Presentation } from '@models';
|
|
5
|
+
import * as Notifications from '@helpers/notifications.helpers';
|
|
6
|
+
import { addNotificationToQueueSpy } from './helpers/spies';
|
|
7
|
+
import { LCNNotificationTypeEnumValidator } from '@learncard/types';
|
|
5
8
|
|
|
6
9
|
const noAuthClient = getClient();
|
|
7
10
|
let userA: Awaited<ReturnType<typeof getUser>>;
|
|
@@ -13,6 +16,10 @@ describe('Presentations', () => {
|
|
|
13
16
|
userA = await getUser();
|
|
14
17
|
userB = await getUser('b'.repeat(64));
|
|
15
18
|
userC = await getUser('c'.repeat(64));
|
|
19
|
+
|
|
20
|
+
vi.spyOn(Notifications, 'addNotificationToQueue').mockImplementation(
|
|
21
|
+
addNotificationToQueueSpy
|
|
22
|
+
);
|
|
16
23
|
});
|
|
17
24
|
|
|
18
25
|
describe('sendPresentation', () => {
|
|
@@ -22,6 +29,8 @@ describe('Presentations', () => {
|
|
|
22
29
|
await Presentation.delete({ detach: true, where: {} });
|
|
23
30
|
await userA.clients.fullAuth.profile.createProfile({ profileId: 'usera' });
|
|
24
31
|
await userB.clients.fullAuth.profile.createProfile({ profileId: 'userb' });
|
|
32
|
+
|
|
33
|
+
addNotificationToQueueSpy.mockReset();
|
|
25
34
|
});
|
|
26
35
|
|
|
27
36
|
afterAll(async () => {
|
|
@@ -52,6 +61,25 @@ describe('Presentations', () => {
|
|
|
52
61
|
presentation: testVp,
|
|
53
62
|
})
|
|
54
63
|
).resolves.not.toThrow();
|
|
64
|
+
|
|
65
|
+
expect(addNotificationToQueueSpy).toHaveBeenCalledWith(
|
|
66
|
+
expect.objectContaining({
|
|
67
|
+
type: LCNNotificationTypeEnumValidator.enum.PRESENTATION_RECEIVED,
|
|
68
|
+
to: expect.objectContaining({ profileId: 'userb' }),
|
|
69
|
+
from: expect.objectContaining({ profileId: 'usera' }),
|
|
70
|
+
})
|
|
71
|
+
);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('should not notify a profile when it sends a presentation to itself', async () => {
|
|
75
|
+
await expect(
|
|
76
|
+
userA.clients.fullAuth.presentation.sendPresentation({
|
|
77
|
+
profileId: 'usera',
|
|
78
|
+
presentation: testVp,
|
|
79
|
+
})
|
|
80
|
+
).resolves.not.toThrow();
|
|
81
|
+
|
|
82
|
+
expect(addNotificationToQueueSpy).not.toHaveBeenCalled();
|
|
55
83
|
});
|
|
56
84
|
|
|
57
85
|
it('should allow sending a presentation to did:web', async () => {
|
|
@@ -137,7 +165,7 @@ describe('Presentations', () => {
|
|
|
137
165
|
).resolves.not.toThrow();
|
|
138
166
|
});
|
|
139
167
|
|
|
140
|
-
it('should
|
|
168
|
+
it('should accept the same presentation idempotently', async () => {
|
|
141
169
|
const uri = await userA.clients.fullAuth.presentation.sendPresentation({
|
|
142
170
|
profileId: 'userb',
|
|
143
171
|
presentation: testVp,
|
|
@@ -148,13 +176,14 @@ describe('Presentations', () => {
|
|
|
148
176
|
userB.clients.fullAuth.presentation.acceptPresentation({ uri })
|
|
149
177
|
).resolves.not.toThrow();
|
|
150
178
|
|
|
151
|
-
//
|
|
179
|
+
// A retry after a lost response should also succeed without creating another edge.
|
|
152
180
|
await expect(
|
|
153
181
|
userB.clients.fullAuth.presentation.acceptPresentation({ uri })
|
|
154
|
-
).
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
182
|
+
).resolves.not.toThrow();
|
|
183
|
+
|
|
184
|
+
await expect(
|
|
185
|
+
userB.clients.fullAuth.presentation.receivedPresentations()
|
|
186
|
+
).resolves.toHaveLength(1);
|
|
158
187
|
});
|
|
159
188
|
});
|
|
160
189
|
|
|
@@ -228,6 +257,25 @@ describe('Presentations', () => {
|
|
|
228
257
|
vi.useRealTimers();
|
|
229
258
|
});
|
|
230
259
|
|
|
260
|
+
it('should preserve presentation metadata after acceptance', async () => {
|
|
261
|
+
const metadata = {
|
|
262
|
+
type: 'learncard.share-link.v1',
|
|
263
|
+
shareId: 'AAAAAAAAAAAAAAAAAAAAAA',
|
|
264
|
+
title: 'Career highlights',
|
|
265
|
+
sharer: { profileId: 'usera', displayName: 'User A' },
|
|
266
|
+
};
|
|
267
|
+
const uri = await userA.clients.fullAuth.presentation.sendPresentation({
|
|
268
|
+
profileId: 'userb',
|
|
269
|
+
presentation: testVp,
|
|
270
|
+
metadata,
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
await userB.clients.fullAuth.presentation.acceptPresentation({ uri });
|
|
274
|
+
|
|
275
|
+
const presentations = await userB.clients.fullAuth.presentation.receivedPresentations();
|
|
276
|
+
expect(presentations[0]?.metadata).toEqual(metadata);
|
|
277
|
+
});
|
|
278
|
+
|
|
231
279
|
it('should allow filtering received presentations by who sent them', async () => {
|
|
232
280
|
await userC.clients.fullAuth.profile.createProfile({ profileId: 'userc' });
|
|
233
281
|
|
package/test/profiles.spec.ts
CHANGED
|
@@ -5,7 +5,15 @@ import {
|
|
|
5
5
|
ProfileVisibilityEnum,
|
|
6
6
|
} from '@learncard/types';
|
|
7
7
|
import { getClient, getUser } from './helpers/getClient';
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
Profile,
|
|
10
|
+
ProfileManager,
|
|
11
|
+
SigningAuthority,
|
|
12
|
+
Credential,
|
|
13
|
+
Boost,
|
|
14
|
+
ClaimHook,
|
|
15
|
+
ContactMethod,
|
|
16
|
+
} from '@models';
|
|
9
17
|
import cache from '@cache';
|
|
10
18
|
import { testVc, sendBoost, testVp, testUnsignedBoost } from './helpers/send';
|
|
11
19
|
|
|
@@ -28,6 +36,30 @@ describe('Profiles', () => {
|
|
|
28
36
|
userC = await getUser('c'.repeat(64));
|
|
29
37
|
});
|
|
30
38
|
|
|
39
|
+
describe('createManagedProfile', () => {
|
|
40
|
+
beforeEach(async () => {
|
|
41
|
+
await ProfileManager.delete({ detach: true, where: {} });
|
|
42
|
+
await Profile.delete({ detach: true, where: {} });
|
|
43
|
+
await userA.clients.fullAuth.profile.createProfile({ profileId: 'usera' });
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
afterAll(async () => {
|
|
47
|
+
await ProfileManager.delete({ detach: true, where: {} });
|
|
48
|
+
await Profile.delete({ detach: true, where: {} });
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('should reserve sample persona profileIds', async () => {
|
|
52
|
+
const managerDid = await userA.clients.fullAuth.profileManager.createProfileManager({});
|
|
53
|
+
const managerClient = getClient({ did: managerDid, isChallengeValid: true });
|
|
54
|
+
|
|
55
|
+
await expect(
|
|
56
|
+
managerClient.profileManager.createManagedProfile({
|
|
57
|
+
profileId: 'Sample-college-board',
|
|
58
|
+
})
|
|
59
|
+
).rejects.toMatchObject({ code: 'BAD_REQUEST' });
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
31
63
|
describe('createProfile', () => {
|
|
32
64
|
beforeEach(async () => {
|
|
33
65
|
await Profile.delete({ detach: true, where: {} });
|
|
@@ -63,6 +95,14 @@ describe('Profiles', () => {
|
|
|
63
95
|
).rejects.toThrow();
|
|
64
96
|
});
|
|
65
97
|
|
|
98
|
+
it('should reserve sample persona profileIds', async () => {
|
|
99
|
+
await expect(
|
|
100
|
+
userA.clients.fullAuth.profile.createProfile({
|
|
101
|
+
profileId: 'Sample-college-board',
|
|
102
|
+
})
|
|
103
|
+
).rejects.toMatchObject({ code: 'BAD_REQUEST' });
|
|
104
|
+
});
|
|
105
|
+
|
|
66
106
|
it('should not allow creating a profile with an email that has already been taken', async () => {
|
|
67
107
|
await expect(
|
|
68
108
|
userA.clients.fullAuth.profile.createProfile({
|
|
@@ -387,6 +427,14 @@ describe('Profiles', () => {
|
|
|
387
427
|
).rejects.toThrow();
|
|
388
428
|
});
|
|
389
429
|
|
|
430
|
+
it('should reserve sample persona profileIds', async () => {
|
|
431
|
+
await expect(
|
|
432
|
+
userA.clients.fullAuth.profile.createServiceProfile({
|
|
433
|
+
profileId: 'sample-college-board',
|
|
434
|
+
})
|
|
435
|
+
).rejects.toMatchObject({ code: 'BAD_REQUEST' });
|
|
436
|
+
});
|
|
437
|
+
|
|
390
438
|
it('should not allow creating a profile with an email that has already been taken', async () => {
|
|
391
439
|
await expect(
|
|
392
440
|
userA.clients.fullAuth.profile.createServiceProfile({
|
|
@@ -490,6 +538,16 @@ describe('Profiles', () => {
|
|
|
490
538
|
})
|
|
491
539
|
).resolves.not.toThrow();
|
|
492
540
|
});
|
|
541
|
+
|
|
542
|
+
it('should reserve sample persona profileIds', async () => {
|
|
543
|
+
await userA.clients.fullAuth.profile.createProfile({ profileId: 'usera' });
|
|
544
|
+
|
|
545
|
+
await expect(
|
|
546
|
+
userA.clients.fullAuth.profile.createManagedServiceProfile({
|
|
547
|
+
profileId: 'sample-college-board',
|
|
548
|
+
})
|
|
549
|
+
).rejects.toMatchObject({ code: 'BAD_REQUEST' });
|
|
550
|
+
});
|
|
493
551
|
});
|
|
494
552
|
|
|
495
553
|
describe('getManagedServiceProfile', () => {
|
|
@@ -1255,6 +1313,14 @@ describe('Profiles', () => {
|
|
|
1255
1313
|
).rejects.toMatchObject({ code: 'CONFLICT' });
|
|
1256
1314
|
});
|
|
1257
1315
|
|
|
1316
|
+
it('should not allow changing a profileId to the sample persona namespace', async () => {
|
|
1317
|
+
await expect(
|
|
1318
|
+
userA.clients.fullAuth.profile.updateProfile({
|
|
1319
|
+
profileId: 'sample-college-board',
|
|
1320
|
+
})
|
|
1321
|
+
).rejects.toMatchObject({ code: 'BAD_REQUEST' });
|
|
1322
|
+
});
|
|
1323
|
+
|
|
1258
1324
|
it('should allow you to update your email', async () => {
|
|
1259
1325
|
await expect(
|
|
1260
1326
|
userA.clients.fullAuth.profile.updateProfile({ email: 'nice@test.com' })
|