@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
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
} from './credential-refresh-materiality.helpers';
|
|
21
21
|
import { getNotificationMessage } from './notificationMessages';
|
|
22
22
|
import { resolveRecipientLocale } from './getRecipientLocale.helpers';
|
|
23
|
+
import { fetchNotificationWebhook } from './notification-webhook-fetch.helpers';
|
|
23
24
|
|
|
24
25
|
// Timeout value in milliseconds for aborting the request
|
|
25
26
|
const TIMEOUT = 6000;
|
|
@@ -357,18 +358,25 @@ export async function sendNotification(
|
|
|
357
358
|
// Set a timeout to abort the fetch request
|
|
358
359
|
const timeoutId = setTimeout(() => controller.abort(), TIMEOUT);
|
|
359
360
|
|
|
360
|
-
let response: Response
|
|
361
|
+
let response: Pick<Response, 'ok' | 'status' | 'text'>;
|
|
362
|
+
let responseText: string;
|
|
361
363
|
try {
|
|
362
364
|
directWebhookRequestStarted = true;
|
|
363
|
-
response = await
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
365
|
+
response = await fetchNotificationWebhook(
|
|
366
|
+
notificationsWebhook,
|
|
367
|
+
{
|
|
368
|
+
method: 'POST',
|
|
369
|
+
headers: {
|
|
370
|
+
'Content-Type': 'application/json',
|
|
371
|
+
'Authorization': `Bearer ${didJwt}`,
|
|
372
|
+
},
|
|
373
|
+
body: JSON.stringify(notification),
|
|
374
|
+
signal,
|
|
368
375
|
},
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
376
|
+
runtimeEnvironment.IS_OFFLINE
|
|
377
|
+
);
|
|
378
|
+
// Keep the deadline active while reading the acknowledgement body too.
|
|
379
|
+
responseText = await response.text();
|
|
372
380
|
} finally {
|
|
373
381
|
clearTimeout(timeoutId);
|
|
374
382
|
}
|
|
@@ -386,8 +394,6 @@ export async function sendNotification(
|
|
|
386
394
|
throw createWebhookTransportError(response.status);
|
|
387
395
|
}
|
|
388
396
|
|
|
389
|
-
const responseText = await response.text();
|
|
390
|
-
|
|
391
397
|
let responseBody: unknown;
|
|
392
398
|
if (responseText.trim().length > 0) {
|
|
393
399
|
try {
|
|
@@ -398,7 +404,8 @@ export async function sendNotification(
|
|
|
398
404
|
}
|
|
399
405
|
|
|
400
406
|
if (
|
|
401
|
-
notification.data?.metadata?.connectionPrompt
|
|
407
|
+
(notification.data?.metadata?.connectionPrompt ||
|
|
408
|
+
notification.type === 'ANNOUNCEMENT') &&
|
|
402
409
|
extractNotificationWebhookSuccess(responseBody) === null
|
|
403
410
|
) {
|
|
404
411
|
throw createUnrecognizedWebhookAcknowledgementError(response.status);
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
|
|
3
|
+
vi.mock('@instance', () => ({ neogma: {} }));
|
|
4
|
+
vi.mock('./credential-refresh.helpers', () => ({}));
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
computeRefreshSendRequestDigest,
|
|
8
|
+
matchesRefreshSendRequestDigest,
|
|
9
|
+
} from './refresh-send-intent.helpers';
|
|
10
|
+
|
|
11
|
+
describe('managed send metadata digest', () => {
|
|
12
|
+
afterEach(() => vi.unstubAllEnvs());
|
|
13
|
+
const request = { recipientProfileId: 'recipient', holderDid: 'did:example:holder' };
|
|
14
|
+
|
|
15
|
+
it('matches current and retained keys without accepting removed keys or changed metadata', () => {
|
|
16
|
+
vi.stubEnv('CREDENTIAL_REFRESH_DIGEST_SECRET', 'old-send-test-key');
|
|
17
|
+
vi.stubEnv('CREDENTIAL_REFRESH_DIGEST_KEY_VERSION', 'v1');
|
|
18
|
+
vi.stubEnv('CREDENTIAL_REFRESH_DIGEST_PREVIOUS_KEYS', '{}');
|
|
19
|
+
const stored = computeRefreshSendRequestDigest(request);
|
|
20
|
+
expect(matchesRefreshSendRequestDigest(request, stored)).toBe(true);
|
|
21
|
+
vi.stubEnv('CREDENTIAL_REFRESH_DIGEST_SECRET', 'new-send-test-key');
|
|
22
|
+
vi.stubEnv('CREDENTIAL_REFRESH_DIGEST_KEY_VERSION', 'v2');
|
|
23
|
+
vi.stubEnv(
|
|
24
|
+
'CREDENTIAL_REFRESH_DIGEST_PREVIOUS_KEYS',
|
|
25
|
+
JSON.stringify({ v1: 'old-send-test-key' })
|
|
26
|
+
);
|
|
27
|
+
expect(matchesRefreshSendRequestDigest(request, stored)).toBe(true);
|
|
28
|
+
expect(
|
|
29
|
+
matchesRefreshSendRequestDigest(request, computeRefreshSendRequestDigest(request))
|
|
30
|
+
).toBe(true);
|
|
31
|
+
expect(
|
|
32
|
+
matchesRefreshSendRequestDigest({ ...request, integrationId: 'other' }, stored)
|
|
33
|
+
).toBe(false);
|
|
34
|
+
expect(computeRefreshSendRequestDigest(request)).toMatch(/^v1:[A-Za-z0-9_-]{43}$/);
|
|
35
|
+
vi.stubEnv('CREDENTIAL_REFRESH_DIGEST_PREVIOUS_KEYS', '{}');
|
|
36
|
+
expect(matchesRefreshSendRequestDigest(request, stored)).toBe(false);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('ignores claim content and uses a versioned HMAC', () => {
|
|
40
|
+
const original = {
|
|
41
|
+
...request,
|
|
42
|
+
template: { name: 'PRIVATE' },
|
|
43
|
+
templateData: { grade: 'A' },
|
|
44
|
+
};
|
|
45
|
+
const changed = { ...request, template: { name: 'OTHER' }, templateData: { grade: 'F' } };
|
|
46
|
+
expect(computeRefreshSendRequestDigest(original)).toMatch(/^v1:[A-Za-z0-9_-]{43}$/);
|
|
47
|
+
expect(computeRefreshSendRequestDigest(original)).toBe(
|
|
48
|
+
computeRefreshSendRequestDigest(changed)
|
|
49
|
+
);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('binds explicitly public display and routing metadata', () => {
|
|
53
|
+
expect(
|
|
54
|
+
computeRefreshSendRequestDigest({ ...request, display: { name: 'Public' } })
|
|
55
|
+
).not.toBe(computeRefreshSendRequestDigest(request));
|
|
56
|
+
expect(computeRefreshSendRequestDigest({ ...request, integrationId: 'other' })).not.toBe(
|
|
57
|
+
computeRefreshSendRequestDigest(request)
|
|
58
|
+
);
|
|
59
|
+
});
|
|
60
|
+
});
|
|
@@ -1,11 +1,16 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createHmac, randomUUID } from 'crypto';
|
|
2
2
|
import { TRPCError } from '@trpc/server';
|
|
3
3
|
import type { ManagedCredentialRefreshReceipt, SendBoostResponse } from '@learncard/types';
|
|
4
|
+
import { getStatusEntriesForCredential, getStatusManagement } from './status-list.helpers';
|
|
4
5
|
import { canonicalizeCredentialJson } from '@learncard/helpers';
|
|
5
6
|
|
|
6
7
|
import { neogma } from '@instance';
|
|
7
8
|
|
|
8
9
|
import { getInitialRefreshRoot, getManagedCredentialUri } from './credential-refresh.helpers';
|
|
10
|
+
import {
|
|
11
|
+
getCredentialRefreshDigestSecret,
|
|
12
|
+
getCredentialRefreshDigestCandidateSecrets,
|
|
13
|
+
} from './credential-refresh-materiality.helpers';
|
|
9
14
|
|
|
10
15
|
/** A preparation not touched for this long is considered abandoned and may be taken over. */
|
|
11
16
|
export const REFRESH_SEND_INTENT_STALE_MS = 5 * 60_000;
|
|
@@ -26,6 +31,8 @@ export type RefreshSendIntent = {
|
|
|
26
31
|
credentialId?: string;
|
|
27
32
|
refreshId?: string;
|
|
28
33
|
holderDid?: string;
|
|
34
|
+
contractUri?: string;
|
|
35
|
+
integrationId?: string;
|
|
29
36
|
pendingReceipt?: ManagedCredentialRefreshReceipt;
|
|
30
37
|
pendingActivityId?: string;
|
|
31
38
|
result?: SendBoostResponse;
|
|
@@ -40,32 +47,58 @@ export type ClaimRefreshSendIntentResult =
|
|
|
40
47
|
const getIntentKey = (issuerProfileId: string, idempotencyKey: string): string =>
|
|
41
48
|
`${issuerProfileId}:${idempotencyKey}`;
|
|
42
49
|
|
|
43
|
-
/**
|
|
44
|
-
export const computeRefreshSendRequestDigest = (
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
50
|
+
/** Versioned, domain-separated public-metadata digest. Legacy unkeyed intents fail closed. */
|
|
51
|
+
export const computeRefreshSendRequestDigest = (
|
|
52
|
+
request: {
|
|
53
|
+
statusBinding?: 'server';
|
|
54
|
+
statusPurposes?: ('revocation' | 'suspension')[];
|
|
55
|
+
recipientProfileId: string;
|
|
56
|
+
holderDid: string;
|
|
57
|
+
credentialId?: string;
|
|
58
|
+
templateUri?: string;
|
|
59
|
+
display?: import('@learncard/types').CredentialDisplay;
|
|
60
|
+
contractUri?: string;
|
|
61
|
+
integrationId?: string;
|
|
62
|
+
/** Included so an idempotencyKey can never silently replay across modes. */
|
|
63
|
+
encryption?: 'network' | 'holder';
|
|
64
|
+
},
|
|
65
|
+
secret: string = getCredentialRefreshDigestSecret()
|
|
66
|
+
): string =>
|
|
67
|
+
'v1:' +
|
|
68
|
+
createHmac('sha256', secret)
|
|
69
|
+
.update('refresh-send-intent:v1\0')
|
|
55
70
|
.update(
|
|
56
71
|
canonicalizeCredentialJson({
|
|
57
72
|
recipientProfileId: request.recipientProfileId,
|
|
58
73
|
holderDid: request.holderDid,
|
|
59
74
|
templateUri: request.templateUri ?? null,
|
|
60
|
-
|
|
75
|
+
display: request.display ?? {},
|
|
61
76
|
contractUri: request.contractUri ?? null,
|
|
62
|
-
templateData: request.templateData ?? {},
|
|
63
77
|
integrationId: request.integrationId ?? null,
|
|
78
|
+
refresh: true,
|
|
64
79
|
credentialId: request.credentialId ?? null,
|
|
80
|
+
encryption: request.encryption ?? 'network',
|
|
81
|
+
...(request.statusBinding ? { statusBinding: request.statusBinding } : {}),
|
|
82
|
+
...(request.statusPurposes?.includes('suspension')
|
|
83
|
+
? { suspensionRequested: true }
|
|
84
|
+
: {}),
|
|
65
85
|
})
|
|
66
86
|
)
|
|
67
87
|
.digest('base64url');
|
|
68
88
|
|
|
89
|
+
/** The v1 prefix identifies the request scheme, not the rotating key version. */
|
|
90
|
+
export const getRefreshSendRequestDigestCandidates = (
|
|
91
|
+
request: Parameters<typeof computeRefreshSendRequestDigest>[0]
|
|
92
|
+
): string[] =>
|
|
93
|
+
getCredentialRefreshDigestCandidateSecrets().map(secret =>
|
|
94
|
+
computeRefreshSendRequestDigest(request, secret)
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
export const matchesRefreshSendRequestDigest = (
|
|
98
|
+
request: Parameters<typeof computeRefreshSendRequestDigest>[0],
|
|
99
|
+
stored: string
|
|
100
|
+
): boolean => getRefreshSendRequestDigestCandidates(request).includes(stored);
|
|
101
|
+
|
|
69
102
|
const parseIntent = (props: Record<string, unknown>): RefreshSendIntent => ({
|
|
70
103
|
intentKey: props.intentKey as string,
|
|
71
104
|
issuerProfileId: props.issuerProfileId as string,
|
|
@@ -76,6 +109,8 @@ const parseIntent = (props: Record<string, unknown>): RefreshSendIntent => ({
|
|
|
76
109
|
credentialId: (props.credentialId as string | undefined) ?? undefined,
|
|
77
110
|
refreshId: (props.refreshId as string | undefined) ?? undefined,
|
|
78
111
|
holderDid: (props.holderDid as string | undefined) ?? undefined,
|
|
112
|
+
contractUri: (props.contractUri as string | undefined) ?? undefined,
|
|
113
|
+
integrationId: (props.integrationId as string | undefined) ?? undefined,
|
|
79
114
|
pendingReceipt: props.pendingReceipt
|
|
80
115
|
? (JSON.parse(props.pendingReceipt as string) as ManagedCredentialRefreshReceipt)
|
|
81
116
|
: undefined,
|
|
@@ -97,6 +132,19 @@ export const getRefreshSendIntent = async (
|
|
|
97
132
|
return node ? parseIntent(node.properties) : null;
|
|
98
133
|
};
|
|
99
134
|
|
|
135
|
+
/** Resolve the preparation even when the caller did not supply an idempotency key. */
|
|
136
|
+
export const getRefreshSendIntentForRefresh = async (
|
|
137
|
+
issuerProfileId: string,
|
|
138
|
+
refreshId: string
|
|
139
|
+
): Promise<RefreshSendIntent | null> => {
|
|
140
|
+
const result = await neogma.queryRunner.run(
|
|
141
|
+
'MATCH (i:RefreshSendIntent {issuerProfileId: $issuerProfileId, refreshId: $refreshId}) RETURN i',
|
|
142
|
+
{ issuerProfileId, refreshId }
|
|
143
|
+
);
|
|
144
|
+
const node = result.records[0]?.get('i');
|
|
145
|
+
return node ? parseIntent(node.properties) : null;
|
|
146
|
+
};
|
|
147
|
+
|
|
100
148
|
/**
|
|
101
149
|
* Creates or claims the intent for (issuer, idempotencyKey). Concurrency-safe through
|
|
102
150
|
* the `refresh_send_intent_key_unique` constraint. Throws CONFLICT when the key was used
|
|
@@ -106,8 +154,10 @@ export const claimRefreshSendIntent = async (params: {
|
|
|
106
154
|
issuerProfileId: string;
|
|
107
155
|
idempotencyKey: string;
|
|
108
156
|
requestDigest: string;
|
|
157
|
+
requestDigestCandidates?: string[];
|
|
109
158
|
}): Promise<ClaimRefreshSendIntentResult> => {
|
|
110
159
|
const { issuerProfileId, idempotencyKey, requestDigest } = params;
|
|
160
|
+
const requestDigestCandidates = params.requestDigestCandidates ?? [requestDigest];
|
|
111
161
|
const claimToken = randomUUID();
|
|
112
162
|
const now = new Date();
|
|
113
163
|
|
|
@@ -125,7 +175,7 @@ export const claimRefreshSendIntent = async (params: {
|
|
|
125
175
|
WITH i
|
|
126
176
|
WITH i WHERE i.state = 'preparing'
|
|
127
177
|
AND i.claimToken <> $claimToken
|
|
128
|
-
|
|
178
|
+
AND i.requestDigest IN $requestDigestCandidates
|
|
129
179
|
AND i.updatedAt < $staleBefore
|
|
130
180
|
SET i.claimToken = $claimToken, i.updatedAt = $now
|
|
131
181
|
}
|
|
@@ -134,6 +184,7 @@ export const claimRefreshSendIntent = async (params: {
|
|
|
134
184
|
intentKey: getIntentKey(issuerProfileId, idempotencyKey),
|
|
135
185
|
issuerProfileId,
|
|
136
186
|
requestDigest,
|
|
187
|
+
requestDigestCandidates,
|
|
137
188
|
claimToken,
|
|
138
189
|
now: now.toISOString(),
|
|
139
190
|
staleBefore: new Date(now.getTime() - REFRESH_SEND_INTENT_STALE_MS).toISOString(),
|
|
@@ -141,7 +192,7 @@ export const claimRefreshSendIntent = async (params: {
|
|
|
141
192
|
);
|
|
142
193
|
const intent = parseIntent(result.records[0]!.get('i').properties);
|
|
143
194
|
|
|
144
|
-
if (intent.requestDigest
|
|
195
|
+
if (!requestDigestCandidates.includes(intent.requestDigest)) {
|
|
145
196
|
throw new TRPCError({
|
|
146
197
|
code: 'CONFLICT',
|
|
147
198
|
message: 'This idempotencyKey was already used for a different send request.',
|
|
@@ -173,7 +224,15 @@ const lostIntentClaim = (): TRPCError =>
|
|
|
173
224
|
export const recordRefreshSendIntent = async (
|
|
174
225
|
intent: RefreshSendIntent,
|
|
175
226
|
fields: Partial<
|
|
176
|
-
Pick<
|
|
227
|
+
Pick<
|
|
228
|
+
RefreshSendIntent,
|
|
229
|
+
| 'boostUri'
|
|
230
|
+
| 'credentialId'
|
|
231
|
+
| 'refreshId'
|
|
232
|
+
| 'holderDid'
|
|
233
|
+
| 'contractUri'
|
|
234
|
+
| 'integrationId'
|
|
235
|
+
>
|
|
177
236
|
> & { state?: 'prepared' }
|
|
178
237
|
): Promise<RefreshSendIntent> => {
|
|
179
238
|
const result = await neogma.queryRunner.run(
|
|
@@ -265,6 +324,10 @@ export const reconcileBoundRefreshSendIntent = async (
|
|
|
265
324
|
credentialUri: getManagedCredentialUri(root.rootId, domain),
|
|
266
325
|
activityId: intent.pendingActivityId ?? '',
|
|
267
326
|
refresh: intent.pendingReceipt,
|
|
327
|
+
statusManagement: getStatusManagement(
|
|
328
|
+
await getStatusEntriesForCredential(root.rootId),
|
|
329
|
+
domain
|
|
330
|
+
),
|
|
268
331
|
};
|
|
269
332
|
|
|
270
333
|
await markRefreshSendIntentDelivered(intent, result);
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { TRPCError } from '@trpc/server';
|
|
2
|
+
import type { BoostInstance } from '@models';
|
|
3
|
+
import type { ProfileType } from 'types/profile';
|
|
4
|
+
import {
|
|
5
|
+
getContractDetailsByUri,
|
|
6
|
+
getContractTermsForProfile,
|
|
7
|
+
getWritersForContract,
|
|
8
|
+
} from '@accesslayer/consentflowcontract/relationships/read';
|
|
9
|
+
import { setRelatedBoostForContract } from '@accesslayer/consentflowcontract/relationships/create';
|
|
10
|
+
|
|
11
|
+
/** Resolve current send permissions; delivery must recheck the returned terms transactionally. */
|
|
12
|
+
export const resolveContractForSend = async (params: {
|
|
13
|
+
profile: ProfileType;
|
|
14
|
+
targetProfile: ProfileType;
|
|
15
|
+
boost: BoostInstance;
|
|
16
|
+
boostCreated: boolean;
|
|
17
|
+
contractUri?: string;
|
|
18
|
+
}): Promise<Awaited<ReturnType<typeof getContractTermsForProfile>> | null> => {
|
|
19
|
+
const { profile, targetProfile, boost, boostCreated, contractUri } = params;
|
|
20
|
+
if (!contractUri) return null;
|
|
21
|
+
|
|
22
|
+
const details = await getContractDetailsByUri(decodeURIComponent(contractUri));
|
|
23
|
+
if (!details) {
|
|
24
|
+
throw new TRPCError({ code: 'NOT_FOUND', message: 'Could not find contract' });
|
|
25
|
+
}
|
|
26
|
+
const terms = await getContractTermsForProfile(targetProfile, details.contract);
|
|
27
|
+
const writers = await getWritersForContract(details.contract);
|
|
28
|
+
const allowed =
|
|
29
|
+
terms &&
|
|
30
|
+
writers.some(writer => writer.profileId === profile.profileId) &&
|
|
31
|
+
!terms.terms.deniedWriters?.includes(profile.profileId) &&
|
|
32
|
+
(!boost.category || terms.terms.write?.credentials?.categories?.[boost.category] === true);
|
|
33
|
+
|
|
34
|
+
if (boostCreated) await setRelatedBoostForContract(details.contract, boost);
|
|
35
|
+
return allowed ? terms : null;
|
|
36
|
+
};
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
2
|
|
|
3
|
-
const mocks = vi.hoisted(() => ({
|
|
3
|
+
const mocks = vi.hoisted(() => ({ run: vi.fn(), session: vi.fn(), transaction: vi.fn() }));
|
|
4
4
|
vi.mock('@environment', () => ({ environment: {} }));
|
|
5
|
-
vi.mock('@models', () => ({ Credential: { findOne: mocks.findOne } }));
|
|
6
5
|
vi.mock('@instance', () => ({
|
|
7
|
-
neogma: {
|
|
6
|
+
neogma: {
|
|
7
|
+
driver: { session: mocks.session },
|
|
8
|
+
getTransaction: mocks.transaction,
|
|
9
|
+
queryRunner: { run: mocks.run },
|
|
10
|
+
},
|
|
8
11
|
}));
|
|
9
12
|
vi.mock('@helpers/learnCard.helpers', () => ({
|
|
10
13
|
getLearnCard: vi.fn(),
|
|
@@ -13,6 +16,21 @@ vi.mock('@helpers/learnCard.helpers', () => ({
|
|
|
13
16
|
|
|
14
17
|
import { setCredentialBitstringStatus } from './status-list.helpers';
|
|
15
18
|
|
|
19
|
+
type StoredRow = { direct?: string; body?: string; entries?: string };
|
|
20
|
+
|
|
21
|
+
/** Mock the stored-status lookup, then the issuer-owner lookup. */
|
|
22
|
+
const mockStored = (row: StoredRow | null, owner: string | null = 'issuer') => {
|
|
23
|
+
const record = (values: Record<string, unknown>) => ({
|
|
24
|
+
get: (key: string) => values[key] ?? null,
|
|
25
|
+
});
|
|
26
|
+
mocks.run.mockImplementation(async (query: string) => {
|
|
27
|
+
if (query.includes('AS owner')) {
|
|
28
|
+
return { records: owner ? [record({ owner })] : [] };
|
|
29
|
+
}
|
|
30
|
+
return { records: row ? [record({ ...row })] : [] };
|
|
31
|
+
});
|
|
32
|
+
};
|
|
33
|
+
|
|
16
34
|
describe('stored credential status validation', () => {
|
|
17
35
|
beforeEach(() => {
|
|
18
36
|
vi.clearAllMocks();
|
|
@@ -21,7 +39,7 @@ describe('stored credential status validation', () => {
|
|
|
21
39
|
it.each(['{truncated', 'null', '{}', '[null]', '[{}]', '"not an array"'])(
|
|
22
40
|
'returns false for malformed status metadata (%s) without starting a status write',
|
|
23
41
|
async statusEntries => {
|
|
24
|
-
|
|
42
|
+
mockStored({ direct: statusEntries, body: '{}' });
|
|
25
43
|
await expect(
|
|
26
44
|
setCredentialBitstringStatus('credential', 'revocation', true)
|
|
27
45
|
).resolves.toBe(false);
|
|
@@ -30,7 +48,7 @@ describe('stored credential status validation', () => {
|
|
|
30
48
|
);
|
|
31
49
|
|
|
32
50
|
it('returns false when legacy credential JSON cannot be parsed', async () => {
|
|
33
|
-
|
|
51
|
+
mockStored({ body: '{truncated' });
|
|
34
52
|
await expect(setCredentialBitstringStatus('credential', 'suspension', true)).resolves.toBe(
|
|
35
53
|
false
|
|
36
54
|
);
|
|
@@ -38,16 +56,15 @@ describe('stored credential status validation', () => {
|
|
|
38
56
|
});
|
|
39
57
|
|
|
40
58
|
it('returns false for valid empty metadata or a missing credential', async () => {
|
|
41
|
-
|
|
42
|
-
.mockResolvedValueOnce({ statusEntries: '[]', credential: '{}' })
|
|
43
|
-
.mockResolvedValueOnce(null);
|
|
59
|
+
mockStored({ direct: '[]', body: '{}' });
|
|
44
60
|
expect(await setCredentialBitstringStatus('credential', 'revocation', true)).toBe(false);
|
|
61
|
+
mockStored(null);
|
|
45
62
|
expect(await setCredentialBitstringStatus('missing', 'revocation', true)).toBe(false);
|
|
46
63
|
});
|
|
47
64
|
|
|
48
65
|
it('returns false when a valid entry cannot be updated', async () => {
|
|
49
|
-
|
|
50
|
-
|
|
66
|
+
mockStored({
|
|
67
|
+
direct: JSON.stringify([
|
|
51
68
|
{
|
|
52
69
|
type: 'BitstringStatusListEntry',
|
|
53
70
|
statusPurpose: 'revocation',
|
|
@@ -61,8 +78,8 @@ describe('stored credential status validation', () => {
|
|
|
61
78
|
});
|
|
62
79
|
|
|
63
80
|
it('rejects malformed numeric coordinates before any status write', async () => {
|
|
64
|
-
|
|
65
|
-
|
|
81
|
+
mockStored({
|
|
82
|
+
direct: JSON.stringify([
|
|
66
83
|
{
|
|
67
84
|
type: 'BitstringStatusListEntry',
|
|
68
85
|
statusPurpose: 'revocation',
|