@learncard/network-brain-service 4.1.3 → 5.0.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/.env.example +14 -0
- package/CHANGELOG.md +143 -0
- package/README.md +27 -1
- package/bootstrapLambda.test.ts +70 -32
- package/config/config.dev.json +1 -0
- package/config/config.example.json +2 -1
- package/config/config.production.json +1 -0
- package/config/config.scouts.dev.json +1 -0
- package/config/config.scouts.production.json +1 -0
- package/consentFlowDeadlineLambda.test.ts +108 -0
- package/consentFlowDeadlineLambda.ts +18 -0
- package/consentFlowDeadlineLambdaApp.ts +14 -0
- package/dist/accesslayer/boost/create.d.ts +8 -2
- package/dist/accesslayer/boost/create.d.ts.map +1 -1
- package/dist/accesslayer/campaign/audience.d.ts +4 -4
- package/dist/accesslayer/campaign/audience.d.ts.map +1 -1
- package/dist/accesslayer/campaign/create.d.ts +1 -1
- package/dist/accesslayer/campaign/create.d.ts.map +1 -1
- package/dist/accesslayer/campaign/delivery.d.ts +5 -0
- package/dist/accesslayer/campaign/delivery.d.ts.map +1 -0
- package/dist/accesslayer/campaign/dispatch.d.ts +5 -0
- package/dist/accesslayer/campaign/dispatch.d.ts.map +1 -0
- package/dist/accesslayer/campaign/dispatch.test.d.ts +2 -0
- package/dist/accesslayer/campaign/dispatch.test.d.ts.map +1 -0
- package/dist/accesslayer/campaign/email.d.ts +10 -0
- package/dist/accesslayer/campaign/email.d.ts.map +1 -0
- package/dist/accesslayer/campaign/email.test.d.ts +2 -0
- package/dist/accesslayer/campaign/email.test.d.ts.map +1 -0
- package/dist/accesslayer/campaign/preferences.d.ts +5 -0
- package/dist/accesslayer/campaign/preferences.d.ts.map +1 -0
- package/dist/accesslayer/campaign/read.d.ts.map +1 -1
- package/dist/accesslayer/campaign/test.d.ts +5 -0
- package/dist/accesslayer/campaign/test.d.ts.map +1 -0
- package/dist/accesslayer/campaign/test.test.d.ts +2 -0
- package/dist/accesslayer/campaign/test.test.d.ts.map +1 -0
- package/dist/accesslayer/campaign/update.d.ts.map +1 -1
- package/dist/accesslayer/consentflowcontract/create.d.ts.map +1 -1
- package/dist/accesslayer/consentflowcontract/delete.d.ts +1 -1
- package/dist/accesslayer/consentflowcontract/delete.d.ts.map +1 -1
- package/dist/accesslayer/consentflowcontract/read.d.ts.map +1 -1
- package/dist/accesslayer/consentflowcontract/relationships/create.d.ts +2 -1
- package/dist/accesslayer/consentflowcontract/relationships/create.d.ts.map +1 -1
- package/dist/accesslayer/consentflowcontract/relationships/read.d.ts +19 -4
- package/dist/accesslayer/consentflowcontract/relationships/read.d.ts.map +1 -1
- package/dist/accesslayer/consentflowcontract/relationships/update.d.ts +6 -1
- package/dist/accesslayer/consentflowcontract/relationships/update.d.ts.map +1 -1
- package/dist/accesslayer/consentflowcontract/versions.d.ts +68 -0
- package/dist/accesslayer/consentflowcontract/versions.d.ts.map +1 -0
- package/dist/accesslayer/credential/create.d.ts +3 -1
- package/dist/accesslayer/credential/create.d.ts.map +1 -1
- package/dist/accesslayer/credential/delete.d.ts +1 -0
- package/dist/accesslayer/credential/delete.d.ts.map +1 -1
- package/dist/accesslayer/credential/relationships/create.d.ts +15 -3
- package/dist/accesslayer/credential/relationships/create.d.ts.map +1 -1
- package/dist/accesslayer/credential/relationships/read.d.ts +2 -0
- package/dist/accesslayer/credential/relationships/read.d.ts.map +1 -1
- package/dist/accesslayer/credential/relationships/update.d.ts.map +1 -1
- package/dist/accesslayer/credential-refresh/read.d.ts +1 -0
- package/dist/accesslayer/credential-refresh/read.d.ts.map +1 -1
- package/dist/accesslayer/credential-refresh/update.d.ts.map +1 -1
- package/dist/accesslayer/inbox-credential/create.d.ts +1 -0
- package/dist/accesslayer/inbox-credential/create.d.ts.map +1 -1
- package/dist/accesslayer/inbox-credential/refresh.d.ts +7 -0
- package/dist/accesslayer/inbox-credential/refresh.d.ts.map +1 -1
- package/dist/app.d.ts +11 -1
- package/dist/app.d.ts.map +1 -1
- package/dist/cache/app-store.caches.d.ts +61 -1
- package/dist/cache/app-store.caches.d.ts.map +1 -1
- package/dist/campaign-unsubscribe.d.ts +7 -0
- package/dist/campaign-unsubscribe.d.ts.map +1 -0
- package/dist/campaign-unsubscribe.test.d.ts +2 -0
- package/dist/campaign-unsubscribe.test.d.ts.map +1 -0
- package/dist/config/environment.d.ts +42 -0
- package/dist/config/environment.d.ts.map +1 -1
- package/dist/config/stageConfig.d.ts +4 -0
- package/dist/config/stageConfig.d.ts.map +1 -1
- package/dist/credential-refresh.d.ts.map +1 -1
- package/dist/helpers/boost.helpers.d.ts +4 -1
- package/dist/helpers/boost.helpers.d.ts.map +1 -1
- package/dist/helpers/campaign-app-origin.helpers.d.ts +2 -0
- package/dist/helpers/campaign-app-origin.helpers.d.ts.map +1 -0
- package/dist/helpers/campaign-email.helpers.d.ts +13 -0
- package/dist/helpers/campaign-email.helpers.d.ts.map +1 -0
- package/dist/helpers/campaign-notifications.helpers.d.ts +7 -0
- package/dist/helpers/campaign-notifications.helpers.d.ts.map +1 -0
- package/dist/helpers/campaign-queue.helpers.d.ts +7 -0
- package/dist/helpers/campaign-queue.helpers.d.ts.map +1 -0
- package/dist/helpers/campaign-queue.helpers.test.d.ts +2 -0
- package/dist/helpers/campaign-queue.helpers.test.d.ts.map +1 -0
- package/dist/helpers/campaign-unsubscribe-page.d.ts +2 -0
- package/dist/helpers/campaign-unsubscribe-page.d.ts.map +1 -0
- package/dist/helpers/campaign-unsubscribe.helpers.d.ts +7 -0
- package/dist/helpers/campaign-unsubscribe.helpers.d.ts.map +1 -0
- package/dist/helpers/campaign-webhook.helpers.test.d.ts +2 -0
- package/dist/helpers/campaign-webhook.helpers.test.d.ts.map +1 -0
- package/dist/helpers/consentflow-deadline.helpers.d.ts +5 -0
- package/dist/helpers/consentflow-deadline.helpers.d.ts.map +1 -0
- package/dist/helpers/consentflow-legacy-migration.helpers.d.ts +2 -0
- package/dist/helpers/consentflow-legacy-migration.helpers.d.ts.map +1 -0
- package/dist/helpers/contract-events.helpers.d.ts.map +1 -1
- package/dist/helpers/contract.helpers.d.ts +1 -1
- package/dist/helpers/contract.helpers.d.ts.map +1 -1
- package/dist/helpers/credential-display.helpers.d.ts +5 -0
- package/dist/helpers/credential-display.helpers.d.ts.map +1 -0
- package/dist/helpers/credential-display.helpers.test.d.ts +2 -0
- package/dist/helpers/credential-display.helpers.test.d.ts.map +1 -0
- package/dist/helpers/credential-refresh-email.helpers.d.ts.map +1 -1
- package/dist/helpers/credential-refresh-materiality.helpers.d.ts +4 -0
- package/dist/helpers/credential-refresh-materiality.helpers.d.ts.map +1 -1
- package/dist/helpers/credential-refresh.helpers.d.ts +30 -1
- package/dist/helpers/credential-refresh.helpers.d.ts.map +1 -1
- package/dist/helpers/credential-status-mutation.helpers.d.ts +5 -0
- package/dist/helpers/credential-status-mutation.helpers.d.ts.map +1 -0
- package/dist/helpers/credential-status-mutation.helpers.test.d.ts +2 -0
- package/dist/helpers/credential-status-mutation.helpers.test.d.ts.map +1 -0
- package/dist/helpers/credential.helpers.d.ts +1 -1
- package/dist/helpers/credential.helpers.d.ts.map +1 -1
- package/dist/helpers/inbox-refresh.helpers.d.ts +4 -2
- package/dist/helpers/inbox-refresh.helpers.d.ts.map +1 -1
- package/dist/helpers/inbox.helpers.d.ts +1 -0
- package/dist/helpers/inbox.helpers.d.ts.map +1 -1
- package/dist/helpers/learnCard.helpers.d.ts +1 -0
- package/dist/helpers/learnCard.helpers.d.ts.map +1 -1
- package/dist/helpers/managed-status.helpers.d.ts +7 -0
- package/dist/helpers/managed-status.helpers.d.ts.map +1 -0
- package/dist/helpers/managed-status.helpers.test.d.ts +2 -0
- package/dist/helpers/managed-status.helpers.test.d.ts.map +1 -0
- package/dist/helpers/notification-delivery-boundary.helpers.test.d.ts +2 -0
- package/dist/helpers/notification-delivery-boundary.helpers.test.d.ts.map +1 -0
- package/dist/helpers/notification-webhook-fetch.helpers.d.ts +13 -0
- package/dist/helpers/notification-webhook-fetch.helpers.d.ts.map +1 -0
- package/dist/helpers/notification-webhook-fetch.helpers.test.d.ts +2 -0
- package/dist/helpers/notification-webhook-fetch.helpers.test.d.ts.map +1 -0
- package/dist/helpers/notificationQueue.helpers.d.ts.map +1 -1
- package/dist/helpers/notifications.helpers.d.ts.map +1 -1
- package/dist/helpers/refresh-send-intent.helpers.d.ts +13 -5
- package/dist/helpers/refresh-send-intent.helpers.d.ts.map +1 -1
- package/dist/helpers/refresh-send-intent.helpers.test.d.ts +2 -0
- package/dist/helpers/refresh-send-intent.helpers.test.d.ts.map +1 -0
- package/dist/helpers/send-contract.helpers.d.ts +11 -0
- package/dist/helpers/send-contract.helpers.d.ts.map +1 -0
- package/dist/helpers/status-list.helpers.d.ts +12 -1
- package/dist/helpers/status-list.helpers.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/models/ConsentFlowContract.d.ts.map +1 -1
- package/dist/models/ConsentFlowContractVersion.d.ts +12 -0
- package/dist/models/ConsentFlowContractVersion.d.ts.map +1 -0
- package/dist/models/ConsentFlowTerms.d.ts.map +1 -1
- package/dist/models/Credential.d.ts +2 -0
- package/dist/models/Credential.d.ts.map +1 -1
- package/dist/models/CredentialRefresh.d.ts +5 -0
- package/dist/models/CredentialRefresh.d.ts.map +1 -1
- package/dist/models/Integration.d.ts +1 -1
- package/dist/models/Profile.d.ts.map +1 -1
- package/dist/models/campaign-constraints.d.ts.map +1 -1
- package/dist/models/index.d.ts +1 -0
- package/dist/models/index.d.ts.map +1 -1
- package/dist/openapi.d.ts.map +1 -1
- package/dist/publish-metafile.json +1 -1
- package/dist/routes/activity.d.ts +15 -5
- package/dist/routes/activity.d.ts.map +1 -1
- package/dist/routes/app-store.d.ts +15 -5
- package/dist/routes/app-store.d.ts.map +1 -1
- package/dist/routes/auth-grants.d.ts +12 -2
- package/dist/routes/auth-grants.d.ts.map +1 -1
- package/dist/routes/bench.d.ts +11 -1
- package/dist/routes/bench.d.ts.map +1 -1
- package/dist/routes/boosts.d.ts +70 -15
- package/dist/routes/boosts.d.ts.map +1 -1
- package/dist/routes/campaigns.d.ts +183 -51
- package/dist/routes/campaigns.d.ts.map +1 -1
- package/dist/routes/claim-hooks.d.ts +11 -1
- package/dist/routes/claim-hooks.d.ts.map +1 -1
- package/dist/routes/contact-methods.d.ts +11 -1
- package/dist/routes/contact-methods.d.ts.map +1 -1
- package/dist/routes/contracts.d.ts +374 -8
- package/dist/routes/contracts.d.ts.map +1 -1
- package/dist/routes/credential-input-privacy.test.d.ts +2 -0
- package/dist/routes/credential-input-privacy.test.d.ts.map +1 -0
- package/dist/routes/credential-refreshes.d.ts +210 -1
- package/dist/routes/credential-refreshes.d.ts.map +1 -1
- package/dist/routes/credential-share-previews.d.ts +11 -1
- package/dist/routes/credential-share-previews.d.ts.map +1 -1
- package/dist/routes/credentials.d.ts +132 -3
- package/dist/routes/credentials.d.ts.map +1 -1
- package/dist/routes/did-metadata.d.ts +11 -1
- package/dist/routes/did-metadata.d.ts.map +1 -1
- package/dist/routes/federation.d.ts +11 -1
- package/dist/routes/federation.d.ts.map +1 -1
- package/dist/routes/inbox.d.ts +54 -19
- package/dist/routes/inbox.d.ts.map +1 -1
- package/dist/routes/index.d.ts +100 -1
- package/dist/routes/index.d.ts.map +1 -1
- package/dist/routes/integrations.d.ts +14 -4
- package/dist/routes/integrations.d.ts.map +1 -1
- package/dist/routes/presentations.d.ts +11 -1
- package/dist/routes/presentations.d.ts.map +1 -1
- package/dist/routes/profile-manager.d.ts +11 -1
- package/dist/routes/profile-manager.d.ts.map +1 -1
- package/dist/routes/profiles.d.ts +11 -1
- package/dist/routes/profiles.d.ts.map +1 -1
- package/dist/routes/public-share-links.d.ts +22 -2
- package/dist/routes/public-share-links.d.ts.map +1 -1
- package/dist/routes/share-links.d.ts +36 -16
- package/dist/routes/share-links.d.ts.map +1 -1
- package/dist/routes/skill-frameworks.d.ts +11 -1
- package/dist/routes/skill-frameworks.d.ts.map +1 -1
- package/dist/routes/skills.d.ts +11 -1
- package/dist/routes/skills.d.ts.map +1 -1
- package/dist/routes/storage.d.ts +37 -23
- package/dist/routes/storage.d.ts.map +1 -1
- package/dist/routes/test.d.ts +79 -3
- package/dist/routes/test.d.ts.map +1 -1
- package/dist/routes/utilities.d.ts +11 -1
- package/dist/routes/utilities.d.ts.map +1 -1
- package/dist/routes/workflows.d.ts +11 -1
- package/dist/routes/workflows.d.ts.map +1 -1
- package/dist/services/delivery/adapters/postmark.adapter.d.ts +3 -0
- package/dist/services/delivery/adapters/postmark.adapter.d.ts.map +1 -1
- package/dist/services/delivery/delivery.service.d.ts +10 -0
- package/dist/services/delivery/delivery.service.d.ts.map +1 -1
- package/dist/types/activity.d.ts +3 -3
- package/dist/types/app-manifest.d.ts +10 -10
- package/dist/types/campaign.d.ts +13 -1
- package/dist/types/campaign.d.ts.map +1 -1
- package/dist/types/consentflowcontract.d.ts +175 -7
- package/dist/types/consentflowcontract.d.ts.map +1 -1
- package/dist/types/credential-refresh.d.ts +27 -0
- package/dist/types/credential-refresh.d.ts.map +1 -1
- package/dist/types/credential.d.ts +5 -0
- package/dist/types/credential.d.ts.map +1 -1
- package/dist/types/integration.d.ts +3 -3
- package/lambda.ts +44 -14
- package/lambdaApp.ts +20 -0
- package/package.json +20 -19
- package/project.json +5 -0
- package/scripts/migrate-consent-flow-versions.ts +9 -0
- package/scripts/report-send-template-exposure.ts +47 -0
- package/scripts/verify-python-contracts.ts +16 -1
- package/serverless-local.yml +15 -2
- package/serverless.function-env.cjs +4 -0
- package/serverless.local.yml +12 -0
- package/serverless.yml +48 -3
- package/src/accesslayer/boost/create.ts +62 -7
- package/src/accesslayer/campaign/README.md +275 -6
- package/src/accesslayer/campaign/audience.ts +39 -7
- package/src/accesslayer/campaign/create.ts +4 -2
- package/src/accesslayer/campaign/delivery.ts +66 -0
- package/src/accesslayer/campaign/dispatch.test.ts +59 -0
- package/src/accesslayer/campaign/dispatch.ts +172 -0
- package/src/accesslayer/campaign/email.test.ts +73 -0
- package/src/accesslayer/campaign/email.ts +91 -0
- package/src/accesslayer/campaign/preferences.ts +53 -0
- package/src/accesslayer/campaign/read.ts +3 -1
- package/src/accesslayer/campaign/state.ts +1 -1
- package/src/accesslayer/campaign/test.test.ts +31 -0
- package/src/accesslayer/campaign/test.ts +75 -0
- package/src/accesslayer/campaign/update.ts +15 -4
- package/src/accesslayer/consentflowcontract/create.ts +20 -1
- package/src/accesslayer/consentflowcontract/delete.ts +121 -3
- package/src/accesslayer/consentflowcontract/read.ts +15 -9
- package/src/accesslayer/consentflowcontract/relationships/create.ts +70 -36
- package/src/accesslayer/consentflowcontract/relationships/read.ts +305 -73
- package/src/accesslayer/consentflowcontract/relationships/smartResume.ts +1 -1
- package/src/accesslayer/consentflowcontract/relationships/update.ts +269 -98
- package/src/accesslayer/consentflowcontract/versions.ts +790 -0
- package/src/accesslayer/credential/create.ts +33 -9
- package/src/accesslayer/credential/delete.ts +45 -0
- package/src/accesslayer/credential/relationships/create.ts +102 -47
- package/src/accesslayer/credential/relationships/update.ts +8 -116
- package/src/accesslayer/credential-refresh/read.ts +26 -2
- package/src/accesslayer/credential-refresh/update.ts +6 -2
- package/src/accesslayer/inbox-credential/create.ts +7 -1
- package/src/accesslayer/inbox-credential/refresh.ts +14 -4
- package/src/campaign-unsubscribe.test.ts +158 -0
- package/src/campaign-unsubscribe.ts +86 -0
- package/src/config/environment.test.ts +52 -0
- package/src/config/environment.ts +58 -0
- package/src/config/serverlessFunctionEnv.test.ts +59 -0
- package/src/credential-refresh.ts +1 -0
- package/src/docker-entry.ts +26 -1
- package/src/helpers/boost-storage.helpers.test.ts +107 -41
- package/src/helpers/boost.helpers.ts +88 -40
- package/src/helpers/campaign-app-origin.helpers.ts +25 -0
- package/src/helpers/campaign-email.helpers.ts +113 -0
- package/src/helpers/campaign-notifications.helpers.ts +127 -0
- package/src/helpers/campaign-queue.helpers.test.ts +442 -0
- package/src/helpers/campaign-queue.helpers.ts +122 -0
- package/src/helpers/campaign-unsubscribe-page.ts +107 -0
- package/src/helpers/campaign-unsubscribe.helpers.ts +56 -0
- package/src/helpers/campaign-webhook.helpers.test.ts +137 -0
- package/src/helpers/consentflow-deadline.helpers.ts +22 -0
- package/src/helpers/consentflow-legacy-migration.helpers.ts +179 -0
- package/src/helpers/contract-events.helpers.ts +35 -3
- package/src/helpers/contract-requests.helpers.ts +1 -1
- package/src/helpers/contract.helpers.ts +5 -1
- package/src/helpers/credential-display.helpers.test.ts +68 -0
- package/src/helpers/credential-display.helpers.ts +15 -0
- package/src/helpers/credential-refresh-email.helpers.ts +5 -12
- package/src/helpers/credential-refresh-materiality.helpers.test.ts +138 -1
- package/src/helpers/credential-refresh-materiality.helpers.ts +62 -1
- package/src/helpers/credential-refresh-proof.helpers.test.ts +1 -1
- package/src/helpers/credential-refresh-proof.helpers.ts +2 -2
- package/src/helpers/credential-refresh.helpers.ts +1166 -376
- package/src/helpers/credential-status-mutation.helpers.test.ts +155 -0
- package/src/helpers/credential-status-mutation.helpers.ts +166 -0
- package/src/helpers/credential.helpers.ts +19 -11
- package/src/helpers/finalize-inbox.helpers.ts +1 -1
- package/src/helpers/inbox-refresh.helpers.ts +34 -8
- package/src/helpers/inbox.helpers.ts +2 -0
- package/src/helpers/learnCard.helpers.ts +23 -0
- package/src/helpers/managed-status.helpers.test.ts +101 -0
- package/src/helpers/managed-status.helpers.ts +147 -0
- package/src/helpers/notification-delivery-boundary.helpers.test.ts +114 -0
- package/src/helpers/notification-webhook-fetch.helpers.test.ts +228 -0
- package/src/helpers/notification-webhook-fetch.helpers.ts +166 -0
- package/src/helpers/notificationQueue.helpers.ts +5 -0
- package/src/helpers/notifications.helpers.ts +19 -12
- package/src/helpers/refresh-send-intent.helpers.test.ts +60 -0
- package/src/helpers/refresh-send-intent.helpers.ts +81 -18
- package/src/helpers/send-contract.helpers.ts +36 -0
- package/src/helpers/status-list.helpers.test.ts +29 -12
- package/src/helpers/status-list.helpers.ts +329 -20
- package/src/models/ConsentFlowContract.ts +3 -0
- package/src/models/ConsentFlowContractVersion.ts +50 -0
- package/src/models/ConsentFlowTerms.ts +4 -0
- package/src/models/Credential.ts +2 -0
- package/src/models/CredentialRefresh.ts +2 -0
- package/src/models/Profile.ts +2 -0
- package/src/models/campaign-constraints.ts +13 -8
- package/src/models/index.ts +2 -0
- package/src/openapi.ts +21 -0
- package/src/routes/app-store.ts +2 -1
- package/src/routes/boosts.ts +219 -137
- package/src/routes/campaigns.ts +98 -19
- package/src/routes/contracts.ts +433 -115
- package/src/routes/credential-input-privacy.test.ts +92 -0
- package/src/routes/credential-refreshes.ts +273 -2
- package/src/routes/federation.ts +7 -2
- package/src/routes/inbox.ts +6 -1
- package/src/routes/index.ts +31 -2
- package/src/routes/workflows.ts +61 -46
- package/src/services/delivery/adapters/postmark.adapter.test.ts +70 -1
- package/src/services/delivery/adapters/postmark.adapter.ts +84 -11
- package/src/services/delivery/delivery.service.ts +4 -0
- package/src/types/campaign.ts +14 -1
- package/src/types/consentflowcontract.ts +43 -2
- package/src/types/credential-refresh.ts +11 -1
- package/src/types/credential.ts +2 -0
- package/test/boost-status-cleanup.spec.ts +123 -0
- package/test/campaigns-email.unit.spec.ts +268 -0
- package/test/campaigns.spec.ts +804 -30
- package/test/consent-autoboost-idempotency.spec.ts +432 -0
- package/test/consent-data-boundaries.spec.ts +322 -26
- package/test/consent-legacy-migration.spec.ts +315 -0
- package/test/consent-recipients.spec.ts +7 -7
- package/test/consent-version-updates.spec.ts +761 -0
- package/test/consentflow.spec.ts +698 -226
- package/test/contract-requests.spec.ts +55 -9
- package/test/credential-refresh-allocation.spec.ts +212 -14
- package/test/credential-refresh-email.spec.ts +6 -10
- package/test/credential-refresh-endpoint.spec.ts +3 -0
- package/test/credential-refresh-publication.spec.ts +52 -15
- package/test/credential-storage.neo4j.spec.ts +33 -6
- package/test/helpers/legacy-autoboost.ts +48 -0
- package/test/holder-mode-send.spec.ts +710 -0
- package/test/inbox-refresh.spec.ts +105 -0
- package/test/inbox-security.e2e.ts +18 -22
- package/test/managed-status-revocation.spec.ts +970 -0
- package/test/notifications-worker.spec.ts +31 -25
- package/test/refresh-send.spec.ts +334 -5
- package/test/smart-resume-consent.spec.ts +1 -1
- package/test/verification/campaigns.ts +40 -0
- package/vitest.campaigns.config.ts +2 -0
- package/vitest.config.ts +14 -0
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { beforeEach, describe, expect, it } from 'vitest';
|
|
2
2
|
import { randomBytes } from 'node:crypto';
|
|
3
3
|
import type { ConsentFlowDataQuery, ConsentFlowTermsInput } from '@learncard/types';
|
|
4
|
-
import { ConsentFlowTerms,
|
|
4
|
+
import { ConsentFlowTerms, ConsentFlowTransaction } from '@models';
|
|
5
|
+
import { neogma } from '@instance';
|
|
6
|
+
import { suspendExpiredConsentFlowTerms } from '@helpers/consentflow-deadline.helpers';
|
|
7
|
+
import { encodeConsentedDataCursor } from '@accesslayer/consentflowcontract/relationships/read';
|
|
5
8
|
import { getUser } from './helpers/getClient';
|
|
6
9
|
import { minimalContract, minimalTerms, normalContract, normalFullTerms } from './helpers/contract';
|
|
7
10
|
|
|
@@ -50,6 +53,27 @@ describe('consented data boundaries', () => {
|
|
|
50
53
|
return { contractUri, termsUri };
|
|
51
54
|
};
|
|
52
55
|
|
|
56
|
+
const seedLegacyExpiry = async (
|
|
57
|
+
kind: 'terms' | 'contract',
|
|
58
|
+
contractUri: string,
|
|
59
|
+
termsUri: string,
|
|
60
|
+
expiresAt: string
|
|
61
|
+
) => {
|
|
62
|
+
if (kind === 'terms') {
|
|
63
|
+
await ConsentFlowTerms.update(
|
|
64
|
+
{ expiresAt },
|
|
65
|
+
{ where: { id: termsUri.split(':').at(-1)! } }
|
|
66
|
+
);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
// Legacy fixtures seed the accepted snapshot as well as the current request.
|
|
70
|
+
await neogma.queryRunner.run(
|
|
71
|
+
`MATCH (contract:ConsentFlowContract {id:$id})-[:HAS_VERSION]->(version:ConsentFlowContractVersion {version:1})
|
|
72
|
+
SET contract.expiresAt = $expiresAt, version.expiresAt = $expiresAt`,
|
|
73
|
+
{ id: contractUri.split(':').at(-1)!, expiresAt }
|
|
74
|
+
);
|
|
75
|
+
};
|
|
76
|
+
|
|
53
77
|
const readAll = async (
|
|
54
78
|
contractUri: string,
|
|
55
79
|
query: ConsentFlowDataQuery = {},
|
|
@@ -148,9 +172,7 @@ describe('consented data boundaries', () => {
|
|
|
148
172
|
'allows locked sync when %s expiry contains only whitespace',
|
|
149
173
|
async kind => {
|
|
150
174
|
const { contractUri, termsUri } = await consent();
|
|
151
|
-
|
|
152
|
-
const uri = kind === 'terms' ? termsUri : contractUri;
|
|
153
|
-
await model.update({ expiresAt: ' ' }, { where: { id: uri.split(':').at(-1)! } });
|
|
175
|
+
await seedLegacyExpiry(kind, contractUri, termsUri, ' ');
|
|
154
176
|
await expect(
|
|
155
177
|
learner.clients.fullAuth.contracts.syncCredentialsToContract({
|
|
156
178
|
termsUri,
|
|
@@ -170,22 +192,18 @@ describe('consented data boundaries', () => {
|
|
|
170
192
|
|
|
171
193
|
it.each(['terms', 'contract'] as const)('excludes expired %s on every route', async kind => {
|
|
172
194
|
const past = '2000-01-01T00:00:00.000Z';
|
|
173
|
-
const { contractUri } = await consent({
|
|
195
|
+
const { contractUri, termsUri } = await consent({
|
|
174
196
|
expiresAt: kind === 'terms' ? past : undefined,
|
|
175
197
|
});
|
|
176
198
|
// Seed an existing consent that subsequently expires, rather than consent to an expired contract.
|
|
177
|
-
if (kind === 'contract')
|
|
178
|
-
await ConsentFlowContract.update(
|
|
179
|
-
{ expiresAt: past },
|
|
180
|
-
{ where: { id: contractUri.split(':').at(-1)! } }
|
|
181
|
-
);
|
|
199
|
+
if (kind === 'contract') await seedLegacyExpiry(kind, contractUri, termsUri, past);
|
|
182
200
|
for (const response of await readAll(contractUri)) expect(response.records).toEqual([]);
|
|
183
201
|
});
|
|
184
202
|
|
|
185
203
|
it('preserves one-time snapshots without permitting ongoing sync, then honors withdrawal', async () => {
|
|
186
204
|
const { contractUri, termsUri } = await consent({ oneTime: true });
|
|
187
205
|
const history = await learner.clients.fullAuth.contracts.getConsentedContracts();
|
|
188
|
-
expect(history.records.find(record => record.uri === termsUri)?.status).toBe('
|
|
206
|
+
expect(history.records.find(record => record.uri === termsUri)?.status).toBe('consumed');
|
|
189
207
|
for (const response of await readAll(contractUri)) {
|
|
190
208
|
expect(response.records).toHaveLength(1);
|
|
191
209
|
expect(JSON.stringify(response)).toContain('achievement1');
|
|
@@ -200,10 +218,28 @@ describe('consented data boundaries', () => {
|
|
|
200
218
|
for (const response of await readAll(contractUri)) expect(response.records).toEqual([]);
|
|
201
219
|
});
|
|
202
220
|
|
|
203
|
-
it('
|
|
221
|
+
it('persists suspension when a re-consent deadline has passed', async () => {
|
|
222
|
+
const { termsUri } = await consent();
|
|
223
|
+
const termsId = termsUri.split(':').at(-1)!;
|
|
224
|
+
await ConsentFlowTerms.update(
|
|
225
|
+
{ status: 'needs_reconsent', reconsentDeadline: '2026-10-14T00:00:00.000Z' },
|
|
226
|
+
{ where: { id: termsId } }
|
|
227
|
+
);
|
|
228
|
+
|
|
229
|
+
await expect(suspendExpiredConsentFlowTerms('2026-10-15T00:00:00.000Z')).resolves.toEqual({
|
|
230
|
+
suspended: 1,
|
|
231
|
+
});
|
|
232
|
+
const terms = await ConsentFlowTerms.findOne({ where: { id: termsId } });
|
|
233
|
+
expect(terms?.dataValues.status).toBe('suspended');
|
|
234
|
+
expect(terms?.dataValues.updatedAt).toBe('2026-10-15T00:00:00.000Z');
|
|
235
|
+
await expect(suspendExpiredConsentFlowTerms('2026-10-15T00:00:00.000Z')).resolves.toEqual({
|
|
236
|
+
suspended: 0,
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
it('excludes consumed access without the explicit one-time flag', async () => {
|
|
204
240
|
const { contractUri, termsUri } = await consent();
|
|
205
241
|
await ConsentFlowTerms.update(
|
|
206
|
-
{ status: '
|
|
242
|
+
{ status: 'consumed' },
|
|
207
243
|
{ where: { id: termsUri.split(':').at(-1)! } }
|
|
208
244
|
);
|
|
209
245
|
for (const response of await readAll(contractUri)) expect(response.records).toEqual([]);
|
|
@@ -278,11 +314,7 @@ describe('consented data boundaries', () => {
|
|
|
278
314
|
'excludes malformed legacy %s expiry without failing the query',
|
|
279
315
|
async kind => {
|
|
280
316
|
const { contractUri, termsUri } = await consent();
|
|
281
|
-
|
|
282
|
-
await model.update(
|
|
283
|
-
{ expiresAt: 'invalid-legacy-date' },
|
|
284
|
-
{ where: { id: (kind === 'terms' ? termsUri : contractUri).split(':').at(-1)! } }
|
|
285
|
-
);
|
|
317
|
+
await seedLegacyExpiry(kind, contractUri, termsUri, 'invalid-legacy-date');
|
|
286
318
|
for (const response of await readAll(contractUri)) expect(response.records).toEqual([]);
|
|
287
319
|
}
|
|
288
320
|
);
|
|
@@ -297,7 +329,63 @@ describe('consented data boundaries', () => {
|
|
|
297
329
|
}
|
|
298
330
|
});
|
|
299
331
|
|
|
300
|
-
it('
|
|
332
|
+
it('keeps accepted snapshots isolated across consenters, contracts, and versions', async () => {
|
|
333
|
+
const first = await consent({
|
|
334
|
+
terms: {
|
|
335
|
+
...structuredClone(normalFullTerms),
|
|
336
|
+
read: { ...normalFullTerms.read, personal: { name: 'first-grant' } },
|
|
337
|
+
},
|
|
338
|
+
});
|
|
339
|
+
const changed = await owner.clients.fullAuth.contracts.updateConsentFlowContract({
|
|
340
|
+
uri: first.contractUri,
|
|
341
|
+
changes: { contract: { read: { personal: { email: { required: true } } } } },
|
|
342
|
+
reason: 'Verify enrolled learners',
|
|
343
|
+
});
|
|
344
|
+
const secondLearner = await getUser(randomBytes(32).toString('hex'));
|
|
345
|
+
await secondLearner.clients.fullAuth.profile.createProfile({
|
|
346
|
+
profileId: `boundary-third-${randomBytes(5).toString('hex')}`,
|
|
347
|
+
});
|
|
348
|
+
await secondLearner.clients.fullAuth.contracts.consentToContract({
|
|
349
|
+
contractUri: first.contractUri,
|
|
350
|
+
terms: {
|
|
351
|
+
...structuredClone(normalFullTerms),
|
|
352
|
+
read: {
|
|
353
|
+
...normalFullTerms.read,
|
|
354
|
+
personal: { name: 'second-grant', email: 'second@example.test' },
|
|
355
|
+
},
|
|
356
|
+
},
|
|
357
|
+
});
|
|
358
|
+
const otherContract = await consent({
|
|
359
|
+
terms: {
|
|
360
|
+
...structuredClone(normalFullTerms),
|
|
361
|
+
read: { ...normalFullTerms.read, personal: { name: 'other-contract' } },
|
|
362
|
+
},
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
const response = await owner.clients.fullAuth.contracts.getConsentedData({ limit: 10 });
|
|
366
|
+
const names = response.records.map(record => record.personal?.name).sort();
|
|
367
|
+
expect(names).toEqual(['first-grant', 'other-contract', 'second-grant']);
|
|
368
|
+
expect(response.records).toHaveLength(3);
|
|
369
|
+
|
|
370
|
+
const firstContract = await owner.clients.fullAuth.contracts.getConsentedDataForContract({
|
|
371
|
+
uri: first.contractUri,
|
|
372
|
+
limit: 10,
|
|
373
|
+
});
|
|
374
|
+
expect(firstContract.records.map(record => record.personal?.name).sort()).toEqual([
|
|
375
|
+
'first-grant',
|
|
376
|
+
'second-grant',
|
|
377
|
+
]);
|
|
378
|
+
expect(
|
|
379
|
+
firstContract.records.find(record => record.personal?.name === 'first-grant')?.personal
|
|
380
|
+
).toEqual({ name: 'first-grant' });
|
|
381
|
+
expect(
|
|
382
|
+
firstContract.records.find(record => record.personal?.name === 'second-grant')?.personal
|
|
383
|
+
).toEqual({ name: 'second-grant', email: 'second@example.test' });
|
|
384
|
+
expect(changed.version).toBe(2);
|
|
385
|
+
expect(otherContract.contractUri).not.toBe(first.contractUri);
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
it('filters expired terms and accepts opaque contract/profile cursors', async () => {
|
|
301
389
|
const { contractUri, termsUri } = await consent();
|
|
302
390
|
const secondLearner = await getUser(randomBytes(32).toString('hex'));
|
|
303
391
|
await secondLearner.clients.fullAuth.profile.createProfile({
|
|
@@ -319,12 +407,19 @@ describe('consented data boundaries', () => {
|
|
|
319
407
|
expect(response.records).toHaveLength(1);
|
|
320
408
|
expect(response.hasMore).toBe(false);
|
|
321
409
|
}
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
410
|
+
const cursor = encodeConsentedDataCursor(
|
|
411
|
+
'2001-01-01T00:00:00Z',
|
|
412
|
+
termsUri.split(':').at(-1)!
|
|
413
|
+
);
|
|
414
|
+
for (const page of await Promise.all([
|
|
415
|
+
owner.clients.fullAuth.contracts.getConsentedDataForContract({
|
|
416
|
+
uri: contractUri,
|
|
417
|
+
cursor,
|
|
418
|
+
limit: 1,
|
|
419
|
+
}),
|
|
420
|
+
owner.clients.fullAuth.contracts.getConsentedData({ cursor, limit: 1 }),
|
|
421
|
+
])) {
|
|
422
|
+
expect(page.records).toEqual([]);
|
|
328
423
|
}
|
|
329
424
|
});
|
|
330
425
|
|
|
@@ -341,7 +436,6 @@ describe('consented data boundaries', () => {
|
|
|
341
436
|
});
|
|
342
437
|
expect(response.records).toHaveLength(1);
|
|
343
438
|
expect(response.hasMore).toBe(false);
|
|
344
|
-
expect(response.cursor).toBe('2001-01-01T00:00:00Z');
|
|
345
439
|
const next = await owner.clients.fullAuth.contracts.getConsentedDataForDid({
|
|
346
440
|
did: learnerDid,
|
|
347
441
|
limit: 1,
|
|
@@ -349,4 +443,206 @@ describe('consented data boundaries', () => {
|
|
|
349
443
|
});
|
|
350
444
|
expect(next.records).toEqual([]);
|
|
351
445
|
});
|
|
446
|
+
it('keyset-paginates tied timestamps and advances over expired scanned terms', async () => {
|
|
447
|
+
const contractUri = await owner.clients.fullAuth.contracts.createConsentFlowContract({
|
|
448
|
+
name: 'Tied-page regression',
|
|
449
|
+
contract: normalContract,
|
|
450
|
+
});
|
|
451
|
+
const termIds: string[] = [];
|
|
452
|
+
for (let index = 0; index < 51; index++) {
|
|
453
|
+
const holder = await getUser(randomBytes(32).toString('hex'));
|
|
454
|
+
await holder.clients.fullAuth.profile.createProfile({
|
|
455
|
+
profileId: `tied-holder-${randomBytes(5).toString('hex')}`,
|
|
456
|
+
});
|
|
457
|
+
const { termsUri } = await holder.clients.fullAuth.contracts.consentToContract({
|
|
458
|
+
contractUri,
|
|
459
|
+
terms: {
|
|
460
|
+
...normalFullTerms,
|
|
461
|
+
read: {
|
|
462
|
+
...normalFullTerms.read,
|
|
463
|
+
personal: { name: `Holder ${index}` },
|
|
464
|
+
},
|
|
465
|
+
},
|
|
466
|
+
});
|
|
467
|
+
termIds.push(termsUri.split(':').at(-1)!);
|
|
468
|
+
}
|
|
469
|
+
await owner.clients.fullAuth.contracts.updateConsentFlowContract({
|
|
470
|
+
uri: contractUri,
|
|
471
|
+
changes: { subtitle: 'One publication updates the entire cohort' },
|
|
472
|
+
});
|
|
473
|
+
const expired = await neogma.queryRunner.run(
|
|
474
|
+
`MATCH (terms:ConsentFlowTerms) WHERE terms.id IN $ids
|
|
475
|
+
WITH terms ORDER BY terms.id SKIP 24 LIMIT 1
|
|
476
|
+
SET terms.expiresAt='2000-01-01T00:00:00Z'
|
|
477
|
+
RETURN terms['terms.read.personal.name'] AS name`,
|
|
478
|
+
{ ids: termIds }
|
|
479
|
+
);
|
|
480
|
+
const expiredName = String(expired.records[0].get('name'));
|
|
481
|
+
const expectedNames = new Set(
|
|
482
|
+
Array.from({ length: 51 }, (_, index) => `Holder ${index}`).filter(
|
|
483
|
+
name => name !== expiredName
|
|
484
|
+
)
|
|
485
|
+
);
|
|
486
|
+
const readers = [
|
|
487
|
+
(cursor?: string) =>
|
|
488
|
+
owner.clients.fullAuth.contracts.getConsentedDataForContract({
|
|
489
|
+
uri: contractUri,
|
|
490
|
+
limit: 25,
|
|
491
|
+
cursor,
|
|
492
|
+
}),
|
|
493
|
+
(cursor?: string) =>
|
|
494
|
+
owner.clients.fullAuth.contracts.getConsentedData({
|
|
495
|
+
limit: 25,
|
|
496
|
+
cursor,
|
|
497
|
+
}),
|
|
498
|
+
];
|
|
499
|
+
for (const read of readers) {
|
|
500
|
+
const seenNames = new Set<string>();
|
|
501
|
+
let cursor: string | undefined;
|
|
502
|
+
do {
|
|
503
|
+
const page = await read(cursor);
|
|
504
|
+
for (const record of page.records) {
|
|
505
|
+
const name = record.personal.name;
|
|
506
|
+
expect(seenNames.has(name)).toBe(false);
|
|
507
|
+
seenNames.add(name);
|
|
508
|
+
}
|
|
509
|
+
if (!page.hasMore) break;
|
|
510
|
+
expect(page.cursor).toBeDefined();
|
|
511
|
+
expect(page.cursor).not.toBe(cursor);
|
|
512
|
+
cursor = page.cursor;
|
|
513
|
+
} while (cursor);
|
|
514
|
+
expect(seenNames).toEqual(expectedNames);
|
|
515
|
+
}
|
|
516
|
+
});
|
|
517
|
+
it('rejects malformed and obsolete date-only cursors with BAD_REQUEST', async () => {
|
|
518
|
+
const { contractUri, termsUri } = await consent();
|
|
519
|
+
const requests = [
|
|
520
|
+
() =>
|
|
521
|
+
owner.clients.fullAuth.contracts.getConsentedDataForContract({
|
|
522
|
+
uri: contractUri,
|
|
523
|
+
limit: 1,
|
|
524
|
+
cursor: '2025-01-01T00:00:00Z',
|
|
525
|
+
}),
|
|
526
|
+
() =>
|
|
527
|
+
owner.clients.fullAuth.contracts.getConsentedData({
|
|
528
|
+
limit: 1,
|
|
529
|
+
cursor: 'not-a-cursor',
|
|
530
|
+
}),
|
|
531
|
+
() =>
|
|
532
|
+
owner.clients.fullAuth.contracts.getConsentedDataForDid({
|
|
533
|
+
did: learnerDid,
|
|
534
|
+
limit: 1,
|
|
535
|
+
cursor: '2025-01-01T00:00:00Z',
|
|
536
|
+
}),
|
|
537
|
+
() =>
|
|
538
|
+
learner.clients.fullAuth.contracts.getConsentedContracts({
|
|
539
|
+
limit: 1,
|
|
540
|
+
cursor: '2025-01-01T00:00:00Z',
|
|
541
|
+
}),
|
|
542
|
+
() =>
|
|
543
|
+
learner.clients.fullAuth.contracts.getTermsTransactionHistory({
|
|
544
|
+
uri: termsUri,
|
|
545
|
+
limit: 1,
|
|
546
|
+
cursor: 'not-a-cursor',
|
|
547
|
+
}),
|
|
548
|
+
];
|
|
549
|
+
for (const request of requests) {
|
|
550
|
+
await expect(request()).rejects.toMatchObject({ code: 'BAD_REQUEST' });
|
|
551
|
+
}
|
|
552
|
+
});
|
|
553
|
+
|
|
554
|
+
it('paginates tied holder-list, export, and between-profile cohorts with null update dates', async () => {
|
|
555
|
+
const termsIds: string[] = [];
|
|
556
|
+
for (let index = 0; index < 101; index++) {
|
|
557
|
+
const { termsUri } = await consent();
|
|
558
|
+
termsIds.push(termsUri.split(':').at(-1)!);
|
|
559
|
+
}
|
|
560
|
+
await neogma.queryRunner.run(
|
|
561
|
+
`MATCH (terms:ConsentFlowTerms) WHERE terms.id IN $ids
|
|
562
|
+
SET terms.updatedAt = '2020-01-01T00:00:00Z'`,
|
|
563
|
+
{ ids: termsIds }
|
|
564
|
+
);
|
|
565
|
+
const nullTimestampId = termsIds.at(-1)!;
|
|
566
|
+
await neogma.queryRunner.run(
|
|
567
|
+
`MATCH (terms:ConsentFlowTerms {id:$id}) REMOVE terms.updatedAt`,
|
|
568
|
+
{ id: nullTimestampId }
|
|
569
|
+
);
|
|
570
|
+
|
|
571
|
+
const seenTerms = new Set<string>();
|
|
572
|
+
let cursor: string | undefined;
|
|
573
|
+
do {
|
|
574
|
+
const page = await learner.clients.fullAuth.contracts.getConsentedContracts({
|
|
575
|
+
limit: 25,
|
|
576
|
+
cursor,
|
|
577
|
+
});
|
|
578
|
+
for (const record of page.records) {
|
|
579
|
+
expect(seenTerms.has(record.uri)).toBe(false);
|
|
580
|
+
seenTerms.add(record.uri);
|
|
581
|
+
}
|
|
582
|
+
if (!page.hasMore) break;
|
|
583
|
+
expect(page.cursor).toBeDefined();
|
|
584
|
+
cursor = page.cursor;
|
|
585
|
+
} while (cursor);
|
|
586
|
+
expect(seenTerms.size).toBe(101);
|
|
587
|
+
|
|
588
|
+
const exported = await learner.clients.fullAuth.credential.getHolderExportMetadata();
|
|
589
|
+
expect(new Set(exported.consentRecords.map(record => record.termsUri))).toEqual(seenTerms);
|
|
590
|
+
|
|
591
|
+
const betweenTerms = new Set<string>();
|
|
592
|
+
cursor = undefined;
|
|
593
|
+
do {
|
|
594
|
+
const page = await owner.clients.fullAuth.contracts.getConsentedDataForDid({
|
|
595
|
+
did: learnerDid,
|
|
596
|
+
limit: 25,
|
|
597
|
+
cursor,
|
|
598
|
+
});
|
|
599
|
+
for (const record of page.records) {
|
|
600
|
+
expect(betweenTerms.has(record.termsUri)).toBe(false);
|
|
601
|
+
betweenTerms.add(record.termsUri);
|
|
602
|
+
}
|
|
603
|
+
if (!page.hasMore) break;
|
|
604
|
+
expect(page.cursor).toBeDefined();
|
|
605
|
+
cursor = page.cursor;
|
|
606
|
+
} while (cursor);
|
|
607
|
+
expect(betweenTerms.size).toBe(101);
|
|
608
|
+
});
|
|
609
|
+
|
|
610
|
+
it('keeps tied transaction history complete in public pages and holder exports', async () => {
|
|
611
|
+
const { termsUri } = await consent();
|
|
612
|
+
const terms = structuredClone(normalFullTerms);
|
|
613
|
+
for (let index = 0; index < 101; index++) {
|
|
614
|
+
terms.read.personal.name = `Saved choice ${index}`;
|
|
615
|
+
await learner.clients.fullAuth.contracts.updateConsentedContractTerms({
|
|
616
|
+
uri: termsUri,
|
|
617
|
+
terms,
|
|
618
|
+
});
|
|
619
|
+
}
|
|
620
|
+
const tied = await neogma.queryRunner.run(
|
|
621
|
+
`MATCH (transaction:ConsentFlowTransaction)-[:${ConsentFlowTransaction.getRelationshipByAlias('isFor').name}]->(:ConsentFlowTerms {id:$id})
|
|
622
|
+
SET transaction.date='2020-01-01T00:00:00Z'
|
|
623
|
+
RETURN transaction.id AS id`,
|
|
624
|
+
{ id: termsUri.split(':').at(-1)! }
|
|
625
|
+
);
|
|
626
|
+
const expected = new Set(tied.records.map(record => String(record.get('id'))));
|
|
627
|
+
expect(expected.size).toBe(102);
|
|
628
|
+
const seen = new Set<string>();
|
|
629
|
+
let cursor: string | undefined;
|
|
630
|
+
do {
|
|
631
|
+
const page = await learner.clients.fullAuth.contracts.getTermsTransactionHistory({
|
|
632
|
+
uri: termsUri,
|
|
633
|
+
limit: 25,
|
|
634
|
+
cursor,
|
|
635
|
+
});
|
|
636
|
+
for (const record of page.records) {
|
|
637
|
+
expect(seen.has(record.id)).toBe(false);
|
|
638
|
+
seen.add(record.id);
|
|
639
|
+
}
|
|
640
|
+
cursor = page.hasMore ? page.cursor : undefined;
|
|
641
|
+
} while (cursor);
|
|
642
|
+
expect(seen).toEqual(expected);
|
|
643
|
+
const exported = await learner.clients.fullAuth.credential.getHolderExportMetadata();
|
|
644
|
+
const record = exported.consentRecords.find(record => record.termsUri === termsUri);
|
|
645
|
+
expect(record?.transactions).toHaveLength(expected.size);
|
|
646
|
+
expect(new Set(record!.transactions.map(transaction => transaction.id))).toEqual(expected);
|
|
647
|
+
});
|
|
352
648
|
});
|