@memberjunction/server 6.2.0-edge.2 → 6.2.0-edge.4
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/README.md +30 -0
- package/dist/.tsbuildinfo +1 -1
- package/dist/agentSessions/BridgeHostInstance.d.ts +25 -0
- package/dist/agentSessions/BridgeHostInstance.d.ts.map +1 -0
- package/dist/agentSessions/BridgeHostInstance.js +30 -0
- package/dist/agentSessions/BridgeHostInstance.js.map +1 -0
- package/dist/agentSessions/HostInstance.d.ts +75 -11
- package/dist/agentSessions/HostInstance.d.ts.map +1 -1
- package/dist/agentSessions/HostInstance.js +99 -28
- package/dist/agentSessions/HostInstance.js.map +1 -1
- package/dist/agentSessions/ReturningVisitorRecap.d.ts.map +1 -1
- package/dist/agentSessions/ReturningVisitorRecap.js +7 -3
- package/dist/agentSessions/ReturningVisitorRecap.js.map +1 -1
- package/dist/agentSessions/SessionJanitor.d.ts +173 -25
- package/dist/agentSessions/SessionJanitor.d.ts.map +1 -1
- package/dist/agentSessions/SessionJanitor.js +452 -54
- package/dist/agentSessions/SessionJanitor.js.map +1 -1
- package/dist/agentSessions/SessionManager.d.ts +43 -2
- package/dist/agentSessions/SessionManager.d.ts.map +1 -1
- package/dist/agentSessions/SessionManager.js +99 -8
- package/dist/agentSessions/SessionManager.js.map +1 -1
- package/dist/agentSessions/SessionRunIDVerifier.d.ts +63 -0
- package/dist/agentSessions/SessionRunIDVerifier.d.ts.map +1 -0
- package/dist/agentSessions/SessionRunIDVerifier.js +129 -0
- package/dist/agentSessions/SessionRunIDVerifier.js.map +1 -0
- package/dist/agentSessions/index.d.ts +2 -0
- package/dist/agentSessions/index.d.ts.map +1 -1
- package/dist/agentSessions/index.js +10 -3
- package/dist/agentSessions/index.js.map +1 -1
- package/dist/auth/actionAuthorization.d.ts +40 -0
- package/dist/auth/actionAuthorization.d.ts.map +1 -0
- package/dist/auth/actionAuthorization.js +94 -0
- package/dist/auth/actionAuthorization.js.map +1 -0
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/index.js +3 -10
- package/dist/auth/index.js.map +1 -1
- package/dist/auth/magicLink/MagicLinkService.d.ts.map +1 -1
- package/dist/auth/magicLink/MagicLinkService.js +6 -3
- package/dist/auth/magicLink/MagicLinkService.js.map +1 -1
- package/dist/auth/newUsers.d.ts +10 -0
- package/dist/auth/newUsers.d.ts.map +1 -1
- package/dist/auth/newUsers.js +80 -11
- package/dist/auth/newUsers.js.map +1 -1
- package/dist/config.d.ts +4567 -71
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +246 -3
- package/dist/config.js.map +1 -1
- package/dist/context.d.ts +8 -0
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +25 -34
- package/dist/context.js.map +1 -1
- package/dist/generated/graphql-schemas/__mj.d.ts +1073 -10
- package/dist/generated/graphql-schemas/__mj.d.ts.map +1 -1
- package/dist/generated/graphql-schemas/__mj.js +9188 -3649
- package/dist/generated/graphql-schemas/__mj.js.map +1 -1
- package/dist/generic/OrphanedConversationDetailReconciler.js +3 -3
- package/dist/generic/OrphanedConversationDetailReconciler.js.map +1 -1
- package/dist/generic/ResolverBase.d.ts +62 -6
- package/dist/generic/ResolverBase.d.ts.map +1 -1
- package/dist/generic/ResolverBase.js +179 -35
- package/dist/generic/ResolverBase.js.map +1 -1
- package/dist/generic/RunViewResolver.d.ts +4 -0
- package/dist/generic/RunViewResolver.d.ts.map +1 -1
- package/dist/generic/RunViewResolver.js +37 -1
- package/dist/generic/RunViewResolver.js.map +1 -1
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +214 -159
- package/dist/index.js.map +1 -1
- package/dist/isolatedProvider.d.ts +20 -0
- package/dist/isolatedProvider.d.ts.map +1 -0
- package/dist/isolatedProvider.js +22 -0
- package/dist/isolatedProvider.js.map +1 -0
- package/dist/postgresqlCompat.d.ts +8 -0
- package/dist/postgresqlCompat.d.ts.map +1 -0
- package/dist/postgresqlCompat.js +19 -0
- package/dist/postgresqlCompat.js.map +1 -0
- package/dist/postgresqlPoolSettings.d.ts +108 -0
- package/dist/postgresqlPoolSettings.d.ts.map +1 -0
- package/dist/postgresqlPoolSettings.js +134 -0
- package/dist/postgresqlPoolSettings.js.map +1 -0
- package/dist/realtimeProxy/RealtimeProxyServer.d.ts +44 -4
- package/dist/realtimeProxy/RealtimeProxyServer.d.ts.map +1 -1
- package/dist/realtimeProxy/RealtimeProxyServer.js +121 -16
- package/dist/realtimeProxy/RealtimeProxyServer.js.map +1 -1
- package/dist/realtimeProxy/RealtimeRelayTunnel.d.ts +171 -0
- package/dist/realtimeProxy/RealtimeRelayTunnel.d.ts.map +1 -0
- package/dist/realtimeProxy/RealtimeRelayTunnel.js +532 -0
- package/dist/realtimeProxy/RealtimeRelayTunnel.js.map +1 -0
- package/dist/realtimeSessions/RealtimeSessionEventService.d.ts +148 -0
- package/dist/realtimeSessions/RealtimeSessionEventService.d.ts.map +1 -0
- package/dist/realtimeSessions/RealtimeSessionEventService.js +191 -0
- package/dist/realtimeSessions/RealtimeSessionEventService.js.map +1 -0
- package/dist/realtimeSessions/RealtimeSessionVerificationService.d.ts +66 -0
- package/dist/realtimeSessions/RealtimeSessionVerificationService.d.ts.map +1 -0
- package/dist/realtimeSessions/RealtimeSessionVerificationService.js +138 -0
- package/dist/realtimeSessions/RealtimeSessionVerificationService.js.map +1 -0
- package/dist/realtimeSessions/RealtimeVerifyRouter.d.ts +38 -0
- package/dist/realtimeSessions/RealtimeVerifyRouter.d.ts.map +1 -0
- package/dist/realtimeSessions/RealtimeVerifyRouter.js +98 -0
- package/dist/realtimeSessions/RealtimeVerifyRouter.js.map +1 -0
- package/dist/realtimeSessions/SessionVerificationPortsBase.d.ts +61 -0
- package/dist/realtimeSessions/SessionVerificationPortsBase.d.ts.map +1 -0
- package/dist/realtimeSessions/SessionVerificationPortsBase.js +149 -0
- package/dist/realtimeSessions/SessionVerificationPortsBase.js.map +1 -0
- package/dist/realtimeSessions/SlidingWindowRateLimiter.d.ts +57 -0
- package/dist/realtimeSessions/SlidingWindowRateLimiter.d.ts.map +1 -0
- package/dist/realtimeSessions/SlidingWindowRateLimiter.js +69 -0
- package/dist/realtimeSessions/SlidingWindowRateLimiter.js.map +1 -0
- package/dist/realtimeSessions/index.d.ts +17 -0
- package/dist/realtimeSessions/index.d.ts.map +1 -0
- package/dist/realtimeSessions/index.js +17 -0
- package/dist/realtimeSessions/index.js.map +1 -0
- package/dist/realtimeSessions/sessionAccess.d.ts +86 -0
- package/dist/realtimeSessions/sessionAccess.d.ts.map +1 -0
- package/dist/realtimeSessions/sessionAccess.js +90 -0
- package/dist/realtimeSessions/sessionAccess.js.map +1 -0
- package/dist/realtimeSessions/sessionConfigStamps.d.ts +70 -0
- package/dist/realtimeSessions/sessionConfigStamps.d.ts.map +1 -0
- package/dist/realtimeSessions/sessionConfigStamps.js +93 -0
- package/dist/realtimeSessions/sessionConfigStamps.js.map +1 -0
- package/dist/realtimeSessions/subscriptionAuthorization.d.ts +65 -0
- package/dist/realtimeSessions/subscriptionAuthorization.d.ts.map +1 -0
- package/dist/realtimeSessions/subscriptionAuthorization.js +71 -0
- package/dist/realtimeSessions/subscriptionAuthorization.js.map +1 -0
- package/dist/realtimeSessions/verificationCore.d.ts +243 -0
- package/dist/realtimeSessions/verificationCore.d.ts.map +1 -0
- package/dist/realtimeSessions/verificationCore.js +356 -0
- package/dist/realtimeSessions/verificationCore.js.map +1 -0
- package/dist/realtimeSessions/verificationPages.d.ts +66 -0
- package/dist/realtimeSessions/verificationPages.d.ts.map +1 -0
- package/dist/realtimeSessions/verificationPages.js +110 -0
- package/dist/realtimeSessions/verificationPages.js.map +1 -0
- package/dist/realtimeSessions/verificationPolicy.d.ts +147 -0
- package/dist/realtimeSessions/verificationPolicy.d.ts.map +1 -0
- package/dist/realtimeSessions/verificationPolicy.js +249 -0
- package/dist/realtimeSessions/verificationPolicy.js.map +1 -0
- package/dist/realtimeSessions/verificationWorkflow.d.ts +263 -0
- package/dist/realtimeSessions/verificationWorkflow.d.ts.map +1 -0
- package/dist/realtimeSessions/verificationWorkflow.js +435 -0
- package/dist/realtimeSessions/verificationWorkflow.js.map +1 -0
- package/dist/realtimeWidget/widgetGuestElevation.d.ts +20 -2
- package/dist/realtimeWidget/widgetGuestElevation.d.ts.map +1 -1
- package/dist/realtimeWidget/widgetGuestElevation.js +31 -2
- package/dist/realtimeWidget/widgetGuestElevation.js.map +1 -1
- package/dist/resolvers/ActionResolver.d.ts +31 -5
- package/dist/resolvers/ActionResolver.d.ts.map +1 -1
- package/dist/resolvers/ActionResolver.js +75 -14
- package/dist/resolvers/ActionResolver.js.map +1 -1
- package/dist/resolvers/AdhocQueryResolver.d.ts +35 -17
- package/dist/resolvers/AdhocQueryResolver.d.ts.map +1 -1
- package/dist/resolvers/AdhocQueryResolver.js +92 -166
- package/dist/resolvers/AdhocQueryResolver.js.map +1 -1
- package/dist/resolvers/CacheStatsResolver.d.ts +27 -0
- package/dist/resolvers/CacheStatsResolver.d.ts.map +1 -1
- package/dist/resolvers/CacheStatsResolver.js +113 -2
- package/dist/resolvers/CacheStatsResolver.js.map +1 -1
- package/dist/resolvers/ClientToolRequestResolver.d.ts +4 -2
- package/dist/resolvers/ClientToolRequestResolver.d.ts.map +1 -1
- package/dist/resolvers/ClientToolRequestResolver.js +28 -10
- package/dist/resolvers/ClientToolRequestResolver.js.map +1 -1
- package/dist/resolvers/EntityRecordNameResolver.d.ts +7 -23
- package/dist/resolvers/EntityRecordNameResolver.d.ts.map +1 -1
- package/dist/resolvers/EntityRecordNameResolver.js +9 -47
- package/dist/resolvers/EntityRecordNameResolver.js.map +1 -1
- package/dist/resolvers/FetchEntityVectorsResolver.d.ts +12 -1
- package/dist/resolvers/FetchEntityVectorsResolver.d.ts.map +1 -1
- package/dist/resolvers/FetchEntityVectorsResolver.js +83 -2
- package/dist/resolvers/FetchEntityVectorsResolver.js.map +1 -1
- package/dist/resolvers/GetDataResolver.d.ts.map +1 -1
- package/dist/resolvers/GetDataResolver.js +9 -0
- package/dist/resolvers/GetDataResolver.js.map +1 -1
- package/dist/resolvers/HumanHandoffResolver.d.ts +107 -0
- package/dist/resolvers/HumanHandoffResolver.d.ts.map +1 -0
- package/dist/resolvers/HumanHandoffResolver.js +330 -0
- package/dist/resolvers/HumanHandoffResolver.js.map +1 -0
- package/dist/resolvers/MCPResolver.d.ts.map +1 -1
- package/dist/resolvers/MCPResolver.js +9 -4
- package/dist/resolvers/MCPResolver.js.map +1 -1
- package/dist/resolvers/MeetingResolver.d.ts +69 -0
- package/dist/resolvers/MeetingResolver.d.ts.map +1 -0
- package/dist/resolvers/MeetingResolver.js +381 -0
- package/dist/resolvers/MeetingResolver.js.map +1 -0
- package/dist/resolvers/RealtimeBridgeResolver.d.ts +88 -3
- package/dist/resolvers/RealtimeBridgeResolver.d.ts.map +1 -1
- package/dist/resolvers/RealtimeBridgeResolver.js +352 -12
- package/dist/resolvers/RealtimeBridgeResolver.js.map +1 -1
- package/dist/resolvers/RealtimeClientSessionResolver.d.ts +156 -108
- package/dist/resolvers/RealtimeClientSessionResolver.d.ts.map +1 -1
- package/dist/resolvers/RealtimeClientSessionResolver.js +395 -293
- package/dist/resolvers/RealtimeClientSessionResolver.js.map +1 -1
- package/dist/resolvers/RealtimeSessionEventsResolver.d.ts +61 -0
- package/dist/resolvers/RealtimeSessionEventsResolver.d.ts.map +1 -0
- package/dist/resolvers/RealtimeSessionEventsResolver.js +130 -0
- package/dist/resolvers/RealtimeSessionEventsResolver.js.map +1 -0
- package/dist/resolvers/RealtimeSessionVerificationResolver.d.ts +85 -0
- package/dist/resolvers/RealtimeSessionVerificationResolver.d.ts.map +1 -0
- package/dist/resolvers/RealtimeSessionVerificationResolver.js +188 -0
- package/dist/resolvers/RealtimeSessionVerificationResolver.js.map +1 -0
- package/dist/resolvers/RemoteBrowserActionResolver.d.ts +2 -0
- package/dist/resolvers/RemoteBrowserActionResolver.d.ts.map +1 -1
- package/dist/resolvers/RemoteBrowserActionResolver.js +5 -1
- package/dist/resolvers/RemoteBrowserActionResolver.js.map +1 -1
- package/dist/resolvers/RunAIAgentResolver.d.ts +10 -0
- package/dist/resolvers/RunAIAgentResolver.d.ts.map +1 -1
- package/dist/resolvers/RunAIAgentResolver.js +68 -11
- package/dist/resolvers/RunAIAgentResolver.js.map +1 -1
- package/dist/resolvers/RunTemplateResolver.d.ts.map +1 -1
- package/dist/resolvers/RunTemplateResolver.js +12 -11
- package/dist/resolvers/RunTemplateResolver.js.map +1 -1
- package/dist/resolvers/SyncDataResolver.d.ts.map +1 -1
- package/dist/resolvers/SyncDataResolver.js +6 -2
- package/dist/resolvers/SyncDataResolver.js.map +1 -1
- package/dist/resolvers/SyncRolesUsersResolver.d.ts +2 -0
- package/dist/resolvers/SyncRolesUsersResolver.d.ts.map +1 -1
- package/dist/resolvers/SyncRolesUsersResolver.js +23 -6
- package/dist/resolvers/SyncRolesUsersResolver.js.map +1 -1
- package/dist/resolvers/UserAvatarResolver.d.ts +16 -0
- package/dist/resolvers/UserAvatarResolver.d.ts.map +1 -0
- package/dist/resolvers/UserAvatarResolver.js +124 -0
- package/dist/resolvers/UserAvatarResolver.js.map +1 -0
- package/dist/resolvers/UserResolver.d.ts.map +1 -1
- package/dist/resolvers/UserResolver.js +2 -17
- package/dist/resolvers/UserResolver.js.map +1 -1
- package/dist/resolvers/adhocTimeout.d.ts +7 -0
- package/dist/resolvers/adhocTimeout.d.ts.map +1 -0
- package/dist/resolvers/adhocTimeout.js +12 -0
- package/dist/resolvers/adhocTimeout.js.map +1 -0
- package/dist/resolvers/agentVisionConsentAudit.d.ts +37 -0
- package/dist/resolvers/agentVisionConsentAudit.d.ts.map +1 -0
- package/dist/resolvers/agentVisionConsentAudit.js +63 -0
- package/dist/resolvers/agentVisionConsentAudit.js.map +1 -0
- package/dist/resolvers/avatarInputValidation.d.ts +40 -0
- package/dist/resolvers/avatarInputValidation.d.ts.map +1 -0
- package/dist/resolvers/avatarInputValidation.js +97 -0
- package/dist/resolvers/avatarInputValidation.js.map +1 -0
- package/dist/resolvers/currentUserRoles.d.ts +29 -0
- package/dist/resolvers/currentUserRoles.d.ts.map +1 -1
- package/dist/resolvers/currentUserRoles.js +30 -0
- package/dist/resolvers/currentUserRoles.js.map +1 -1
- package/dist/resolvers/meetings/index.d.ts +5 -0
- package/dist/resolvers/meetings/index.d.ts.map +1 -0
- package/dist/resolvers/meetings/index.js +5 -0
- package/dist/resolvers/meetings/index.js.map +1 -0
- package/dist/resolvers/meetings/meetingHelpers.d.ts +17 -0
- package/dist/resolvers/meetings/meetingHelpers.d.ts.map +1 -0
- package/dist/resolvers/meetings/meetingHelpers.js +212 -0
- package/dist/resolvers/meetings/meetingHelpers.js.map +1 -0
- package/dist/resolvers/meetings/meetingMutations.d.ts +18 -0
- package/dist/resolvers/meetings/meetingMutations.d.ts.map +1 -0
- package/dist/resolvers/meetings/meetingMutations.js +439 -0
- package/dist/resolvers/meetings/meetingMutations.js.map +1 -0
- package/dist/resolvers/meetings/meetingQueries.d.ts +7 -0
- package/dist/resolvers/meetings/meetingQueries.d.ts.map +1 -0
- package/dist/resolvers/meetings/meetingQueries.js +113 -0
- package/dist/resolvers/meetings/meetingQueries.js.map +1 -0
- package/dist/resolvers/meetings/meetingTypes.d.ts +89 -0
- package/dist/resolvers/meetings/meetingTypes.d.ts.map +1 -0
- package/dist/resolvers/meetings/meetingTypes.js +345 -0
- package/dist/resolvers/meetings/meetingTypes.js.map +1 -0
- package/dist/rest/ViewOperationsHandler.d.ts.map +1 -1
- package/dist/rest/ViewOperationsHandler.js +8 -5
- package/dist/rest/ViewOperationsHandler.js.map +1 -1
- package/dist/services/TaskGraphActionRunner.d.ts +8 -0
- package/dist/services/TaskGraphActionRunner.d.ts.map +1 -1
- package/dist/services/TaskGraphActionRunner.js +27 -1
- package/dist/services/TaskGraphActionRunner.js.map +1 -1
- package/dist/services/TaskGraphContinuationDeliverer.d.ts +4 -3
- package/dist/services/TaskGraphContinuationDeliverer.d.ts.map +1 -1
- package/dist/services/TaskGraphContinuationDeliverer.js +13 -7
- package/dist/services/TaskGraphContinuationDeliverer.js.map +1 -1
- package/dist/services/WorkQueueHostService.d.ts +36 -0
- package/dist/services/WorkQueueHostService.d.ts.map +1 -0
- package/dist/services/WorkQueueHostService.js +97 -0
- package/dist/services/WorkQueueHostService.js.map +1 -0
- package/dist/services/workQueueConfig.d.ts +103 -0
- package/dist/services/workQueueConfig.d.ts.map +1 -0
- package/dist/services/workQueueConfig.js +48 -0
- package/dist/services/workQueueConfig.js.map +1 -0
- package/dist/sharedCache.d.ts +105 -0
- package/dist/sharedCache.d.ts.map +1 -0
- package/dist/sharedCache.js +276 -0
- package/dist/sharedCache.js.map +1 -0
- package/dist/types.d.ts +11 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +97 -94
- package/src/__tests__/ActionResolver.authorization.test.ts +353 -0
- package/src/__tests__/AdhocQueryResolver.bugs.test.ts +14 -35
- package/src/__tests__/AdhocQueryResolver.pagination.test.ts +6 -2
- package/src/__tests__/AdhocQueryResolver.scopeGuard.test.ts +3 -3
- package/src/__tests__/AdhocQueryResolver.sqlAuthorizer.test.ts +104 -0
- package/src/__tests__/BridgeHostInstance.test.ts +232 -0
- package/src/__tests__/ClientToolRequestResolver.test.ts +116 -0
- package/src/__tests__/EntityRecordNameResolver.fieldSecurity.test.ts +27 -72
- package/src/__tests__/FetchEntityVectorsResolver.test.ts +285 -0
- package/src/__tests__/HostInstance.test.ts +84 -0
- package/src/__tests__/HumanHandoffResolver.test.ts +250 -0
- package/src/__tests__/MeetingResolver.test.ts +448 -0
- package/src/__tests__/RealtimeBridgeResolver.test.ts +651 -4
- package/src/__tests__/RealtimeClientSessionResolver.configTamper.test.ts +383 -0
- package/src/__tests__/RealtimeClientSessionResolver.test.ts +682 -58
- package/src/__tests__/RealtimeProxyServer.test.ts +16 -1
- package/src/__tests__/RealtimeRelay.test.ts +876 -0
- package/src/__tests__/RemoteBrowserActionResolver.goalRunLink.test.ts +129 -0
- package/src/__tests__/ResolverBase.adhocReadScope.test.ts +96 -0
- package/src/__tests__/ResolverBase.aggregateScreen.test.ts +174 -0
- package/src/__tests__/ResolverBase.clientOldValues.test.ts +6 -3
- package/src/__tests__/ResolverBase.filterEscaping.test.ts +21 -2
- package/src/__tests__/ResolverBase.includeBinaryFields.test.ts +291 -0
- package/src/__tests__/ReturningVisitorRecap.test.ts +115 -0
- package/src/__tests__/RunAIAgentResolver.branch.test.ts +169 -0
- package/src/__tests__/RunAIAgentResolver.historyFrom.test.ts +4 -4
- package/src/__tests__/RunAIAgentResolver.stoppedPredecessor.test.ts +166 -0
- package/src/__tests__/RunTemplateResolver.test.ts +132 -0
- package/src/__tests__/RunViewResolver.cacheCheckForwarding.test.ts +63 -0
- package/src/__tests__/SessionJanitor.test.ts +845 -18
- package/src/__tests__/SessionManager.test.ts +213 -17
- package/src/__tests__/SessionRunIDVerifier.test.ts +251 -0
- package/src/__tests__/SyncRolesUsersResolver.userRoles.test.ts +97 -0
- package/src/__tests__/TaskGraphActionRunner.durablePayload.test.ts +41 -0
- package/src/__tests__/TaskGraphContinuationDeliverer.test.ts +50 -3
- package/src/__tests__/UserAvatarResolver.test.ts +255 -0
- package/src/__tests__/UserResolver.currentUser.test.ts +16 -1
- package/src/__tests__/WorkQueueHostService.test.ts +133 -0
- package/src/__tests__/actionAuthorization.test.ts +130 -0
- package/src/__tests__/adhocTimeoutClamp.test.ts +26 -0
- package/src/__tests__/agentVisionConsentAudit.test.ts +118 -0
- package/src/__tests__/avatarInputValidation.test.ts +160 -0
- package/src/__tests__/isolatedProviderTransactions.test.ts +169 -0
- package/src/__tests__/magicLinkService.consume.test.ts +6 -0
- package/src/__tests__/magicLinkService.provisionLog.test.ts +5 -0
- package/src/__tests__/newUsers.template.test.ts +151 -0
- package/src/__tests__/newUsers.test.ts +74 -5
- package/src/__tests__/orphanedConversationDetailReconciler.test.ts +17 -2
- package/src/__tests__/postgresqlPoolSettings.test.ts +131 -0
- package/src/__tests__/realtimeSessions/RealtimeSessionEventService.test.ts +201 -0
- package/src/__tests__/realtimeSessions/RealtimeSessionEventsResolver.test.ts +215 -0
- package/src/__tests__/realtimeSessions/RealtimeSessionVerificationResolver.test.ts +102 -0
- package/src/__tests__/realtimeSessions/RealtimeSessionVerificationService.test.ts +349 -0
- package/src/__tests__/realtimeSessions/RealtimeVerifyRouter.test.ts +131 -0
- package/src/__tests__/realtimeSessions/SlidingWindowRateLimiter.test.ts +56 -0
- package/src/__tests__/realtimeSessions/helpers.ts +4 -0
- package/src/__tests__/realtimeSessions/sessionAccess.test.ts +129 -0
- package/src/__tests__/realtimeSessions/sessionConfigStamps.test.ts +124 -0
- package/src/__tests__/realtimeSessions/verificationCore.test.ts +355 -0
- package/src/__tests__/realtimeSessions/verificationPages.test.ts +84 -0
- package/src/__tests__/realtimeSessions/verificationPolicy.test.ts +226 -0
- package/src/__tests__/realtimeSessions/verificationWorkflow.test.ts +633 -0
- package/src/__tests__/resolverBase.fls.test.ts +87 -4
- package/src/__tests__/serverExtensionReservedRoots.test.ts +2 -0
- package/src/__tests__/sharedCache.test.ts +310 -0
- package/src/__tests__/translateBracketsToPG.test.ts +20 -0
- package/src/__tests__/widgetGuestElevation.test.ts +44 -0
- package/src/agentSessions/BridgeHostInstance.ts +32 -0
- package/src/agentSessions/HostInstance.ts +124 -28
- package/src/agentSessions/ReturningVisitorRecap.ts +7 -3
- package/src/agentSessions/SessionJanitor.ts +543 -57
- package/src/agentSessions/SessionManager.ts +125 -25
- package/src/agentSessions/SessionRunIDVerifier.ts +171 -0
- package/src/agentSessions/index.ts +10 -3
- package/src/auth/actionAuthorization.ts +116 -0
- package/src/auth/index.ts +3 -8
- package/src/auth/magicLink/MagicLinkService.ts +6 -3
- package/src/auth/newUsers.ts +99 -11
- package/src/config.ts +255 -3
- package/src/context.ts +26 -34
- package/src/generated/graphql-schemas/__mj.ts +22701 -18838
- package/src/generic/OrphanedConversationDetailReconciler.ts +3 -3
- package/src/generic/ResolverBase.ts +210 -36
- package/src/generic/RunViewResolver.ts +37 -2
- package/src/index.ts +236 -170
- package/src/isolatedProvider.ts +22 -0
- package/src/postgresqlCompat.ts +21 -0
- package/src/postgresqlPoolSettings.ts +213 -0
- package/src/realtimeProxy/RealtimeProxyServer.ts +148 -16
- package/src/realtimeProxy/RealtimeRelayTunnel.ts +645 -0
- package/src/realtimeSessions/README.md +179 -0
- package/src/realtimeSessions/RealtimeSessionEventService.ts +274 -0
- package/src/realtimeSessions/RealtimeSessionVerificationService.ts +167 -0
- package/src/realtimeSessions/RealtimeVerifyRouter.ts +108 -0
- package/src/realtimeSessions/SessionVerificationPortsBase.ts +189 -0
- package/src/realtimeSessions/SlidingWindowRateLimiter.ts +92 -0
- package/src/realtimeSessions/index.ts +16 -0
- package/src/realtimeSessions/sessionAccess.ts +141 -0
- package/src/realtimeSessions/sessionConfigStamps.ts +123 -0
- package/src/realtimeSessions/subscriptionAuthorization.ts +116 -0
- package/src/realtimeSessions/verificationCore.ts +502 -0
- package/src/realtimeSessions/verificationPages.ts +149 -0
- package/src/realtimeSessions/verificationPolicy.ts +321 -0
- package/src/realtimeSessions/verificationWorkflow.ts +702 -0
- package/src/realtimeWidget/widgetGuestElevation.ts +34 -2
- package/src/resolvers/ActionResolver.ts +84 -21
- package/src/resolvers/AdhocQueryResolver.ts +105 -187
- package/src/resolvers/CacheStatsResolver.ts +89 -2
- package/src/resolvers/ClientToolRequestResolver.ts +25 -7
- package/src/resolvers/EntityRecordNameResolver.ts +9 -51
- package/src/resolvers/FetchEntityVectorsResolver.ts +97 -2
- package/src/resolvers/GetDataResolver.ts +10 -0
- package/src/resolvers/HumanHandoffResolver.ts +312 -0
- package/src/resolvers/MCPResolver.ts +8 -5
- package/src/resolvers/MeetingResolver.ts +438 -0
- package/src/resolvers/RealtimeBridgeResolver.ts +385 -19
- package/src/resolvers/RealtimeClientSessionResolver.ts +503 -344
- package/src/resolvers/RealtimeSessionEventsResolver.ts +133 -0
- package/src/resolvers/RealtimeSessionVerificationResolver.ts +174 -0
- package/src/resolvers/RemoteBrowserActionResolver.ts +11 -1
- package/src/resolvers/RunAIAgentResolver.ts +78 -11
- package/src/resolvers/RunTemplateResolver.ts +13 -12
- package/src/resolvers/SyncDataResolver.ts +6 -2
- package/src/resolvers/SyncRolesUsersResolver.ts +24 -6
- package/src/resolvers/UserAvatarResolver.ts +111 -0
- package/src/resolvers/UserResolver.ts +2 -16
- package/src/resolvers/adhocTimeout.ts +12 -0
- package/src/resolvers/agentVisionConsentAudit.ts +82 -0
- package/src/resolvers/avatarInputValidation.ts +123 -0
- package/src/resolvers/currentUserRoles.ts +32 -1
- package/src/resolvers/meetings/index.ts +4 -0
- package/src/resolvers/meetings/meetingHelpers.ts +307 -0
- package/src/resolvers/meetings/meetingMutations.ts +556 -0
- package/src/resolvers/meetings/meetingQueries.ts +172 -0
- package/src/resolvers/meetings/meetingTypes.ts +238 -0
- package/src/rest/ViewOperationsHandler.ts +8 -5
- package/src/services/TaskGraphActionRunner.ts +29 -1
- package/src/services/TaskGraphContinuationDeliverer.ts +13 -7
- package/src/services/WorkQueueHostService.ts +129 -0
- package/src/services/workQueueConfig.ts +52 -0
- package/src/sharedCache.ts +302 -0
- package/src/types.ts +12 -0
- package/dist/resolvers/adhoc-query-helpers.d.ts +0 -33
- package/dist/resolvers/adhoc-query-helpers.d.ts.map +0 -1
- package/dist/resolvers/adhoc-query-helpers.js +0 -50
- package/dist/resolvers/adhoc-query-helpers.js.map +0 -1
- package/src/__tests__/adhoc-query-helpers.test.ts +0 -50
- package/src/resolvers/adhoc-query-helpers.ts +0 -67
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for userHandling.newUserTemplate: NewUserBase.createNewUser builds the new user as a copy of a
|
|
3
|
+
* template user through the record-cloning engine, and falls back to a plain user whenever the template
|
|
4
|
+
* can't be used (missing, not flagged IsTemplate, or the copy fails), so sign-in never breaks.
|
|
5
|
+
*/
|
|
6
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
7
|
+
|
|
8
|
+
const {
|
|
9
|
+
mockConfig,
|
|
10
|
+
mockClone,
|
|
11
|
+
mockRunView,
|
|
12
|
+
mockLogError,
|
|
13
|
+
mockProvider,
|
|
14
|
+
savedUsers,
|
|
15
|
+
} = vi.hoisted(() => {
|
|
16
|
+
const savedUsers: Array<Record<string, unknown>> = [];
|
|
17
|
+
const makeUser = () => {
|
|
18
|
+
const user: Record<string, unknown> & { NewRecord: () => void; Load: (id: string) => Promise<boolean>; Save: () => Promise<boolean> } = {
|
|
19
|
+
NewRecord: vi.fn(() => {
|
|
20
|
+
user.ID = 'plain-user-id';
|
|
21
|
+
user.Created = 'plain';
|
|
22
|
+
}),
|
|
23
|
+
Load: vi.fn(async (id: string) => {
|
|
24
|
+
user.ID = id;
|
|
25
|
+
user.Created = 'template';
|
|
26
|
+
return true;
|
|
27
|
+
}),
|
|
28
|
+
Save: vi.fn(async () => {
|
|
29
|
+
savedUsers.push({ ...user });
|
|
30
|
+
return true;
|
|
31
|
+
}),
|
|
32
|
+
LatestResult: null,
|
|
33
|
+
};
|
|
34
|
+
return user;
|
|
35
|
+
};
|
|
36
|
+
return {
|
|
37
|
+
mockConfig: {
|
|
38
|
+
userHandling: {
|
|
39
|
+
newUserRoles: [] as string[],
|
|
40
|
+
CreateUserApplicationRecords: false,
|
|
41
|
+
UserApplications: [] as string[],
|
|
42
|
+
contextUserForNewUserCreation: '',
|
|
43
|
+
newUserTemplate: { email: 'demo@example.org', preset: 'template' } as { email: string; preset: string } | undefined,
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
mockClone: vi.fn(),
|
|
47
|
+
mockRunView: vi.fn(),
|
|
48
|
+
mockLogError: vi.fn(),
|
|
49
|
+
mockProvider: {
|
|
50
|
+
GetEntityObject: vi.fn(async () => makeUser()),
|
|
51
|
+
BeginTransaction: vi.fn(),
|
|
52
|
+
CommitTransaction: vi.fn(),
|
|
53
|
+
RollbackTransaction: vi.fn(),
|
|
54
|
+
Roles: [],
|
|
55
|
+
Applications: [],
|
|
56
|
+
},
|
|
57
|
+
savedUsers,
|
|
58
|
+
};
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
vi.mock('../config.js', () => ({ configInfo: mockConfig }));
|
|
62
|
+
vi.mock('../isolatedProvider.js', () => ({ CreateIsolatedProvider: vi.fn(async () => mockProvider) }));
|
|
63
|
+
vi.mock('../auth/principals.js', () => ({ ResolveConfiguredPrincipal: vi.fn(() => ({ ID: 'system-id', Type: 'Owner' })) }));
|
|
64
|
+
vi.mock('@memberjunction/record-cloning', () => ({
|
|
65
|
+
RecordCloneEngine: class {
|
|
66
|
+
Clone = mockClone;
|
|
67
|
+
},
|
|
68
|
+
}));
|
|
69
|
+
vi.mock('@memberjunction/core', async (importOriginal) => {
|
|
70
|
+
const actual = await importOriginal<typeof import('@memberjunction/core')>();
|
|
71
|
+
return {
|
|
72
|
+
...actual,
|
|
73
|
+
RunView: { FromMetadataProvider: () => ({ RunView: mockRunView }) },
|
|
74
|
+
LogError: mockLogError,
|
|
75
|
+
LogStatus: vi.fn(),
|
|
76
|
+
};
|
|
77
|
+
});
|
|
78
|
+
vi.mock('@memberjunction/core-entities', () => ({ UserInfoEngine: { GetDefaultApplicationsForNewUser: () => [] } }));
|
|
79
|
+
|
|
80
|
+
import { NewUserBase } from '../auth/newUsers.js';
|
|
81
|
+
|
|
82
|
+
const templateRow = (isTemplate: boolean) => ({ Success: true, Results: [{ ID: 'tpl-id', IsTemplate: isTemplate }] });
|
|
83
|
+
const cloned = { Success: true, ResultCode: 'SUCCESS', Warnings: [], RecordsCloned: 120, Roots: [{ EntityName: 'MJ: Users', SourceKey: { KeyValuePairs: [] }, TargetKey: { KeyValuePairs: [{ FieldName: 'ID', Value: 'new-user-id' }] } }] };
|
|
84
|
+
|
|
85
|
+
describe('NewUserBase.createNewUser with newUserTemplate', () => {
|
|
86
|
+
beforeEach(() => {
|
|
87
|
+
vi.clearAllMocks();
|
|
88
|
+
savedUsers.length = 0;
|
|
89
|
+
mockConfig.userHandling.newUserTemplate = { email: 'demo@example.org', preset: 'template' };
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('creates the user as an active copy of the template', async () => {
|
|
93
|
+
mockRunView.mockResolvedValue(templateRow(true));
|
|
94
|
+
mockClone.mockResolvedValue(cloned);
|
|
95
|
+
|
|
96
|
+
const user = await new NewUserBase().createNewUser('Jane', 'Doe', 'jane@example.com');
|
|
97
|
+
|
|
98
|
+
expect(mockClone).toHaveBeenCalledWith(
|
|
99
|
+
expect.objectContaining({
|
|
100
|
+
EntityName: 'MJ: Users',
|
|
101
|
+
SourceRecordKey: expect.objectContaining({ KeyValuePairs: [expect.objectContaining({ FieldName: 'ID', Value: 'tpl-id' })] }),
|
|
102
|
+
Preset: 'template',
|
|
103
|
+
PromptedValues: { Email: 'jane@example.com', FirstName: 'Jane', LastName: 'Doe' },
|
|
104
|
+
}),
|
|
105
|
+
expect.objectContaining({ ID: 'system-id' })
|
|
106
|
+
);
|
|
107
|
+
expect(user?.ID).toBe('new-user-id');
|
|
108
|
+
expect(savedUsers).toEqual([expect.objectContaining({ ID: 'new-user-id', Created: 'template', IsActive: true, LinkedRecordType: 'None' })]);
|
|
109
|
+
expect(mockProvider.BeginTransaction).not.toHaveBeenCalled();
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('falls back to a plain user when the template user is not flagged IsTemplate', async () => {
|
|
113
|
+
mockRunView.mockResolvedValue(templateRow(false));
|
|
114
|
+
|
|
115
|
+
const user = await new NewUserBase().createNewUser('Jane', 'Doe', 'jane@example.com');
|
|
116
|
+
|
|
117
|
+
expect(mockClone).not.toHaveBeenCalled();
|
|
118
|
+
expect(mockLogError.mock.calls.flat().join(' ')).toContain('not marked as a template user');
|
|
119
|
+
expect(user?.ID).toBe('plain-user-id');
|
|
120
|
+
expect(savedUsers[0]).toEqual(expect.objectContaining({ Created: 'plain', Email: 'jane@example.com', IsActive: true }));
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('falls back to a plain user when no user has the template email', async () => {
|
|
124
|
+
mockRunView.mockResolvedValue({ Success: true, Results: [] });
|
|
125
|
+
|
|
126
|
+
const user = await new NewUserBase().createNewUser('Jane', 'Doe', 'jane@example.com');
|
|
127
|
+
|
|
128
|
+
expect(mockClone).not.toHaveBeenCalled();
|
|
129
|
+
expect(user?.ID).toBe('plain-user-id');
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('falls back to a plain user when the copy is blocked', async () => {
|
|
133
|
+
mockRunView.mockResolvedValue(templateRow(true));
|
|
134
|
+
mockClone.mockResolvedValue({ Success: false, ResultCode: 'BLOCKED', Warnings: [{ Severity: 'Error', Code: 'FORBIDDEN', Message: 'needs Clone Records' }] });
|
|
135
|
+
|
|
136
|
+
const user = await new NewUserBase().createNewUser('Jane', 'Doe', 'jane@example.com');
|
|
137
|
+
|
|
138
|
+
expect(mockLogError.mock.calls.flat().join(' ')).toContain('needs Clone Records');
|
|
139
|
+
expect(user?.ID).toBe('plain-user-id');
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it('creates a plain user, as before, when no template is configured', async () => {
|
|
143
|
+
mockConfig.userHandling.newUserTemplate = undefined;
|
|
144
|
+
|
|
145
|
+
const user = await new NewUserBase().createNewUser('Jane', 'Doe', 'jane@example.com');
|
|
146
|
+
|
|
147
|
+
expect(mockRunView).not.toHaveBeenCalled();
|
|
148
|
+
expect(mockClone).not.toHaveBeenCalled();
|
|
149
|
+
expect(user?.ID).toBe('plain-user-id');
|
|
150
|
+
});
|
|
151
|
+
});
|
|
@@ -30,6 +30,8 @@ const {
|
|
|
30
30
|
mockBeginTransaction,
|
|
31
31
|
mockCommitTransaction,
|
|
32
32
|
mockRollbackTransaction,
|
|
33
|
+
mockGlobalProviderCalls,
|
|
34
|
+
mockCreateIsolatedProvider,
|
|
33
35
|
mockGetDefaultApplicationsForNewUser,
|
|
34
36
|
mockRolesArray,
|
|
35
37
|
mockApplicationsArray,
|
|
@@ -69,6 +71,10 @@ const {
|
|
|
69
71
|
mockBeginTransaction: vi.fn(),
|
|
70
72
|
mockCommitTransaction: vi.fn(),
|
|
71
73
|
mockRollbackTransaction: vi.fn(),
|
|
74
|
+
// Every call new-user creation makes on the SHARED Metadata.Provider. Must stay empty: a
|
|
75
|
+
// transaction there nests into every concurrent request's (see CreateIsolatedProvider).
|
|
76
|
+
mockGlobalProviderCalls: [] as string[],
|
|
77
|
+
mockCreateIsolatedProvider: vi.fn(),
|
|
72
78
|
mockGetDefaultApplicationsForNewUser: vi.fn(),
|
|
73
79
|
mockRolesArray: [] as Array<{ ID: string; Name: string }>,
|
|
74
80
|
mockApplicationsArray: [] as Array<{ ID: string; Name: string }>,
|
|
@@ -79,6 +85,9 @@ const {
|
|
|
79
85
|
|
|
80
86
|
vi.mock('../config.js', () => ({ configInfo: mockConfig }));
|
|
81
87
|
|
|
88
|
+
// The provider new-user creation must use: its own instance, so its transaction is its own.
|
|
89
|
+
vi.mock('../isolatedProvider.js', () => ({ CreateIsolatedProvider: mockCreateIsolatedProvider }));
|
|
90
|
+
|
|
82
91
|
vi.mock('@memberjunction/generic-database-provider', () => {
|
|
83
92
|
const instance = {
|
|
84
93
|
get Users() {
|
|
@@ -89,6 +98,18 @@ vi.mock('@memberjunction/generic-database-provider', () => {
|
|
|
89
98
|
},
|
|
90
99
|
Refresh: mockRefresh,
|
|
91
100
|
GetSystemUser: vi.fn(),
|
|
101
|
+
/**
|
|
102
|
+
* The real cache falls back to a database read on a miss. The stub answers from
|
|
103
|
+
* the same array `Users` exposes, so these tests still describe the cache's behaviour.
|
|
104
|
+
*/
|
|
105
|
+
FindUser: vi.fn(async ({ Email }: { Email?: string }) =>
|
|
106
|
+
mockCacheUsers.find((u: UserInfo) => {
|
|
107
|
+
if (!u.Email || u.Email.trim() === '') {
|
|
108
|
+
console.error(`SYSTEM METADATA ISSUE: User ${u.ID} has no email address`);
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
return u.Email.toLowerCase().trim() === (Email ?? '').toLowerCase().trim();
|
|
112
|
+
})),
|
|
92
113
|
};
|
|
93
114
|
return {
|
|
94
115
|
UserCache: class MockUserCache {
|
|
@@ -104,10 +125,11 @@ vi.mock('@memberjunction/generic-database-provider', () => {
|
|
|
104
125
|
|
|
105
126
|
vi.mock('@memberjunction/core', async (importOriginal) => {
|
|
106
127
|
const actual = await importOriginal<typeof import('@memberjunction/core')>();
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
128
|
+
// The shared global provider: records any use instead of doing anything.
|
|
129
|
+
const globalProvider = {
|
|
130
|
+
BeginTransaction: async () => { mockGlobalProviderCalls.push('BeginTransaction'); },
|
|
131
|
+
CommitTransaction: async () => { mockGlobalProviderCalls.push('CommitTransaction'); },
|
|
132
|
+
RollbackTransaction: async () => { mockGlobalProviderCalls.push('RollbackTransaction'); },
|
|
111
133
|
Dialect: { BooleanLiteral: (value: boolean) => (value ? '1' : '0') },
|
|
112
134
|
};
|
|
113
135
|
class MockMetadata {
|
|
@@ -118,9 +140,10 @@ vi.mock('@memberjunction/core', async (importOriginal) => {
|
|
|
118
140
|
return mockApplicationsArray;
|
|
119
141
|
}
|
|
120
142
|
public GetEntityObject(entityName: string, contextUser?: UserInfo): Promise<unknown> {
|
|
143
|
+
mockGlobalProviderCalls.push(`GetEntityObject:${entityName}`);
|
|
121
144
|
return mockGetEntityObject(entityName, contextUser);
|
|
122
145
|
}
|
|
123
|
-
static Provider =
|
|
146
|
+
static Provider = globalProvider;
|
|
124
147
|
}
|
|
125
148
|
class MockUserInfo {
|
|
126
149
|
constructor(_provider: unknown, initData: Record<string, unknown>) {
|
|
@@ -128,6 +151,9 @@ vi.mock('@memberjunction/core', async (importOriginal) => {
|
|
|
128
151
|
}
|
|
129
152
|
}
|
|
130
153
|
class MockRunView {
|
|
154
|
+
public static FromMetadataProvider(_provider: unknown): MockRunView {
|
|
155
|
+
return new MockRunView();
|
|
156
|
+
}
|
|
131
157
|
public RunView(params: unknown, contextUser?: UserInfo): Promise<unknown> {
|
|
132
158
|
return mockRunViewFn(params, contextUser);
|
|
133
159
|
}
|
|
@@ -295,6 +321,21 @@ beforeEach(() => {
|
|
|
295
321
|
});
|
|
296
322
|
mockRunViewFn.mockResolvedValue({ Success: true, Results: [] });
|
|
297
323
|
mockGetDefaultApplicationsForNewUser.mockReturnValue([]);
|
|
324
|
+
|
|
325
|
+
mockGlobalProviderCalls.length = 0;
|
|
326
|
+
mockCreateIsolatedProvider.mockImplementation(async () => ({
|
|
327
|
+
BeginTransaction: mockBeginTransaction,
|
|
328
|
+
CommitTransaction: mockCommitTransaction,
|
|
329
|
+
RollbackTransaction: mockRollbackTransaction,
|
|
330
|
+
Dialect: { BooleanLiteral: (value: boolean) => (value ? '1' : '0') },
|
|
331
|
+
get Roles() {
|
|
332
|
+
return mockRolesArray;
|
|
333
|
+
},
|
|
334
|
+
get Applications() {
|
|
335
|
+
return mockApplicationsArray;
|
|
336
|
+
},
|
|
337
|
+
GetEntityObject: (entityName: string, contextUser?: UserInfo) => mockGetEntityObject(entityName, contextUser),
|
|
338
|
+
}));
|
|
298
339
|
});
|
|
299
340
|
|
|
300
341
|
// ─── NewUserBase.createNewUser ──────────────────────────────────────────────
|
|
@@ -303,6 +344,34 @@ describe('NewUserBase.createNewUser', () => {
|
|
|
303
344
|
const create = (first = 'Ada', last = 'Lovelace', email = 'ada@example.com') =>
|
|
304
345
|
new NewUserBase().createNewUser(first, last, email);
|
|
305
346
|
|
|
347
|
+
describe('provider isolation', () => {
|
|
348
|
+
it('runs the whole provisioning on its own provider, never the shared Metadata.Provider', async () => {
|
|
349
|
+
mockConfig.userHandling.CreateUserApplicationRecords = true;
|
|
350
|
+
mockConfig.userHandling.UserApplications = ['CRM'];
|
|
351
|
+
mockRunViewFn.mockResolvedValue({ Success: true, Results: [{ EntityID: 'ent-1', Entity: 'Accounts' }] });
|
|
352
|
+
|
|
353
|
+
const user = await create();
|
|
354
|
+
|
|
355
|
+
expect(user).toBeTruthy();
|
|
356
|
+
expect(mockCreateIsolatedProvider).toHaveBeenCalledTimes(1);
|
|
357
|
+
expect(mockBeginTransaction).toHaveBeenCalledTimes(1);
|
|
358
|
+
expect(mockCommitTransaction).toHaveBeenCalledTimes(1);
|
|
359
|
+
// User, role, application and application-entity records all came from the isolated one.
|
|
360
|
+
expect(getEntityObjectCalls.map((c) => c.entityName)).toEqual([
|
|
361
|
+
'MJ: Users', 'MJ: User Roles', 'MJ: User Applications', 'MJ: User Application Entities',
|
|
362
|
+
]);
|
|
363
|
+
expect(mockGlobalProviderCalls).toEqual([]);
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
it('rolls back on the isolated provider too, never the shared one', async () => {
|
|
367
|
+
saveBehavior['MJ: Users'] = () => false;
|
|
368
|
+
|
|
369
|
+
expect(await create()).toBeNull();
|
|
370
|
+
expect(mockRollbackTransaction).toHaveBeenCalledTimes(1);
|
|
371
|
+
expect(mockGlobalProviderCalls).toEqual([]);
|
|
372
|
+
});
|
|
373
|
+
});
|
|
374
|
+
|
|
306
375
|
describe('context-user resolution', () => {
|
|
307
376
|
it('uses the configured contextUserForNewUserCreation for every entity it creates', async () => {
|
|
308
377
|
const user = await create();
|
|
@@ -20,8 +20,10 @@ const { mockGetEntityObject, writableStore } = vi.hoisted(() => ({
|
|
|
20
20
|
writableStore: { last: null as Record<string, unknown> | null },
|
|
21
21
|
}));
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
// The real UUID helpers: the reconciler indexes users with IndexByUUID, so a stub that only
|
|
24
|
+
// provides UUIDsEqual would make the owner lookup throw (and the pass silently do nothing).
|
|
25
|
+
vi.mock('@memberjunction/global', async (importOriginal) => ({
|
|
26
|
+
...(await importOriginal<typeof import('@memberjunction/global')>()),
|
|
25
27
|
}));
|
|
26
28
|
|
|
27
29
|
vi.mock('@memberjunction/sqlserver-dataprovider', () => ({
|
|
@@ -230,6 +232,19 @@ describe('ReconcileOrphanedConversationDetails', () => {
|
|
|
230
232
|
);
|
|
231
233
|
});
|
|
232
234
|
|
|
235
|
+
it('resolves the owner when the conversation spells the user ID in a different case', async () => {
|
|
236
|
+
// The cached user is 'OWNER-1'; SQL Server and PostgreSQL disagree on UUID case, so the
|
|
237
|
+
// conversation row may carry it lower-cased. The owner lookup must still find the user.
|
|
238
|
+
script([detail()], [{ ConversationDetailID: DETAIL_ID, Status: 'Failed', CompletedAt: longAgo() }], 'owner-1');
|
|
239
|
+
|
|
240
|
+
await ReconcileOrphanedConversationDetails(PROVIDER, USER);
|
|
241
|
+
|
|
242
|
+
expect(mockGetEntityObject).toHaveBeenCalledWith(
|
|
243
|
+
'MJ: Conversation Details',
|
|
244
|
+
expect.objectContaining({ ID: 'OWNER-1' })
|
|
245
|
+
);
|
|
246
|
+
});
|
|
247
|
+
|
|
233
248
|
it('leaves the detail alone when no owner can be resolved', async () => {
|
|
234
249
|
script([detail()], [{ ConversationDetailID: DETAIL_ID, Status: 'Failed', CompletedAt: longAgo() }], null);
|
|
235
250
|
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PostgreSQL pools read the same databaseSettings values as SQL Server, with the same defaults.
|
|
3
|
+
*/
|
|
4
|
+
import { describe, it, expect } from 'vitest';
|
|
5
|
+
import {
|
|
6
|
+
BuildPostgreSQLConnectionConfig,
|
|
7
|
+
CODEGEN_STATEMENT_TIMEOUT_MS,
|
|
8
|
+
ResolvePostgreSQLEndpoint,
|
|
9
|
+
ResolvePostgreSQLReadOnlyCredentials,
|
|
10
|
+
ToPGPoolConfig
|
|
11
|
+
} from '../postgresqlPoolSettings.js';
|
|
12
|
+
import type { DatabaseSettingsInfo } from '../config.js';
|
|
13
|
+
import { DescribeReadOnlyLoginOverreach } from '../postgresqlPoolSettings.js';
|
|
14
|
+
import type { ReadOnlyLoginCheckClient, ReadOnlyLoginGrants } from '../postgresqlPoolSettings.js';
|
|
15
|
+
|
|
16
|
+
const defaults: DatabaseSettingsInfo = {
|
|
17
|
+
connectionTimeout: 45000,
|
|
18
|
+
requestTimeout: 30000,
|
|
19
|
+
metadataCacheRefreshInterval: 180000,
|
|
20
|
+
connectionPool: { max: 50, min: 5, idleTimeoutMillis: 30000, acquireTimeoutMillis: 30000 }
|
|
21
|
+
};
|
|
22
|
+
const endpoint = { Host: 'db', Port: 5432, Database: 'mj', User: 'api', Password: 'secret' };
|
|
23
|
+
|
|
24
|
+
describe('BuildPostgreSQLConnectionConfig', () => {
|
|
25
|
+
it('gives API pools requestTimeout as the statement timeout and the pool settings SQL Server uses', () => {
|
|
26
|
+
const config = BuildPostgreSQLConnectionConfig(endpoint, defaults, 'api');
|
|
27
|
+
expect(config).toMatchObject({
|
|
28
|
+
...endpoint,
|
|
29
|
+
MaxConnections: 50,
|
|
30
|
+
MinConnections: 5,
|
|
31
|
+
IdleTimeoutMillis: 30000,
|
|
32
|
+
ConnectionTimeoutMillis: 30000,
|
|
33
|
+
StatementTimeoutMs: 30000,
|
|
34
|
+
IdleInTransactionSessionTimeoutMs: 60000
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('follows a configured requestTimeout and pool', () => {
|
|
39
|
+
const config = BuildPostgreSQLConnectionConfig(endpoint, {
|
|
40
|
+
...defaults,
|
|
41
|
+
requestTimeout: 5000,
|
|
42
|
+
connectionPool: { max: 12, min: 1, idleTimeoutMillis: 7000, acquireTimeoutMillis: 9000 }
|
|
43
|
+
}, 'api');
|
|
44
|
+
expect(config).toMatchObject({ MaxConnections: 12, MinConnections: 1, IdleTimeoutMillis: 7000, ConnectionTimeoutMillis: 9000, StatementTimeoutMs: 5000, IdleInTransactionSessionTimeoutMs: 10000 });
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('sets no timeouts when requestTimeout is 0', () => {
|
|
48
|
+
const config = BuildPostgreSQLConnectionConfig(endpoint, { ...defaults, requestTimeout: 0 }, 'api');
|
|
49
|
+
expect(config.StatementTimeoutMs).toBe(0);
|
|
50
|
+
expect(config.IdleInTransactionSessionTimeoutMs).toBe(0);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('keeps the read-only pool small but gives it the API timeouts', () => {
|
|
54
|
+
const config = BuildPostgreSQLConnectionConfig(endpoint, defaults, 'read-only');
|
|
55
|
+
expect(config).toMatchObject({ MaxConnections: 10, MinConnections: 0, StatementTimeoutMs: 30000, IdleInTransactionSessionTimeoutMs: 60000 });
|
|
56
|
+
expect(BuildPostgreSQLConnectionConfig(endpoint, { ...defaults, connectionPool: { max: 4, min: 1, idleTimeoutMillis: 1, acquireTimeoutMillis: 1 } }, 'read-only').MaxConnections).toBe(4);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('gives the CodeGen pool the long timeout the SQL Server CodeGen pool has, and no idle-in-transaction limit', () => {
|
|
60
|
+
const config = BuildPostgreSQLConnectionConfig(endpoint, defaults, 'codegen');
|
|
61
|
+
expect(config.StatementTimeoutMs).toBe(CODEGEN_STATEMENT_TIMEOUT_MS);
|
|
62
|
+
expect(CODEGEN_STATEMENT_TIMEOUT_MS).toBe(600000);
|
|
63
|
+
expect(config.IdleInTransactionSessionTimeoutMs).toBe(0);
|
|
64
|
+
expect(config.MaxConnections).toBe(10);
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
describe('ToPGPoolConfig', () => {
|
|
69
|
+
it('maps the connection config onto pg.Pool option names', () => {
|
|
70
|
+
const pool = ToPGPoolConfig(BuildPostgreSQLConnectionConfig(endpoint, defaults, 'api'));
|
|
71
|
+
expect(pool).toEqual({
|
|
72
|
+
host: 'db', port: 5432, database: 'mj', user: 'api', password: 'secret',
|
|
73
|
+
max: 50, min: 5, idleTimeoutMillis: 30000, connectionTimeoutMillis: 30000,
|
|
74
|
+
statement_timeout: 30000, idle_in_transaction_session_timeout: 60000
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('leaves the timeouts out when they are 0', () => {
|
|
79
|
+
const pool = ToPGPoolConfig(BuildPostgreSQLConnectionConfig(endpoint, { ...defaults, requestTimeout: 0 }, 'api'));
|
|
80
|
+
expect(pool).not.toHaveProperty('statement_timeout');
|
|
81
|
+
expect(pool).not.toHaveProperty('idle_in_transaction_session_timeout');
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
describe('ResolvePostgreSQLEndpoint', () => {
|
|
86
|
+
it('prefers PG_* variables over DB_* ones', () => {
|
|
87
|
+
expect(ResolvePostgreSQLEndpoint({ PG_HOST: 'pg', DB_HOST: 'db', DB_PORT: '6000', DB_DATABASE: 'd', DB_USERNAME: 'u', DB_PASSWORD: 'p' }))
|
|
88
|
+
.toEqual({ Host: 'pg', Port: 6000, Database: 'd', User: 'u', Password: 'p' });
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('falls back to localhost:5432 and the postgres user', () => {
|
|
92
|
+
expect(ResolvePostgreSQLEndpoint({})).toEqual({ Host: 'localhost', Port: 5432, Database: '', User: 'postgres', Password: '' });
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
describe('ResolvePostgreSQLReadOnlyCredentials', () => {
|
|
97
|
+
it('uses the dbReadOnlyUsername / dbReadOnlyPassword settings SQL Server uses', () => {
|
|
98
|
+
expect(ResolvePostgreSQLReadOnlyCredentials({ dbReadOnlyUsername: 'ro', dbReadOnlyPassword: 'pw' }, {}))
|
|
99
|
+
.toEqual({ User: 'ro', Password: 'pw' });
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('falls back to PG_READ_ONLY_* when the settings are absent', () => {
|
|
103
|
+
expect(ResolvePostgreSQLReadOnlyCredentials({}, { PG_READ_ONLY_USERNAME: 'pgro', PG_READ_ONLY_PASSWORD: 'x' }))
|
|
104
|
+
.toEqual({ User: 'pgro', Password: 'x' });
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('returns null when no complete pair is configured', () => {
|
|
108
|
+
expect(ResolvePostgreSQLReadOnlyCredentials({ dbReadOnlyUsername: 'ro' }, { PG_READ_ONLY_USERNAME: 'pgro' })).toBeNull();
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
describe('DescribeReadOnlyLoginOverreach', () => {
|
|
113
|
+
const clientAnswering = (row: ReadOnlyLoginGrants): ReadOnlyLoginCheckClient => ({
|
|
114
|
+
query: async () => ({ rows: [row] }),
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('says nothing for a login that reads only views', async () => {
|
|
118
|
+
expect(await DescribeReadOnlyLoginOverreach(clientAnswering({ ReadsServerFiles: false, ReadableBaseTables: 0 }), '__mj')).toEqual([]);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('warns when the login can read base tables', async () => {
|
|
122
|
+
const warnings = await DescribeReadOnlyLoginOverreach(clientAnswering({ ReadsServerFiles: false, ReadableBaseTables: 412 }), '__mj');
|
|
123
|
+
expect(warnings).toHaveLength(1);
|
|
124
|
+
expect(warnings[0]).toMatch(/can read 412 base table\(s\) in schema "__mj"/);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('warns when the login can read server files', async () => {
|
|
128
|
+
const warnings = await DescribeReadOnlyLoginOverreach(clientAnswering({ ReadsServerFiles: true, ReadableBaseTables: 0 }), '__mj');
|
|
129
|
+
expect(warnings[0]).toMatch(/pg_read_server_files/);
|
|
130
|
+
});
|
|
131
|
+
});
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
import type { IMetadataProvider, UserInfo } from '@memberjunction/core';
|
|
3
|
+
import { MJGlobal } from '@memberjunction/global';
|
|
4
|
+
import type { PubSubEngine } from 'type-graphql';
|
|
5
|
+
import { PubSubManager } from '../../generic/PubSubManager.js';
|
|
6
|
+
import {
|
|
7
|
+
ParseReplicatedRealtimeSessionEvent,
|
|
8
|
+
REALTIME_SESSION_EVENTS_TOPIC,
|
|
9
|
+
RealtimeSessionEventService,
|
|
10
|
+
type RealtimeSessionEventTopicPayload,
|
|
11
|
+
} from '../../realtimeSessions/RealtimeSessionEventService.js';
|
|
12
|
+
|
|
13
|
+
const OWNER = 'AAAAAAAA-0000-4000-8000-000000000001';
|
|
14
|
+
const SESSION_ID = 'BBBBBBBB-0000-4000-8000-000000000001';
|
|
15
|
+
const CONVERSATION_ID = 'CCCCCCCC-0000-4000-8000-000000000001';
|
|
16
|
+
const USER = { ID: OWNER } as UserInfo;
|
|
17
|
+
|
|
18
|
+
const VERIFIED = { VerifiedEmail: 'pat@example.com', VerifiedName: 'Pat', VerifiedAt: '2026-10-02T12:00:00.000Z', Method: 'link' as const };
|
|
19
|
+
|
|
20
|
+
/** A provider that serves one session (+ conversation) — or nothing. */
|
|
21
|
+
function provider(found = true, externalId: string | null = 'scope-1'): IMetadataProvider {
|
|
22
|
+
return {
|
|
23
|
+
GetEntityObject: vi.fn(async (name: string) => {
|
|
24
|
+
if (name === 'MJ: AI Agent Sessions') {
|
|
25
|
+
return found
|
|
26
|
+
? { ID: SESSION_ID, UserID: OWNER, ConversationID: CONVERSATION_ID, Load: vi.fn(async () => true) }
|
|
27
|
+
: { Load: vi.fn(async () => false) };
|
|
28
|
+
}
|
|
29
|
+
return { ExternalID: externalId, Load: vi.fn(async () => true) };
|
|
30
|
+
}),
|
|
31
|
+
} as unknown as IMetadataProvider;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
describe('RealtimeSessionEventService', () => {
|
|
35
|
+
const published: Array<{ topic: string; payload: RealtimeSessionEventTopicPayload }> = [];
|
|
36
|
+
|
|
37
|
+
beforeEach(() => {
|
|
38
|
+
published.length = 0;
|
|
39
|
+
const engine = { publish: vi.fn(async (topic: string, payload: RealtimeSessionEventTopicPayload) => void published.push({ topic, payload })) };
|
|
40
|
+
PubSubManager.Instance.SetPubSubEngine(engine as unknown as PubSubEngine);
|
|
41
|
+
RealtimeSessionEventService.Instance.SetReplicationHook(undefined);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
describe('Publish', () => {
|
|
45
|
+
it('publishes on the shared topic with the session routing snapshot', async () => {
|
|
46
|
+
const result = await RealtimeSessionEventService.Instance.Publish({
|
|
47
|
+
AgentSessionID: SESSION_ID,
|
|
48
|
+
Type: 'identity.verified',
|
|
49
|
+
Payload: VERIFIED,
|
|
50
|
+
ContextUser: USER,
|
|
51
|
+
Provider: provider(),
|
|
52
|
+
});
|
|
53
|
+
expect(result).toEqual({ Success: true });
|
|
54
|
+
expect(published).toHaveLength(1);
|
|
55
|
+
expect(published[0].topic).toBe(REALTIME_SESSION_EVENTS_TOPIC);
|
|
56
|
+
expect(published[0].payload).toMatchObject({
|
|
57
|
+
Type: 'identity.verified',
|
|
58
|
+
AgentSessionID: SESSION_ID,
|
|
59
|
+
OwnerUserID: OWNER,
|
|
60
|
+
ScopeKey: 'scope-1',
|
|
61
|
+
SourceServerId: MJGlobal.Instance.ProcessUUID,
|
|
62
|
+
});
|
|
63
|
+
expect(JSON.parse(published[0].payload.PayloadJson)).toEqual(VERIFIED);
|
|
64
|
+
expect(Date.parse(published[0].payload.OccurredAt)).not.toBeNaN();
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('snapshots a null scope for a session whose conversation carries none', async () => {
|
|
68
|
+
await RealtimeSessionEventService.Instance.Publish({
|
|
69
|
+
AgentSessionID: SESSION_ID,
|
|
70
|
+
Type: 'identity.verified',
|
|
71
|
+
Payload: VERIFIED,
|
|
72
|
+
ContextUser: USER,
|
|
73
|
+
Provider: provider(true, null),
|
|
74
|
+
});
|
|
75
|
+
expect(published[0].payload.ScopeKey).toBeNull();
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('fails (and publishes nothing) when the session cannot be found', async () => {
|
|
79
|
+
const result = await RealtimeSessionEventService.Instance.Publish({
|
|
80
|
+
AgentSessionID: SESSION_ID,
|
|
81
|
+
Type: 'identity.verified',
|
|
82
|
+
Payload: VERIFIED,
|
|
83
|
+
ContextUser: USER,
|
|
84
|
+
Provider: provider(false),
|
|
85
|
+
});
|
|
86
|
+
expect(result.Success).toBe(false);
|
|
87
|
+
expect(result.ErrorMessage).toContain('not found');
|
|
88
|
+
expect(published).toHaveLength(0);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('returns a failure instead of throwing when reading the session throws', async () => {
|
|
92
|
+
const broken = { GetEntityObject: vi.fn(async () => { throw new Error('db down'); }) } as unknown as IMetadataProvider;
|
|
93
|
+
const result = await RealtimeSessionEventService.Instance.Publish({
|
|
94
|
+
AgentSessionID: SESSION_ID,
|
|
95
|
+
Type: 'identity.verified',
|
|
96
|
+
Payload: VERIFIED,
|
|
97
|
+
ContextUser: USER,
|
|
98
|
+
Provider: broken,
|
|
99
|
+
});
|
|
100
|
+
expect(result).toMatchObject({ Success: false });
|
|
101
|
+
expect(result.ErrorMessage).toContain('db down');
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
describe('PublishRouted', () => {
|
|
106
|
+
it('fails loudly when PubSub is not configured', () => {
|
|
107
|
+
PubSubManager.Instance.SetPubSubEngine(null as unknown as PubSubEngine);
|
|
108
|
+
const result = RealtimeSessionEventService.Instance.PublishRouted({ AgentSessionID: SESSION_ID, OwnerUserID: OWNER, ScopeKey: null }, 'identity.verified', VERIFIED);
|
|
109
|
+
expect(result).toMatchObject({ Success: false });
|
|
110
|
+
expect(result.ErrorMessage).toContain('PubSub is not configured');
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('hands every locally published event to the replication hook', () => {
|
|
114
|
+
const hook = vi.fn();
|
|
115
|
+
RealtimeSessionEventService.Instance.SetReplicationHook(hook);
|
|
116
|
+
RealtimeSessionEventService.Instance.PublishRouted({ AgentSessionID: SESSION_ID, OwnerUserID: OWNER, ScopeKey: null }, 'identity.verified', VERIFIED);
|
|
117
|
+
expect(hook).toHaveBeenCalledTimes(1);
|
|
118
|
+
expect(hook.mock.calls[0][0]).toMatchObject({ AgentSessionID: SESSION_ID, OwnerUserID: OWNER, Type: 'identity.verified' });
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('never lets a failing replication hook break local delivery', () => {
|
|
122
|
+
RealtimeSessionEventService.Instance.SetReplicationHook(() => {
|
|
123
|
+
throw new Error('redis down');
|
|
124
|
+
});
|
|
125
|
+
const result = RealtimeSessionEventService.Instance.PublishRouted({ AgentSessionID: SESSION_ID, OwnerUserID: OWNER, ScopeKey: null }, 'identity.verified', VERIFIED);
|
|
126
|
+
expect(result).toEqual({ Success: true });
|
|
127
|
+
expect(published).toHaveLength(1);
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
describe('PublishReplicated', () => {
|
|
132
|
+
it('republishes locally and never back through the replication hook (no loop)', () => {
|
|
133
|
+
const hook = vi.fn();
|
|
134
|
+
RealtimeSessionEventService.Instance.SetReplicationHook(hook);
|
|
135
|
+
const payload: RealtimeSessionEventTopicPayload = {
|
|
136
|
+
Type: 'identity.verified',
|
|
137
|
+
AgentSessionID: SESSION_ID,
|
|
138
|
+
OccurredAt: '2026-10-02T12:00:00.000Z',
|
|
139
|
+
PayloadJson: JSON.stringify(VERIFIED),
|
|
140
|
+
OwnerUserID: OWNER,
|
|
141
|
+
ScopeKey: null,
|
|
142
|
+
SourceServerId: 'other-instance',
|
|
143
|
+
};
|
|
144
|
+
RealtimeSessionEventService.Instance.PublishReplicated(payload);
|
|
145
|
+
expect(published).toEqual([{ topic: REALTIME_SESSION_EVENTS_TOPIC, payload }]);
|
|
146
|
+
expect(hook).not.toHaveBeenCalled();
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
describe('ParseReplicatedRealtimeSessionEvent', () => {
|
|
152
|
+
const wire = (over: Record<string, unknown> = {}) =>
|
|
153
|
+
JSON.stringify({
|
|
154
|
+
Type: 'identity.verified',
|
|
155
|
+
AgentSessionID: SESSION_ID,
|
|
156
|
+
OccurredAt: '2026-10-02T12:00:00.000Z',
|
|
157
|
+
PayloadJson: '{}',
|
|
158
|
+
OwnerUserID: OWNER,
|
|
159
|
+
ScopeKey: 'scope-1',
|
|
160
|
+
SourceServerId: 'other-instance',
|
|
161
|
+
...over,
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it('accepts a well-formed event from another instance', () => {
|
|
165
|
+
expect(ParseReplicatedRealtimeSessionEvent(wire(), 'this-instance')).toEqual({
|
|
166
|
+
Type: 'identity.verified',
|
|
167
|
+
AgentSessionID: SESSION_ID,
|
|
168
|
+
OccurredAt: '2026-10-02T12:00:00.000Z',
|
|
169
|
+
PayloadJson: '{}',
|
|
170
|
+
OwnerUserID: OWNER,
|
|
171
|
+
ScopeKey: 'scope-1',
|
|
172
|
+
SourceServerId: 'other-instance',
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it('drops our own echo', () => {
|
|
177
|
+
expect(ParseReplicatedRealtimeSessionEvent(wire({ SourceServerId: 'this-instance' }), 'this-instance')).toBeNull();
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it('fails closed when the routing identity is missing or malformed', () => {
|
|
181
|
+
expect(ParseReplicatedRealtimeSessionEvent(wire({ OwnerUserID: undefined }), 'x')).toBeNull();
|
|
182
|
+
expect(ParseReplicatedRealtimeSessionEvent(wire({ OwnerUserID: '' }), 'x')).toBeNull();
|
|
183
|
+
expect(ParseReplicatedRealtimeSessionEvent(wire({ OwnerUserID: 5 }), 'x')).toBeNull();
|
|
184
|
+
expect(ParseReplicatedRealtimeSessionEvent(wire({ ScopeKey: 5 }), 'x')).toBeNull();
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it('normalises an absent scope to null and tolerates an absent source', () => {
|
|
188
|
+
const parsed = ParseReplicatedRealtimeSessionEvent(wire({ ScopeKey: undefined, SourceServerId: undefined }), 'x');
|
|
189
|
+
expect(parsed?.ScopeKey).toBeNull();
|
|
190
|
+
expect(parsed?.SourceServerId).toBeUndefined();
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it('drops non-JSON, non-objects and events missing required strings', () => {
|
|
194
|
+
expect(ParseReplicatedRealtimeSessionEvent('{nope', 'x')).toBeNull();
|
|
195
|
+
expect(ParseReplicatedRealtimeSessionEvent('"text"', 'x')).toBeNull();
|
|
196
|
+
expect(ParseReplicatedRealtimeSessionEvent('null', 'x')).toBeNull();
|
|
197
|
+
expect(ParseReplicatedRealtimeSessionEvent(wire({ Type: undefined }), 'x')).toBeNull();
|
|
198
|
+
expect(ParseReplicatedRealtimeSessionEvent(wire({ PayloadJson: undefined }), 'x')).toBeNull();
|
|
199
|
+
expect(ParseReplicatedRealtimeSessionEvent(wire({ SourceServerId: 7 }), 'x')).toBeNull();
|
|
200
|
+
});
|
|
201
|
+
});
|