@learncard/network-brain-service 3.19.1 → 4.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 +44 -111
- package/AGENTS.md +52 -0
- package/CHANGELOG.md +109 -0
- package/README.md +9 -2
- package/bootstrapLambda.test.ts +72 -0
- package/config/config.dev.json +29 -0
- package/config/config.example.json +29 -0
- package/config/config.json +1 -0
- package/config/config.production.json +29 -0
- package/config/config.scouts.dev.json +24 -0
- package/config/config.scouts.production.json +24 -0
- package/contractEventsLambda.ts +18 -0
- package/contractEventsLambdaApp.ts +19 -0
- package/didWebLambda.ts +13 -18
- package/didWebLambdaApp.ts +22 -0
- package/dist/accesslayer/app-manifest-version/create.d.ts +10 -0
- package/dist/accesslayer/app-manifest-version/create.d.ts.map +1 -0
- package/dist/accesslayer/app-manifest-version/read.d.ts +11 -0
- package/dist/accesslayer/app-manifest-version/read.d.ts.map +1 -0
- package/dist/accesslayer/app-manifest-version/update.d.ts +7 -0
- package/dist/accesslayer/app-manifest-version/update.d.ts.map +1 -0
- package/dist/accesslayer/app-store-listing/read.d.ts +2 -1
- package/dist/accesslayer/app-store-listing/read.d.ts.map +1 -1
- package/dist/accesslayer/app-store-listing/relationships/create.d.ts +1 -0
- package/dist/accesslayer/app-store-listing/relationships/create.d.ts.map +1 -1
- package/dist/accesslayer/app-store-listing/relationships/read.d.ts +2 -0
- package/dist/accesslayer/app-store-listing/relationships/read.d.ts.map +1 -1
- package/dist/accesslayer/app-store-listing/update.d.ts +1 -0
- package/dist/accesslayer/app-store-listing/update.d.ts.map +1 -1
- package/dist/accesslayer/boost/relationships/create.d.ts +2 -1
- package/dist/accesslayer/boost/relationships/create.d.ts.map +1 -1
- package/dist/accesslayer/consentflowcontract/create.d.ts +9 -1
- package/dist/accesslayer/consentflowcontract/create.d.ts.map +1 -1
- package/dist/accesslayer/consentflowcontract/read.d.ts +34 -3
- package/dist/accesslayer/consentflowcontract/read.d.ts.map +1 -1
- package/dist/accesslayer/consentflowcontract/relationships/create.d.ts +4 -1
- package/dist/accesslayer/consentflowcontract/relationships/create.d.ts.map +1 -1
- package/dist/accesslayer/consentflowcontract/relationships/delete.d.ts.map +1 -1
- package/dist/accesslayer/consentflowcontract/relationships/read.d.ts +3 -2
- package/dist/accesslayer/consentflowcontract/relationships/read.d.ts.map +1 -1
- package/dist/accesslayer/consentflowcontract/relationships/recipients.d.ts +12 -0
- package/dist/accesslayer/consentflowcontract/relationships/recipients.d.ts.map +1 -0
- package/dist/accesslayer/consentflowcontract/relationships/smartResume.d.ts +12 -0
- package/dist/accesslayer/consentflowcontract/relationships/smartResume.d.ts.map +1 -0
- package/dist/accesslayer/consentflowcontract/relationships/update.d.ts +8 -5
- package/dist/accesslayer/consentflowcontract/relationships/update.d.ts.map +1 -1
- package/dist/accesslayer/credential/create.d.ts +2 -1
- package/dist/accesslayer/credential/create.d.ts.map +1 -1
- package/dist/accesslayer/credential/relationships/create.d.ts +4 -3
- package/dist/accesslayer/credential/relationships/create.d.ts.map +1 -1
- package/dist/accesslayer/inbox-credential/relationships/create.d.ts +2 -1
- package/dist/accesslayer/inbox-credential/relationships/create.d.ts.map +1 -1
- package/dist/accesslayer/inbox-credential/update.d.ts +2 -1
- package/dist/accesslayer/inbox-credential/update.d.ts.map +1 -1
- package/dist/accesslayer/share-link/abandon.d.ts.map +1 -1
- package/dist/accesslayer/share-link/attachment.d.ts +37 -0
- package/dist/accesslayer/share-link/attachment.d.ts.map +1 -0
- package/dist/accesslayer/share-link/finalize.d.ts.map +1 -1
- package/dist/accesslayer/share-link/helpers.d.ts +3 -0
- package/dist/accesslayer/share-link/helpers.d.ts.map +1 -1
- package/dist/accesslayer/share-link/recovery.d.ts.map +1 -1
- package/dist/accesslayer/share-link/reserve.d.ts.map +1 -1
- package/dist/accesslayer/share-link/revoke.d.ts.map +1 -1
- package/dist/accesslayer/share-link/types.d.ts +8 -3
- package/dist/accesslayer/share-link/types.d.ts.map +1 -1
- package/dist/cache/app-store.caches.d.ts +49 -0
- package/dist/cache/app-store.caches.d.ts.map +1 -1
- package/dist/config/applyDockerStageConfig.d.ts +2 -0
- package/dist/config/applyDockerStageConfig.d.ts.map +1 -0
- package/dist/config/environment.d.ts +5 -0
- package/dist/config/environment.d.ts.map +1 -1
- package/dist/config/serverlessFunctionEnv.test.d.ts +2 -0
- package/dist/config/serverlessFunctionEnv.test.d.ts.map +1 -0
- package/dist/config/stageConfig.d.ts +112 -0
- package/dist/config/stageConfig.d.ts.map +1 -0
- package/dist/config/stageConfig.test.d.ts +2 -0
- package/dist/config/stageConfig.test.d.ts.map +1 -0
- package/dist/didkit_wasm_bg.wasm +0 -0
- package/dist/docker-entry.d.ts +1 -1
- package/dist/docker-entry.d.ts.map +1 -1
- package/dist/helpers/app-manifest.helpers.d.ts +5 -0
- package/dist/helpers/app-manifest.helpers.d.ts.map +1 -0
- package/dist/helpers/claim-hooks.helpers.d.ts +2 -1
- package/dist/helpers/claim-hooks.helpers.d.ts.map +1 -1
- package/dist/helpers/consent-referral.helpers.d.ts +2 -0
- package/dist/helpers/consent-referral.helpers.d.ts.map +1 -0
- package/dist/helpers/consent-scopes.helpers.d.ts +7 -0
- package/dist/helpers/consent-scopes.helpers.d.ts.map +1 -0
- package/dist/helpers/contract-event-maintenance.helpers.d.ts +3 -0
- package/dist/helpers/contract-event-maintenance.helpers.d.ts.map +1 -0
- package/dist/helpers/contract-events.helpers.d.ts +25 -0
- package/dist/helpers/contract-events.helpers.d.ts.map +1 -0
- package/dist/helpers/contract-requests.helpers.d.ts +19 -0
- package/dist/helpers/contract-requests.helpers.d.ts.map +1 -0
- package/dist/helpers/finalize-inbox.helpers.d.ts +3 -3
- package/dist/helpers/finalize-inbox.helpers.d.ts.map +1 -1
- package/dist/helpers/inbox.helpers.d.ts.map +1 -1
- package/dist/helpers/inline-template.helpers.d.ts +45 -0
- package/dist/helpers/inline-template.helpers.d.ts.map +1 -0
- package/dist/helpers/notificationMessages.d.ts +1 -1
- package/dist/helpers/notificationMessages.d.ts.map +1 -1
- package/dist/helpers/notificationQueue.helpers.d.ts.map +1 -1
- package/dist/helpers/notifications.helpers.d.ts +6 -0
- package/dist/helpers/notifications.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-link-attachment-access.d.ts +4 -0
- package/dist/helpers/share-link-attachment-access.d.ts.map +1 -0
- package/dist/helpers/share-link-attachment.d.ts +18 -0
- package/dist/helpers/share-link-attachment.d.ts.map +1 -0
- package/dist/helpers/share-link-coordinator/attachment-stat.d.ts +5 -0
- package/dist/helpers/share-link-coordinator/attachment-stat.d.ts.map +1 -0
- package/dist/helpers/share-link-coordinator/coordinator.d.ts.map +1 -1
- package/dist/helpers/share-link-coordinator/recovery-runner.d.ts.map +1 -1
- package/dist/helpers/share-link-maintenance/runner.d.ts +1 -0
- package/dist/helpers/share-link-maintenance/runner.d.ts.map +1 -1
- package/dist/helpers/share-link-maintenance/runtime.d.ts +2 -0
- package/dist/helpers/share-link-maintenance/runtime.d.ts.map +1 -1
- package/dist/helpers/share-link-maintenance/telemetry.d.ts.map +1 -1
- package/dist/helpers/share-link-maintenance/types.d.ts +4 -1
- package/dist/helpers/share-link-maintenance/types.d.ts.map +1 -1
- package/dist/helpers/share-link-owner-projection.d.ts.map +1 -1
- package/dist/helpers/share-link-policy/production.d.ts +3 -5
- package/dist/helpers/share-link-policy/production.d.ts.map +1 -1
- package/dist/helpers/share-link-policy/resolver.d.ts +1 -1
- package/dist/helpers/share-link-policy/resolver.d.ts.map +1 -1
- package/dist/helpers/share-link-policy/types.d.ts +8 -2
- package/dist/helpers/share-link-policy/types.d.ts.map +1 -1
- package/dist/helpers/signingAuthority.helpers.d.ts.map +1 -1
- package/dist/helpers/smartResume.helpers.d.ts +3 -0
- package/dist/helpers/smartResume.helpers.d.ts.map +1 -0
- package/dist/index.js +2 -2
- package/dist/models/AppManifestVersion.d.ts +20 -0
- package/dist/models/AppManifestVersion.d.ts.map +1 -0
- package/dist/models/AppStoreListing.d.ts +13 -0
- package/dist/models/AppStoreListing.d.ts.map +1 -1
- package/dist/models/ConsentFlowContract.d.ts +13 -2
- package/dist/models/ConsentFlowContract.d.ts.map +1 -1
- package/dist/models/ConsentFlowTerms.d.ts.map +1 -1
- package/dist/models/ConsentFlowTransaction.d.ts.map +1 -1
- package/dist/models/Integration.d.ts +2 -0
- package/dist/models/Integration.d.ts.map +1 -1
- package/dist/models/ShareLink.d.ts +2 -0
- package/dist/models/ShareLink.d.ts.map +1 -1
- 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.map +1 -1
- package/dist/openapi.d.ts +1 -1
- package/dist/openapi.d.ts.map +1 -1
- package/dist/routes/app-store.d.ts +619 -0
- package/dist/routes/app-store.d.ts.map +1 -1
- package/dist/routes/boosts.d.ts +2 -2
- package/dist/routes/boosts.d.ts.map +1 -1
- package/dist/routes/contracts.d.ts +146 -3
- package/dist/routes/contracts.d.ts.map +1 -1
- package/dist/routes/credentials.d.ts +34 -0
- package/dist/routes/credentials.d.ts.map +1 -1
- package/dist/routes/inbox.d.ts +52 -2
- package/dist/routes/inbox.d.ts.map +1 -1
- package/dist/routes/index.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 +55 -0
- package/dist/routes/public-share-links.d.ts.map +1 -1
- package/dist/routes/share-links.d.ts +175 -14
- package/dist/routes/share-links.d.ts.map +1 -1
- package/dist/routes/test.d.ts +5 -0
- package/dist/routes/test.d.ts.map +1 -1
- package/dist/routes/workflows.d.ts.map +1 -1
- package/dist/types/app-manifest.d.ts +339 -0
- package/dist/types/app-manifest.d.ts.map +1 -0
- package/dist/types/app-store-listing.d.ts +34 -0
- package/dist/types/app-store-listing.d.ts.map +1 -1
- package/dist/types/consentflowcontract.d.ts +30 -3
- package/dist/types/consentflowcontract.d.ts.map +1 -1
- package/dist/types/credential-refresh.d.ts +1 -1
- package/esbuildPlugins.cjs +1 -0
- package/lambda.ts +49 -195
- package/lambdaApp.ts +203 -0
- package/package.json +20 -18
- package/scripts/export-openapi.ts +20 -0
- package/scripts/offline-schema-environment.ts +18 -0
- package/scripts/verify-python-contracts.ts +72 -0
- package/serverless-local.yml +3 -6
- package/serverless.function-env.cjs +50 -0
- package/serverless.local.yml +0 -1
- package/serverless.yml +27 -74
- package/shareLinkMaintenanceLambda.ts +13 -44
- package/shareLinkMaintenanceLambdaApp.ts +50 -0
- package/src/accesslayer/app-manifest-version/create.ts +57 -0
- package/src/accesslayer/app-manifest-version/read.ts +129 -0
- package/src/accesslayer/app-manifest-version/update.ts +61 -0
- package/src/accesslayer/app-store-listing/read.ts +6 -0
- package/src/accesslayer/app-store-listing/relationships/create.ts +20 -0
- package/src/accesslayer/app-store-listing/relationships/read.ts +24 -1
- package/src/accesslayer/app-store-listing/update.ts +15 -0
- package/src/accesslayer/boost/relationships/create.ts +8 -2
- package/src/accesslayer/consentflowcontract/create.ts +81 -8
- package/src/accesslayer/consentflowcontract/read.ts +84 -10
- package/src/accesslayer/consentflowcontract/relationships/create.ts +116 -93
- package/src/accesslayer/consentflowcontract/relationships/delete.ts +1 -0
- package/src/accesslayer/consentflowcontract/relationships/read.ts +184 -108
- package/src/accesslayer/consentflowcontract/relationships/recipients.ts +125 -0
- package/src/accesslayer/consentflowcontract/relationships/smartResume.ts +125 -0
- package/src/accesslayer/consentflowcontract/relationships/update.ts +240 -189
- package/src/accesslayer/credential/create.test.ts +2 -115
- package/src/accesslayer/credential/create.ts +11 -6
- package/src/accesslayer/credential/relationships/create.ts +17 -10
- package/src/accesslayer/inbox-credential/relationships/create.ts +70 -48
- package/src/accesslayer/inbox-credential/update.ts +4 -2
- package/src/accesslayer/share-link/abandon.ts +20 -0
- package/src/accesslayer/share-link/attachment.ts +360 -0
- package/src/accesslayer/share-link/finalize.ts +55 -7
- package/src/accesslayer/share-link/helpers.ts +33 -2
- package/src/accesslayer/share-link/recovery.ts +20 -0
- package/src/accesslayer/share-link/reserve.ts +85 -6
- package/src/accesslayer/share-link/revoke.ts +35 -0
- package/src/accesslayer/share-link/types.ts +9 -4
- package/src/cache/app-store.caches.ts +35 -0
- package/src/config/applyDockerStageConfig.ts +6 -0
- package/src/config/environment.test.ts +17 -12
- package/src/config/environment.ts +1 -0
- package/src/config/serverlessFunctionEnv.test.ts +105 -0
- package/src/config/stageConfig.test.ts +25 -0
- package/src/config/stageConfig.ts +24 -0
- package/src/docker-entry.ts +24 -0
- package/src/helpers/app-manifest.helpers.ts +185 -0
- package/src/helpers/claim-hooks.helpers.ts +20 -7
- package/src/helpers/consent-referral.helpers.ts +6 -0
- package/src/helpers/consent-scopes.helpers.ts +58 -0
- package/src/helpers/contract-event-maintenance.helpers.ts +43 -0
- package/src/helpers/contract-events.helpers.ts +540 -0
- package/src/helpers/contract-requests.helpers.ts +211 -0
- package/src/helpers/finalize-inbox.helpers.ts +163 -44
- package/src/helpers/inbox.helpers.ts +51 -66
- package/src/helpers/inline-template.helpers.ts +560 -0
- package/src/helpers/notificationMessages.ts +59 -0
- package/src/helpers/notificationQueue.helpers.ts +23 -5
- package/src/helpers/notifications.helpers.ts +25 -1
- package/src/helpers/profile.helpers.ts +6 -0
- package/src/helpers/share-link-attachment-access.ts +87 -0
- package/src/helpers/share-link-attachment.ts +115 -0
- package/src/helpers/share-link-coordinator/attachment-stat.ts +41 -0
- package/src/helpers/share-link-coordinator/coordinator.ts +15 -2
- package/src/helpers/share-link-coordinator/recovery-runner.ts +15 -0
- package/src/helpers/share-link-maintenance/runner.ts +28 -0
- package/src/helpers/share-link-maintenance/runtime.ts +6 -0
- package/src/helpers/share-link-maintenance/telemetry.ts +3 -0
- package/src/helpers/share-link-maintenance/types.ts +2 -0
- package/src/helpers/share-link-owner-projection.ts +2 -0
- package/src/helpers/share-link-policy/production.ts +37 -17
- package/src/helpers/share-link-policy/resolver.ts +16 -11
- package/src/helpers/share-link-policy/types.ts +15 -7
- package/src/helpers/signingAuthority.helpers.ts +23 -14
- package/src/helpers/smartResume.helpers.ts +123 -0
- package/src/models/AppManifestVersion.ts +43 -0
- package/src/models/AppStoreListing.ts +32 -0
- package/src/models/ConsentFlowContract.ts +34 -3
- package/src/models/ConsentFlowTerms.ts +10 -0
- package/src/models/ConsentFlowTransaction.ts +3 -0
- package/src/models/Integration.ts +2 -0
- package/src/models/ShareLink.ts +4 -0
- package/src/models/index.ts +23 -0
- package/src/models/share-link-constraints.ts +5 -0
- package/src/openapi.ts +44 -8
- package/src/routes/app-store.ts +1054 -27
- package/src/routes/boosts.ts +12 -14
- package/src/routes/contracts.ts +382 -262
- package/src/routes/inbox.ts +4 -2
- package/src/routes/index.ts +4 -1
- package/src/routes/profile-manager.ts +19 -0
- package/src/routes/profiles.ts +12 -0
- package/src/routes/public-share-links.ts +169 -0
- package/src/routes/share-links.ts +69 -0
- package/src/routes/workflows.ts +3 -2
- package/src/types/app-manifest.ts +28 -0
- package/src/types/app-store-listing.ts +13 -0
- package/src/types/consentflowcontract.ts +25 -4
- package/test/app-manifest.helpers.spec.ts +170 -0
- package/test/app-manifest.routes.spec.ts +299 -0
- package/test/app-store-listing-updates.spec.ts +236 -0
- package/test/app-store-listing.spec.ts +65 -3
- package/test/consent-data-boundaries.spec.ts +352 -0
- package/test/consent-recipients.spec.ts +513 -0
- package/test/consent-scopes.helpers.spec.ts +67 -0
- package/test/consentflow.spec.ts +63 -2
- package/test/contract-requests.spec.ts +1588 -0
- package/test/credential-storage.neo4j.spec.ts +122 -0
- package/test/guardian-gated-routes.spec.ts +36 -0
- package/test/inbox-security.e2e.ts +26 -10
- package/test/inbox.spec.ts +184 -8
- package/test/inline-template.helpers.spec.ts +49 -0
- package/test/managed-recipient-inbox.spec.ts +24 -2
- package/test/openapi.spec.ts +14 -73
- package/test/profiles.spec.ts +30 -0
- package/test/share-link-attachment.neo4j.spec.ts +262 -0
- package/test/share-link-attachment.unit.spec.ts +244 -0
- package/test/share-link-lifecycle.neo4j.spec.ts +145 -2
- package/test/share-link-maintenance.unit.spec.ts +32 -5
- package/test/share-link-owner-route.unit.spec.ts +70 -2
- package/test/share-link-policy.unit.spec.ts +18 -4
- package/test/share-link-production-policy.unit.spec.ts +58 -9
- package/test/share-link-public-route.unit.spec.ts +279 -0
- package/test/share-link-service-policy.neo4j.spec.ts +403 -0
- package/test/signing-authority-encryption.spec.ts +75 -0
- package/test/smart-resume-consent.spec.ts +233 -0
- package/test/smart-resume-upload.spec.ts +65 -0
- package/tsconfig.build.json +1 -1
- package/tsconfig.json +1 -0
- package/vitest.config.ts +13 -0
- package/vitest.inbox-e2e.config.ts +2 -0
- package/workerLambda.test.ts +100 -0
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
import { randomBytes } from 'node:crypto';
|
|
2
2
|
|
|
3
|
+
import {
|
|
4
|
+
consumeShareViewReceipt,
|
|
5
|
+
persistShareViewReceipt,
|
|
6
|
+
} from '../src/accesslayer/share-link/receipt';
|
|
7
|
+
|
|
3
8
|
import { afterAll, beforeAll, beforeEach, describe, expect, it } from 'vitest';
|
|
4
9
|
import { v4 as uuid } from 'uuid';
|
|
5
10
|
|
|
6
11
|
import { computeShareLinkRequestHash } from '@helpers/share-link-lifecycle';
|
|
7
12
|
import { neogma } from '@instance';
|
|
13
|
+
import { withShareLinkRead } from '../src/accesslayer/share-link/transaction';
|
|
8
14
|
|
|
9
15
|
import {
|
|
10
16
|
abandonReservation,
|
|
@@ -23,7 +29,12 @@ import {
|
|
|
23
29
|
import { ensureShareLinkConstraints } from '../src/models/share-link-constraints';
|
|
24
30
|
import { toOwnerShareLink } from '../src/helpers/share-link-owner-projection';
|
|
25
31
|
import type { ShareLinkPolicySnapshot } from '../src/helpers/share-link-policy/types';
|
|
26
|
-
import {
|
|
32
|
+
import {
|
|
33
|
+
createProductionShareLinkPolicySource,
|
|
34
|
+
productionShareViewEligibilitySource,
|
|
35
|
+
resolveCurrentShareLinkPolicy,
|
|
36
|
+
} from '../src/helpers/share-link-policy/production';
|
|
37
|
+
import { createShareLinkPolicyResolver } from '../src/helpers/share-link-policy/resolver';
|
|
27
38
|
import type { ShareLinkRecord } from '../src/models/ShareLink';
|
|
28
39
|
|
|
29
40
|
import {
|
|
@@ -782,7 +793,7 @@ describe('share-link lifecycle repository (Neo4j)', () => {
|
|
|
782
793
|
|
|
783
794
|
const protectedShare = await finalizeReservation({
|
|
784
795
|
...protect.reservation,
|
|
785
|
-
resolveCurrentPolicy: async () => policy,
|
|
796
|
+
resolveCurrentPolicy: async () => ({ ...policy, isServiceProfile: false }),
|
|
786
797
|
now: NOW,
|
|
787
798
|
});
|
|
788
799
|
if (protectedShare.outcome !== 'finalized') throw new Error('expected protected share');
|
|
@@ -1265,4 +1276,136 @@ describe('share-link lifecycle repository (Neo4j)', () => {
|
|
|
1265
1276
|
);
|
|
1266
1277
|
}
|
|
1267
1278
|
});
|
|
1279
|
+
|
|
1280
|
+
it('exempts managed service owners but keeps managed child owners restricted in Neo4j', async () => {
|
|
1281
|
+
const serviceProfileId = `service-policy-${uuid()}`;
|
|
1282
|
+
const childProfileId = `child-policy-${uuid()}`;
|
|
1283
|
+
const unknownServiceId = `service-unknown-${uuid()}`;
|
|
1284
|
+
const serviceManagerId = `manager-${uuid()}`;
|
|
1285
|
+
const childManagerId = `manager-${uuid()}`;
|
|
1286
|
+
await neogma.queryRunner.run(
|
|
1287
|
+
`CREATE (service:Profile {profileId: $serviceId, isServiceProfile: true})
|
|
1288
|
+
CREATE (serviceManager:ProfileManager {id: $serviceManagerId, created: $created})
|
|
1289
|
+
CREATE (serviceManager)-[:MANAGES]->(service)
|
|
1290
|
+
CREATE (child:Profile {profileId: $childId, dob: '1990-01-01', isServiceProfile: false})
|
|
1291
|
+
CREATE (child)-[:MANAGED_BY]->(guardian:Profile {profileId: $childManagerId})
|
|
1292
|
+
CREATE (serviceManager)-[:ADMINISTRATED_BY]->(guardian)
|
|
1293
|
+
CREATE (:Profile {profileId: $unknownServiceId, isServiceProfile: true})`,
|
|
1294
|
+
{
|
|
1295
|
+
serviceId: serviceProfileId,
|
|
1296
|
+
serviceManagerId,
|
|
1297
|
+
childId: childProfileId,
|
|
1298
|
+
childManagerId,
|
|
1299
|
+
unknownServiceId,
|
|
1300
|
+
created: NOW.toISOString(),
|
|
1301
|
+
}
|
|
1302
|
+
);
|
|
1303
|
+
try {
|
|
1304
|
+
const shareId = nextShareId();
|
|
1305
|
+
const requestId = uuid();
|
|
1306
|
+
const policy: ShareLinkPolicySnapshot = {
|
|
1307
|
+
isMinor: false,
|
|
1308
|
+
policyResolved: true,
|
|
1309
|
+
defaultExpiryDays: 365,
|
|
1310
|
+
viewCountingEnabled: true,
|
|
1311
|
+
};
|
|
1312
|
+
const reserved = await reserveCreate({
|
|
1313
|
+
namespace: NAMESPACE,
|
|
1314
|
+
ownerProfileId: serviceProfileId,
|
|
1315
|
+
clientRequestId: requestId,
|
|
1316
|
+
shareId,
|
|
1317
|
+
title: 'Service policy integration',
|
|
1318
|
+
note: null,
|
|
1319
|
+
expiresAt: null,
|
|
1320
|
+
selectedCount: 1,
|
|
1321
|
+
content: contentBinding(shareId),
|
|
1322
|
+
requestHash: computeShareLinkRequestHash('create', {
|
|
1323
|
+
id: shareId,
|
|
1324
|
+
title: 'Service policy integration',
|
|
1325
|
+
}),
|
|
1326
|
+
leaseOwner: 'worker-1',
|
|
1327
|
+
policy,
|
|
1328
|
+
now: NOW,
|
|
1329
|
+
});
|
|
1330
|
+
if (reserved.outcome !== 'reserved' || !reserved.reservation.objectRef) {
|
|
1331
|
+
throw new Error('expected service share reservation');
|
|
1332
|
+
}
|
|
1333
|
+
const finalized = await finalizeReservation({
|
|
1334
|
+
...reserved.reservation,
|
|
1335
|
+
resolveCurrentPolicy: resolveCurrentShareLinkPolicy,
|
|
1336
|
+
now: NOW,
|
|
1337
|
+
});
|
|
1338
|
+
const created = {
|
|
1339
|
+
namespace: NAMESPACE,
|
|
1340
|
+
shareId,
|
|
1341
|
+
committed: finalized.share,
|
|
1342
|
+
objectRef: reserved.reservation.objectRef,
|
|
1343
|
+
operationId: reserved.reservation.operationId,
|
|
1344
|
+
};
|
|
1345
|
+
const preflight = createShareLinkPolicyResolver(
|
|
1346
|
+
createProductionShareLinkPolicySource()
|
|
1347
|
+
);
|
|
1348
|
+
expect((await preflight.resolve(serviceProfileId)).viewCountingEnabled).toBe(true);
|
|
1349
|
+
expect((await preflight.resolve(childProfileId)).viewCountingEnabled).toBe(false);
|
|
1350
|
+
expect((await preflight.resolve(unknownServiceId)).viewCountingEnabled).toBe(true);
|
|
1351
|
+
expect(
|
|
1352
|
+
await withShareLinkRead(tx =>
|
|
1353
|
+
resolveCurrentShareLinkPolicy(tx, serviceProfileId, NOW)
|
|
1354
|
+
)
|
|
1355
|
+
).toMatchObject({ viewCountingEnabled: true });
|
|
1356
|
+
const receipt = randomBytes(32).toString('base64url');
|
|
1357
|
+
expect(
|
|
1358
|
+
await persistShareViewReceipt({
|
|
1359
|
+
receipt,
|
|
1360
|
+
namespace: created.namespace,
|
|
1361
|
+
ownerProfileId: serviceProfileId,
|
|
1362
|
+
shareId: created.shareId,
|
|
1363
|
+
shareVersion: created.committed.version,
|
|
1364
|
+
contentVersion: created.committed.contentVersion,
|
|
1365
|
+
objectRef: created.objectRef,
|
|
1366
|
+
operationId: created.operationId,
|
|
1367
|
+
ttlSeconds: 60,
|
|
1368
|
+
eligibilitySource: productionShareViewEligibilitySource,
|
|
1369
|
+
now: () => NOW,
|
|
1370
|
+
})
|
|
1371
|
+
).toBe(true);
|
|
1372
|
+
expect(
|
|
1373
|
+
await consumeShareViewReceipt({
|
|
1374
|
+
receipt,
|
|
1375
|
+
namespace: created.namespace,
|
|
1376
|
+
eligibilitySource: productionShareViewEligibilitySource,
|
|
1377
|
+
now: () => NOW,
|
|
1378
|
+
})
|
|
1379
|
+
).toBe('consumed');
|
|
1380
|
+
const counted = await neogma.queryRunner.run(
|
|
1381
|
+
'MATCH (s:ShareLink {id: $shareId}) RETURN s.viewCount AS viewCount',
|
|
1382
|
+
{ shareId: created.shareId }
|
|
1383
|
+
);
|
|
1384
|
+
expect(Number(counted.records[0]?.get('viewCount'))).toBe(1);
|
|
1385
|
+
expect(
|
|
1386
|
+
await withShareLinkRead(tx =>
|
|
1387
|
+
resolveCurrentShareLinkPolicy(tx, childProfileId, NOW)
|
|
1388
|
+
)
|
|
1389
|
+
).toMatchObject({ viewCountingEnabled: false });
|
|
1390
|
+
expect(
|
|
1391
|
+
await withShareLinkRead(tx =>
|
|
1392
|
+
resolveCurrentShareLinkPolicy(tx, unknownServiceId, NOW)
|
|
1393
|
+
)
|
|
1394
|
+
).toMatchObject({ viewCountingEnabled: true });
|
|
1395
|
+
} finally {
|
|
1396
|
+
await neogma.queryRunner.run(
|
|
1397
|
+
`MATCH (n)
|
|
1398
|
+
WHERE (n:Profile AND n.profileId IN [$serviceId, $childId, $unknownServiceId, $childManagerId])
|
|
1399
|
+
OR (n:ProfileManager AND n.id = $serviceManagerId)
|
|
1400
|
+
DETACH DELETE n`,
|
|
1401
|
+
{
|
|
1402
|
+
serviceId: serviceProfileId,
|
|
1403
|
+
childId: childProfileId,
|
|
1404
|
+
unknownServiceId,
|
|
1405
|
+
serviceManagerId,
|
|
1406
|
+
childManagerId,
|
|
1407
|
+
}
|
|
1408
|
+
);
|
|
1409
|
+
}
|
|
1410
|
+
});
|
|
1268
1411
|
});
|
|
@@ -81,10 +81,11 @@ const makeCleanupRepository = (
|
|
|
81
81
|
|
|
82
82
|
const makeDeleteClient = (
|
|
83
83
|
result: unknown = { ok: true, value: {} }
|
|
84
|
-
):
|
|
84
|
+
): Parameters<typeof runShareLinkMaintenancePass>[0]['client'] =>
|
|
85
85
|
({
|
|
86
86
|
delete: vi.fn().mockResolvedValue(result),
|
|
87
|
-
|
|
87
|
+
stat: vi.fn().mockResolvedValue({ ok: false, error: 'NOT_FOUND' }),
|
|
88
|
+
}) as Parameters<typeof runShareLinkMaintenancePass>[0]['client'];
|
|
88
89
|
|
|
89
90
|
const makeRecoveryRepository = (): RecoveryRunnerDependencies['repository'] =>
|
|
90
91
|
({
|
|
@@ -307,6 +308,31 @@ describe('share-link maintenance pass', () => {
|
|
|
307
308
|
expect(summary.categories).toEqual({});
|
|
308
309
|
});
|
|
309
310
|
|
|
311
|
+
it('runs bounded attachment stage collection and reports a safe failure for retry', async () => {
|
|
312
|
+
const config = enabledConfig();
|
|
313
|
+
const now = new Date(NOW_ISO);
|
|
314
|
+
const expireAttachmentStages = vi.fn().mockResolvedValue(3);
|
|
315
|
+
const dependencies = {
|
|
316
|
+
...runnerDependencies(config, { expireAttachmentStages, now: () => now }),
|
|
317
|
+
client: makeDeleteClient(),
|
|
318
|
+
};
|
|
319
|
+
const summary = await runShareLinkMaintenancePass(dependencies);
|
|
320
|
+
expect(expireAttachmentStages).toHaveBeenCalledWith({
|
|
321
|
+
namespace: NAMESPACE,
|
|
322
|
+
limit: 10,
|
|
323
|
+
now,
|
|
324
|
+
transaction: { timeoutMs: config.graphTimeoutMs, noInlineRetry: true },
|
|
325
|
+
});
|
|
326
|
+
expect(summary.attachmentStages).toEqual({ queued: 3 });
|
|
327
|
+
expireAttachmentStages.mockRejectedValueOnce(new Error('raw private detail'));
|
|
328
|
+
const failed = await runShareLinkMaintenancePass(dependencies);
|
|
329
|
+
expect(failed.status).toBe('failed');
|
|
330
|
+
expect(failed.categories).toMatchObject({
|
|
331
|
+
share_link_maintenance_attachment_stage_failed: 1,
|
|
332
|
+
});
|
|
333
|
+
expect(JSON.stringify(toMaintenanceLogEvent(failed))).not.toContain('raw private detail');
|
|
334
|
+
});
|
|
335
|
+
|
|
310
336
|
it('does not start receipt pruning when the graph budget is exhausted', async () => {
|
|
311
337
|
let clock = 0;
|
|
312
338
|
// budget 1000ms, reserve 100ms. Recovery discovery needs 500ms and cleanup
|
|
@@ -727,6 +753,7 @@ describe('C7 independent review regressions', () => {
|
|
|
727
753
|
client,
|
|
728
754
|
logger: silentLogger,
|
|
729
755
|
remainingTimeMs: () => 6_000,
|
|
756
|
+
pruneReceipts: vi.fn().mockResolvedValue(0),
|
|
730
757
|
});
|
|
731
758
|
expect(summary.status).toBe('deadline_exhausted');
|
|
732
759
|
expect(cleanupRepository.claimCleanupJobs).not.toHaveBeenCalled();
|
|
@@ -1034,14 +1061,14 @@ describe('C7a dependency setup caching', () => {
|
|
|
1034
1061
|
|
|
1035
1062
|
describe('C7a scheduler terminal stop and synchronous failure', () => {
|
|
1036
1063
|
it('does not run a queued timer callback after stop', async () => {
|
|
1037
|
-
|
|
1064
|
+
const timer: { queued: (() => void) | null } = { queued: null };
|
|
1038
1065
|
const runOnce = vi.fn().mockResolvedValue(undefined);
|
|
1039
1066
|
const scheduler = createShareLinkMaintenanceScheduler({
|
|
1040
1067
|
runOnce,
|
|
1041
1068
|
intervalMs: 1_000,
|
|
1042
1069
|
logger: silentLogger,
|
|
1043
1070
|
setTimeoutFn: handler => {
|
|
1044
|
-
queued = handler;
|
|
1071
|
+
timer.queued = handler;
|
|
1045
1072
|
return 1;
|
|
1046
1073
|
},
|
|
1047
1074
|
clearTimeoutFn: () => undefined,
|
|
@@ -1051,7 +1078,7 @@ describe('C7a scheduler terminal stop and synchronous failure', () => {
|
|
|
1051
1078
|
await scheduler.stop();
|
|
1052
1079
|
|
|
1053
1080
|
// Simulate a timer callback that was already queued when stop ran.
|
|
1054
|
-
queued?.();
|
|
1081
|
+
timer.queued?.();
|
|
1055
1082
|
await Promise.resolve();
|
|
1056
1083
|
await Promise.resolve();
|
|
1057
1084
|
|
|
@@ -220,6 +220,72 @@ beforeEach(() => {
|
|
|
220
220
|
recoveryRunner.recoverShareLinkOperation.mockReset();
|
|
221
221
|
});
|
|
222
222
|
|
|
223
|
+
describe('owner attachment route authorization', () => {
|
|
224
|
+
const input = {
|
|
225
|
+
id: SHARE_ID,
|
|
226
|
+
contentVersion: 1,
|
|
227
|
+
attachmentId: OPERATION_ID,
|
|
228
|
+
chunkIndex: 0,
|
|
229
|
+
chunkCount: 1,
|
|
230
|
+
envelope,
|
|
231
|
+
ownerEncryptedRecovery: recovery,
|
|
232
|
+
};
|
|
233
|
+
const setup = () => ({
|
|
234
|
+
...makeDependencies(),
|
|
235
|
+
attachments: {
|
|
236
|
+
put: vi.fn(async () => ({ ok: true as const })),
|
|
237
|
+
delete: vi.fn(async () => ({ ok: true })),
|
|
238
|
+
},
|
|
239
|
+
});
|
|
240
|
+
it('requires authenticated profile and write scope before any staging', async () => {
|
|
241
|
+
const dependencies = setup();
|
|
242
|
+
for (const user of [
|
|
243
|
+
undefined,
|
|
244
|
+
{ ...authenticatedUser, isChallengeValid: false },
|
|
245
|
+
{ ...authenticatedUser, scope: AUTH_GRANT_SHARE_LINKS_READ_SCOPE },
|
|
246
|
+
]) {
|
|
247
|
+
await expect(
|
|
248
|
+
makeCaller(dependencies, { user }).putAttachmentChunk(input)
|
|
249
|
+
).rejects.toMatchObject({ code: 'UNAUTHORIZED' });
|
|
250
|
+
}
|
|
251
|
+
expect(dependencies.attachments.put).not.toHaveBeenCalled();
|
|
252
|
+
await expect(makeCaller(dependencies).putAttachmentChunk(input)).resolves.toEqual({
|
|
253
|
+
ok: true,
|
|
254
|
+
});
|
|
255
|
+
expect(dependencies.attachments.put).toHaveBeenCalledWith(input, {
|
|
256
|
+
namespace: NAME,
|
|
257
|
+
ownerProfileId: OWNER_PROFILE.profileId,
|
|
258
|
+
});
|
|
259
|
+
});
|
|
260
|
+
it('rejects arbitrary tuples and overlarge chunks before staging', async () => {
|
|
261
|
+
const dependencies = setup();
|
|
262
|
+
await expect(
|
|
263
|
+
makeCaller(dependencies).putAttachmentChunk({
|
|
264
|
+
...input,
|
|
265
|
+
namespace: 'arbitrary',
|
|
266
|
+
} as never)
|
|
267
|
+
).rejects.toMatchObject({ code: 'BAD_REQUEST' });
|
|
268
|
+
await expect(
|
|
269
|
+
makeCaller(dependencies).putAttachmentChunk({
|
|
270
|
+
...input,
|
|
271
|
+
envelope: { ...envelope, ct: Buffer.alloc(256 * 1024 + 17).toString('base64url') },
|
|
272
|
+
})
|
|
273
|
+
).rejects.toMatchObject({ code: 'BAD_REQUEST' });
|
|
274
|
+
expect(dependencies.attachments.put).not.toHaveBeenCalled();
|
|
275
|
+
});
|
|
276
|
+
it('derives cleanup owner and passes only the validated exact stage key', async () => {
|
|
277
|
+
const dependencies = setup();
|
|
278
|
+
const key = { id: SHARE_ID, contentVersion: 1, attachmentId: OPERATION_ID, chunkCount: 1 };
|
|
279
|
+
await expect(makeCaller(dependencies).deleteAttachmentChunks(key)).resolves.toEqual({
|
|
280
|
+
ok: true,
|
|
281
|
+
});
|
|
282
|
+
expect(dependencies.attachments.delete).toHaveBeenCalledWith(key, {
|
|
283
|
+
namespace: NAME,
|
|
284
|
+
ownerProfileId: OWNER_PROFILE.profileId,
|
|
285
|
+
});
|
|
286
|
+
});
|
|
287
|
+
});
|
|
288
|
+
|
|
223
289
|
describe.each([
|
|
224
290
|
['standard', profileRoute],
|
|
225
291
|
['private input', profileRouteWithoutInputCapture],
|
|
@@ -678,7 +744,8 @@ describe('share-link owner route boundary', () => {
|
|
|
678
744
|
);
|
|
679
745
|
|
|
680
746
|
const result = await caller.list({ limit: 25 });
|
|
681
|
-
|
|
747
|
+
expect(result.records).toHaveLength(1);
|
|
748
|
+
const record = result.records[0]!;
|
|
682
749
|
|
|
683
750
|
expect(resolveViewCountingEligibility).toHaveBeenCalledTimes(1);
|
|
684
751
|
expect(record).not.toHaveProperty('activeObjectRef');
|
|
@@ -708,8 +775,9 @@ describe('share-link owner route boundary', () => {
|
|
|
708
775
|
|
|
709
776
|
const result = await caller.list({ limit: 25 });
|
|
710
777
|
|
|
778
|
+
expect(result.records).toHaveLength(1);
|
|
711
779
|
expect(result.records[0]).not.toHaveProperty('viewCount');
|
|
712
|
-
expect(result.records[0]
|
|
780
|
+
expect(result.records[0]!.lastViewedAt).toBeNull();
|
|
713
781
|
});
|
|
714
782
|
|
|
715
783
|
it('maps a repository failure to a fixed safe error without leaking the cause', async () => {
|
|
@@ -42,13 +42,24 @@ describe('share-link policy decision table', () => {
|
|
|
42
42
|
expect(composeShareLinkPolicy('unknown', false)).toEqual(DEFAULT_SHARE_LINK_POLICY);
|
|
43
43
|
expect(composeShareLinkPolicy('unknown', true)).toEqual(DEFAULT_SHARE_LINK_POLICY);
|
|
44
44
|
});
|
|
45
|
+
|
|
46
|
+
it.each(['unknown', 'minor'] as const)(
|
|
47
|
+
'allows service tracking with %s age without changing the expiry default',
|
|
48
|
+
age => {
|
|
49
|
+
expect(composeShareLinkPolicy(age, true, true)).toEqual({
|
|
50
|
+
isMinor: false,
|
|
51
|
+
policyResolved: true,
|
|
52
|
+
defaultExpiryDays: 30,
|
|
53
|
+
viewCountingEnabled: true,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
);
|
|
45
57
|
});
|
|
46
58
|
|
|
47
59
|
describe('createShareLinkPolicyResolver', () => {
|
|
48
60
|
it('fails closed to the conservative default when a source throws', async () => {
|
|
49
61
|
const resolver = createShareLinkPolicyResolver({
|
|
50
|
-
|
|
51
|
-
isManaged: vi.fn().mockResolvedValue(false),
|
|
62
|
+
resolveOwner: vi.fn().mockRejectedValue(new Error('profile source down')),
|
|
52
63
|
});
|
|
53
64
|
|
|
54
65
|
await expect(resolver.resolve('owner-1')).resolves.toEqual(DEFAULT_SHARE_LINK_POLICY);
|
|
@@ -56,8 +67,11 @@ describe('createShareLinkPolicyResolver', () => {
|
|
|
56
67
|
|
|
57
68
|
it('never returns an adult policy when the age source is unknown', async () => {
|
|
58
69
|
const resolver = createShareLinkPolicyResolver({
|
|
59
|
-
|
|
60
|
-
|
|
70
|
+
resolveOwner: vi.fn().mockResolvedValue({
|
|
71
|
+
age: 'unknown',
|
|
72
|
+
isManaged: false,
|
|
73
|
+
isServiceProfile: false,
|
|
74
|
+
}),
|
|
61
75
|
});
|
|
62
76
|
|
|
63
77
|
const policy = await resolver.resolve('owner-1');
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import { describe, expect, it, vi } from 'vitest';
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
2
|
|
|
3
3
|
vi.mock('@accesslayer/profile/read', () => ({ getProfileByProfileId: vi.fn() }));
|
|
4
4
|
vi.mock('@accesslayer/profile/relationships/read', () => ({ isProfileManaged: vi.fn() }));
|
|
5
5
|
vi.mock('@helpers/profile.helpers', () => ({
|
|
6
6
|
transformProfileId: (id: string) => id.toLowerCase(),
|
|
7
|
+
isServiceProfileExemptFromGuardianship: (isServiceProfile: unknown, type: unknown) =>
|
|
8
|
+
isServiceProfile === true && type !== 'child',
|
|
7
9
|
}));
|
|
8
10
|
|
|
9
11
|
import {
|
|
@@ -15,6 +17,7 @@ import {
|
|
|
15
17
|
import { getProfileByProfileId } from '@accesslayer/profile/read';
|
|
16
18
|
import { isProfileManaged } from '@accesslayer/profile/relationships/read';
|
|
17
19
|
import type { ShareLinkTransaction } from '@accesslayer/share-link/transaction';
|
|
20
|
+
import { createShareLinkPolicyResolver } from '@helpers/share-link-policy/resolver';
|
|
18
21
|
|
|
19
22
|
const NOW = new Date('2026-09-25T12:00:00.000Z');
|
|
20
23
|
|
|
@@ -22,13 +25,27 @@ const transactionWith = (profile?: {
|
|
|
22
25
|
dob: unknown;
|
|
23
26
|
type: unknown;
|
|
24
27
|
isManaged: unknown;
|
|
28
|
+
isServiceProfile?: unknown;
|
|
25
29
|
}): ShareLinkTransaction => ({
|
|
26
30
|
run: vi.fn(async () => ({
|
|
27
|
-
records: profile
|
|
31
|
+
records: profile
|
|
32
|
+
? [
|
|
33
|
+
{
|
|
34
|
+
get: (key: string) =>
|
|
35
|
+
key === 'profileType'
|
|
36
|
+
? profile.type
|
|
37
|
+
: profile[key as keyof typeof profile],
|
|
38
|
+
},
|
|
39
|
+
]
|
|
40
|
+
: [],
|
|
28
41
|
})),
|
|
29
42
|
});
|
|
30
43
|
|
|
31
44
|
describe('production share-link age policy', () => {
|
|
45
|
+
beforeEach(() => {
|
|
46
|
+
vi.resetAllMocks();
|
|
47
|
+
});
|
|
48
|
+
|
|
32
49
|
it('requires a valid birthdate and an eighteenth birthday before allowing adulthood', () => {
|
|
33
50
|
expect(ageFromPersistedProfile({ dob: '2008-09-25' }, NOW)).toBe('adult');
|
|
34
51
|
expect(ageFromPersistedProfile({ dob: '2008-09-26' }, NOW)).toBe('minor');
|
|
@@ -43,16 +60,48 @@ describe('production share-link age policy', () => {
|
|
|
43
60
|
vi.mocked(getProfileByProfileId).mockResolvedValueOnce({ dob: '1990-01-01' } as never);
|
|
44
61
|
vi.mocked(isProfileManaged).mockResolvedValueOnce(false);
|
|
45
62
|
|
|
46
|
-
const
|
|
47
|
-
expect(await
|
|
48
|
-
expect(await source.isManaged('owner')).toBe(false);
|
|
49
|
-
expect(getProfileByProfileId).toHaveBeenCalledWith('owner');
|
|
63
|
+
const resolver = createShareLinkPolicyResolver(createProductionShareLinkPolicySource());
|
|
64
|
+
expect((await resolver.resolve('owner')).viewCountingEnabled).toBe(true);
|
|
50
65
|
});
|
|
66
|
+
it.each([
|
|
67
|
+
{ dob: undefined, type: undefined, isServiceProfile: true, eligible: true },
|
|
68
|
+
{ dob: 'not-a-date', type: undefined, isServiceProfile: true, eligible: true },
|
|
69
|
+
{ dob: '2020-01-01', type: undefined, isServiceProfile: true, eligible: true },
|
|
70
|
+
{ dob: '1990-01-01', type: undefined, isServiceProfile: true, eligible: true },
|
|
71
|
+
{ dob: undefined, type: 'child', isServiceProfile: true, eligible: false },
|
|
72
|
+
{ dob: undefined, type: undefined, isServiceProfile: 'true', eligible: false },
|
|
73
|
+
])(
|
|
74
|
+
'uses persisted service classification in preflight and locked policy: $dob / $type / $isServiceProfile',
|
|
75
|
+
async ({ dob, type, isServiceProfile, eligible }) => {
|
|
76
|
+
vi.mocked(getProfileByProfileId).mockResolvedValue({
|
|
77
|
+
dob,
|
|
78
|
+
type,
|
|
79
|
+
isServiceProfile,
|
|
80
|
+
} as never);
|
|
81
|
+
vi.mocked(isProfileManaged).mockResolvedValue(true);
|
|
82
|
+
const resolver = createShareLinkPolicyResolver(createProductionShareLinkPolicySource());
|
|
83
|
+
const preflight = await resolver.resolve('owner');
|
|
84
|
+
const locked = await resolveCurrentShareLinkPolicy(
|
|
85
|
+
transactionWith({ dob, type, isServiceProfile, isManaged: true }),
|
|
86
|
+
'owner',
|
|
87
|
+
NOW
|
|
88
|
+
);
|
|
89
|
+
expect(preflight.viewCountingEnabled).toBe(eligible);
|
|
90
|
+
expect(locked).toEqual({ ...preflight, isServiceProfile: eligible });
|
|
91
|
+
expect(locked.defaultExpiryDays).toBe(30);
|
|
92
|
+
}
|
|
93
|
+
);
|
|
51
94
|
|
|
52
95
|
it('rechecks persisted age and management inside the receipt transaction', async () => {
|
|
53
96
|
const adult = transactionWith({ dob: '1990-01-01', type: null, isManaged: false });
|
|
54
97
|
const managed = transactionWith({ dob: '1990-01-01', type: null, isManaged: true });
|
|
55
98
|
const unknown = transactionWith();
|
|
99
|
+
const managedService = transactionWith({
|
|
100
|
+
dob: null,
|
|
101
|
+
type: null,
|
|
102
|
+
isManaged: true,
|
|
103
|
+
isServiceProfile: true,
|
|
104
|
+
});
|
|
56
105
|
const input = {
|
|
57
106
|
namespace: 'test',
|
|
58
107
|
ownerProfileId: 'OWNER',
|
|
@@ -67,9 +116,9 @@ describe('production share-link age policy', () => {
|
|
|
67
116
|
expect(await productionShareViewEligibilitySource.isEligible(adult, input)).toBe(true);
|
|
68
117
|
expect(await productionShareViewEligibilitySource.isEligible(managed, input)).toBe(false);
|
|
69
118
|
expect(await productionShareViewEligibilitySource.isEligible(unknown, input)).toBe(false);
|
|
70
|
-
expect((
|
|
71
|
-
|
|
72
|
-
|
|
119
|
+
expect(await productionShareViewEligibilitySource.isEligible(managedService, input)).toBe(
|
|
120
|
+
true
|
|
121
|
+
);
|
|
73
122
|
expect(
|
|
74
123
|
(await resolveCurrentShareLinkPolicy(unknown, 'owner', NOW)).viewCountingEnabled
|
|
75
124
|
).toBe(false);
|