@memberjunction/server 6.1.4 → 6.2.0-edge.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.tsbuildinfo +1 -1
- package/dist/agentSessions/ReturningVisitorRecap.d.ts +2 -0
- package/dist/agentSessions/ReturningVisitorRecap.d.ts.map +1 -1
- package/dist/agentSessions/ReturningVisitorRecap.js +5 -1
- package/dist/agentSessions/ReturningVisitorRecap.js.map +1 -1
- package/dist/agentSessions/SessionManager.js +2 -2
- package/dist/agentSessions/remoteBrowserGoalEngine.d.ts +8 -0
- package/dist/agentSessions/remoteBrowserGoalEngine.d.ts.map +1 -1
- package/dist/agentSessions/remoteBrowserGoalEngine.js +21 -5
- package/dist/agentSessions/remoteBrowserGoalEngine.js.map +1 -1
- package/dist/auth/AuthProviderCatalogRouter.d.ts +2 -0
- package/dist/auth/AuthProviderCatalogRouter.d.ts.map +1 -1
- package/dist/auth/AuthProviderCatalogRouter.js +5 -1
- package/dist/auth/AuthProviderCatalogRouter.js.map +1 -1
- package/dist/auth/exampleNewUserSubClass.js +2 -1
- package/dist/auth/exampleNewUserSubClass.js.map +1 -1
- package/dist/auth/index.d.ts +20 -1
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/index.js +20 -10
- package/dist/auth/index.js.map +1 -1
- package/dist/auth/initializeProviders.d.ts +8 -0
- package/dist/auth/initializeProviders.d.ts.map +1 -1
- package/dist/auth/initializeProviders.js +21 -5
- package/dist/auth/initializeProviders.js.map +1 -1
- package/dist/auth/magicLink/MagicLinkRouter.d.ts +9 -0
- package/dist/auth/magicLink/MagicLinkRouter.d.ts.map +1 -1
- package/dist/auth/magicLink/MagicLinkRouter.js +22 -10
- package/dist/auth/magicLink/MagicLinkRouter.js.map +1 -1
- package/dist/auth/magicLink/MagicLinkService.d.ts +17 -15
- package/dist/auth/magicLink/MagicLinkService.d.ts.map +1 -1
- package/dist/auth/magicLink/MagicLinkService.js +65 -51
- package/dist/auth/magicLink/MagicLinkService.js.map +1 -1
- package/dist/auth/magicLink/index.d.ts +2 -2
- package/dist/auth/magicLink/index.d.ts.map +1 -1
- package/dist/auth/magicLink/index.js +2 -2
- package/dist/auth/magicLink/index.js.map +1 -1
- package/dist/auth/magicLink/magicLinkCore.d.ts +54 -33
- package/dist/auth/magicLink/magicLinkCore.d.ts.map +1 -1
- package/dist/auth/magicLink/magicLinkCore.js +61 -61
- package/dist/auth/magicLink/magicLinkCore.js.map +1 -1
- package/dist/auth/magicLink/redeemLanding.d.ts +4 -0
- package/dist/auth/magicLink/redeemLanding.d.ts.map +1 -1
- package/dist/auth/magicLink/redeemLanding.js +12 -4
- package/dist/auth/magicLink/redeemLanding.js.map +1 -1
- package/dist/auth/magicLink/types.d.ts +4 -4
- package/dist/auth/principals.d.ts +2 -0
- package/dist/auth/principals.d.ts.map +1 -1
- package/dist/auth/principals.js +6 -2
- package/dist/auth/principals.js.map +1 -1
- package/dist/cache.d.ts +2 -0
- package/dist/cache.d.ts.map +1 -1
- package/dist/cache.js +3 -1
- package/dist/cache.js.map +1 -1
- package/dist/config.d.ts +293 -2
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +11 -4
- package/dist/config.js.map +1 -1
- package/dist/context.d.ts +20 -0
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +27 -19
- package/dist/context.js.map +1 -1
- package/dist/directives/Public.d.ts +2 -0
- package/dist/directives/Public.d.ts.map +1 -1
- package/dist/directives/Public.js +3 -1
- package/dist/directives/Public.js.map +1 -1
- package/dist/directives/RequireSystemUser.d.ts +2 -0
- package/dist/directives/RequireSystemUser.d.ts.map +1 -1
- package/dist/directives/RequireSystemUser.js +3 -1
- package/dist/directives/RequireSystemUser.js.map +1 -1
- package/dist/generated/graphql-schemas/__mj.d.ts +266 -4
- package/dist/generated/graphql-schemas/__mj.d.ts.map +1 -1
- package/dist/generated/graphql-schemas/__mj.js +1304 -0
- package/dist/generated/graphql-schemas/__mj.js.map +1 -1
- package/dist/generic/CacheInvalidationResolver.d.ts +8 -2
- package/dist/generic/CacheInvalidationResolver.d.ts.map +1 -1
- package/dist/generic/CacheInvalidationResolver.js +7 -3
- package/dist/generic/CacheInvalidationResolver.js.map +1 -1
- package/dist/generic/FireAndForgetHeartbeat.d.ts +3 -1
- package/dist/generic/FireAndForgetHeartbeat.d.ts.map +1 -1
- package/dist/generic/FireAndForgetHeartbeat.js +8 -4
- package/dist/generic/FireAndForgetHeartbeat.js.map +1 -1
- package/dist/generic/PushStatusResolver.d.ts +10 -1
- package/dist/generic/PushStatusResolver.d.ts.map +1 -1
- package/dist/generic/PushStatusResolver.js +11 -3
- package/dist/generic/PushStatusResolver.js.map +1 -1
- package/dist/generic/ResolverBase.d.ts +27 -1
- package/dist/generic/ResolverBase.d.ts.map +1 -1
- package/dist/generic/ResolverBase.js +64 -6
- package/dist/generic/ResolverBase.js.map +1 -1
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +29 -6
- package/dist/index.js.map +1 -1
- package/dist/integration/CustomColumnPromoter.d.ts +2 -0
- package/dist/integration/CustomColumnPromoter.d.ts.map +1 -1
- package/dist/integration/CustomColumnPromoter.js +5 -1
- package/dist/integration/CustomColumnPromoter.js.map +1 -1
- package/dist/integration/EntityMapLifecycle.d.ts +4 -0
- package/dist/integration/EntityMapLifecycle.d.ts.map +1 -1
- package/dist/integration/EntityMapLifecycle.js +10 -2
- package/dist/integration/EntityMapLifecycle.js.map +1 -1
- package/dist/logging/NoLog.d.ts +4 -0
- package/dist/logging/NoLog.d.ts.map +1 -1
- package/dist/logging/NoLog.js +10 -2
- package/dist/logging/NoLog.js.map +1 -1
- package/dist/logging/StartupLogger.d.ts +2 -0
- package/dist/logging/StartupLogger.d.ts.map +1 -1
- package/dist/logging/StartupLogger.js +5 -1
- package/dist/logging/StartupLogger.js.map +1 -1
- package/dist/logging/bootAudit.d.ts +11 -7
- package/dist/logging/bootAudit.d.ts.map +1 -1
- package/dist/logging/bootAudit.js +15 -7
- package/dist/logging/bootAudit.js.map +1 -1
- package/dist/logging/boundaryLogPayload.d.ts +4 -0
- package/dist/logging/boundaryLogPayload.d.ts.map +1 -1
- package/dist/logging/boundaryLogPayload.js +5 -1
- package/dist/logging/boundaryLogPayload.js.map +1 -1
- package/dist/logging/secretRedactor.d.ts +2 -0
- package/dist/logging/secretRedactor.d.ts.map +1 -1
- package/dist/logging/secretRedactor.js +8 -4
- package/dist/logging/secretRedactor.js.map +1 -1
- package/dist/logging/shortenForLog.d.ts +2 -0
- package/dist/logging/shortenForLog.d.ts.map +1 -1
- package/dist/logging/shortenForLog.js +7 -3
- package/dist/logging/shortenForLog.js.map +1 -1
- package/dist/logging/variablesLoggingMiddleware.d.ts +2 -0
- package/dist/logging/variablesLoggingMiddleware.d.ts.map +1 -1
- package/dist/logging/variablesLoggingMiddleware.js +8 -6
- package/dist/logging/variablesLoggingMiddleware.js.map +1 -1
- package/dist/metadataRefreshSignal.d.ts +19 -0
- package/dist/metadataRefreshSignal.d.ts.map +1 -0
- package/dist/metadataRefreshSignal.js +45 -0
- package/dist/metadataRefreshSignal.js.map +1 -0
- package/dist/middleware/MJTenantFilterMiddleware.js +4 -4
- package/dist/multiTenancy/index.d.ts +8 -0
- package/dist/multiTenancy/index.d.ts.map +1 -1
- package/dist/multiTenancy/index.js +21 -5
- package/dist/multiTenancy/index.js.map +1 -1
- package/dist/realtimeWidget/WidgetRouter.d.ts +5 -0
- package/dist/realtimeWidget/WidgetRouter.d.ts.map +1 -1
- package/dist/realtimeWidget/WidgetRouter.js +14 -10
- package/dist/realtimeWidget/WidgetRouter.js.map +1 -1
- package/dist/realtimeWidget/WidgetSessionService.d.ts +6 -6
- package/dist/realtimeWidget/WidgetSessionService.js +27 -27
- package/dist/realtimeWidget/host-identity.d.ts +5 -1
- package/dist/realtimeWidget/host-identity.d.ts.map +1 -1
- package/dist/realtimeWidget/host-identity.js +11 -3
- package/dist/realtimeWidget/host-identity.js.map +1 -1
- package/dist/realtimeWidget/index.d.ts +2 -2
- package/dist/realtimeWidget/index.d.ts.map +1 -1
- package/dist/realtimeWidget/index.js +2 -2
- package/dist/realtimeWidget/index.js.map +1 -1
- package/dist/realtimeWidget/visitorIdentity.d.ts +21 -1
- package/dist/realtimeWidget/visitorIdentity.d.ts.map +1 -1
- package/dist/realtimeWidget/visitorIdentity.js +16 -4
- package/dist/realtimeWidget/visitorIdentity.js.map +1 -1
- package/dist/realtimeWidget/widgetCore.d.ts +48 -0
- package/dist/realtimeWidget/widgetCore.d.ts.map +1 -1
- package/dist/realtimeWidget/widgetCore.js +38 -10
- package/dist/realtimeWidget/widgetCore.js.map +1 -1
- package/dist/realtimeWidget/widgetGuestElevation.d.ts +10 -6
- package/dist/realtimeWidget/widgetGuestElevation.d.ts.map +1 -1
- package/dist/realtimeWidget/widgetGuestElevation.js +14 -6
- package/dist/realtimeWidget/widgetGuestElevation.js.map +1 -1
- package/dist/resolvers/AdhocQueryResolver.js +3 -3
- package/dist/resolvers/FetchEntityVectorsResolver.js +2 -2
- package/dist/resolvers/FetchEntityVectorsResolver.js.map +1 -1
- package/dist/resolvers/FileResolver.js +3 -3
- package/dist/resolvers/GeoResolver.d.ts +2 -2
- package/dist/resolvers/GeoResolver.js +4 -4
- package/dist/resolvers/GetDataResolver.d.ts +14 -0
- package/dist/resolvers/GetDataResolver.d.ts.map +1 -1
- package/dist/resolvers/GetDataResolver.js +43 -15
- package/dist/resolvers/GetDataResolver.js.map +1 -1
- package/dist/resolvers/IntegrationDiscoveryResolver.js +2 -2
- package/dist/resolvers/ListOperationsResolver.d.ts +20 -0
- package/dist/resolvers/ListOperationsResolver.d.ts.map +1 -1
- package/dist/resolvers/ListOperationsResolver.js +68 -28
- package/dist/resolvers/ListOperationsResolver.js.map +1 -1
- package/dist/resolvers/MCPResolver.d.ts +11 -1
- package/dist/resolvers/MCPResolver.d.ts.map +1 -1
- package/dist/resolvers/MCPResolver.js +7 -3
- package/dist/resolvers/MCPResolver.js.map +1 -1
- package/dist/resolvers/QuerySystemUserResolver.d.ts.map +1 -1
- package/dist/resolvers/QuerySystemUserResolver.js +2 -1
- package/dist/resolvers/QuerySystemUserResolver.js.map +1 -1
- package/dist/resolvers/RealtimeBridgeResolver.js +3 -3
- package/dist/resolvers/RealtimeClientSessionResolver.d.ts +37 -0
- package/dist/resolvers/RealtimeClientSessionResolver.d.ts.map +1 -1
- package/dist/resolvers/RealtimeClientSessionResolver.js +101 -11
- package/dist/resolvers/RealtimeClientSessionResolver.js.map +1 -1
- package/dist/resolvers/RemoteBrowserActionResolver.js +5 -5
- package/dist/resolvers/RunAIAgentResolver.d.ts +10 -4
- package/dist/resolvers/RunAIAgentResolver.d.ts.map +1 -1
- package/dist/resolvers/RunAIAgentResolver.js +74 -42
- package/dist/resolvers/RunAIAgentResolver.js.map +1 -1
- package/dist/resolvers/RunAIPromptResolver.d.ts +1 -1
- package/dist/resolvers/RunAIPromptResolver.js +2 -2
- package/dist/resolvers/RunTestResolver.js +5 -5
- package/dist/resolvers/SearchEntitiesResolver.d.ts.map +1 -1
- package/dist/resolvers/SearchEntitiesResolver.js +3 -1
- package/dist/resolvers/SearchEntitiesResolver.js.map +1 -1
- package/dist/resolvers/SearchKnowledgeResolver.d.ts +1 -0
- package/dist/resolvers/SearchKnowledgeResolver.d.ts.map +1 -1
- package/dist/resolvers/SearchKnowledgeResolver.js +5 -0
- package/dist/resolvers/SearchKnowledgeResolver.js.map +1 -1
- package/dist/resolvers/SearchKnowledgeSystemUserResolver.d.ts.map +1 -1
- package/dist/resolvers/SearchKnowledgeSystemUserResolver.js +1 -0
- package/dist/resolvers/SearchKnowledgeSystemUserResolver.js.map +1 -1
- package/dist/resolvers/SqlLoggingConfigResolver.d.ts +8 -4
- package/dist/resolvers/SqlLoggingConfigResolver.d.ts.map +1 -1
- package/dist/resolvers/SqlLoggingConfigResolver.js +5 -5
- package/dist/resolvers/TaskGraphFrameResolver.d.ts +8 -1
- package/dist/resolvers/TaskGraphFrameResolver.d.ts.map +1 -1
- package/dist/resolvers/TaskGraphFrameResolver.js +6 -2
- package/dist/resolvers/TaskGraphFrameResolver.js.map +1 -1
- package/dist/resolvers/TransactionGroupResolver.d.ts.map +1 -1
- package/dist/resolvers/TransactionGroupResolver.js +53 -4
- package/dist/resolvers/TransactionGroupResolver.js.map +1 -1
- package/dist/resolvers/adhoc-query-helpers.d.ts +6 -0
- package/dist/resolvers/adhoc-query-helpers.d.ts.map +1 -1
- package/dist/resolvers/adhoc-query-helpers.js +10 -2
- package/dist/resolvers/adhoc-query-helpers.js.map +1 -1
- package/dist/resolvers/artifact-routing.d.ts +8 -8
- package/dist/resolvers/artifact-routing.js +2 -2
- package/dist/resolvers/artifact-routing.js.map +1 -1
- package/dist/resolvers/meetingRecordingRegistration.d.ts +10 -2
- package/dist/resolvers/meetingRecordingRegistration.d.ts.map +1 -1
- package/dist/resolvers/meetingRecordingRegistration.js +24 -8
- package/dist/resolvers/meetingRecordingRegistration.js.map +1 -1
- package/dist/resolvers/peaksSidecar.d.ts +4 -0
- package/dist/resolvers/peaksSidecar.d.ts.map +1 -1
- package/dist/resolvers/peaksSidecar.js +10 -2
- package/dist/resolvers/peaksSidecar.js.map +1 -1
- package/dist/rest/EntityCRUDHandler.d.ts +28 -0
- package/dist/rest/EntityCRUDHandler.d.ts.map +1 -1
- package/dist/rest/EntityCRUDHandler.js +20 -4
- package/dist/rest/EntityCRUDHandler.js.map +1 -1
- package/dist/rest/MediaStreamHandler.d.ts +2 -0
- package/dist/rest/MediaStreamHandler.d.ts.map +1 -1
- package/dist/rest/MediaStreamHandler.js +10 -6
- package/dist/rest/MediaStreamHandler.js.map +1 -1
- package/dist/rest/OAuthCallbackHandler.d.ts +9 -0
- package/dist/rest/OAuthCallbackHandler.d.ts.map +1 -1
- package/dist/rest/OAuthCallbackHandler.js +15 -3
- package/dist/rest/OAuthCallbackHandler.js.map +1 -1
- package/dist/rest/RESTEndpointHandler.d.ts +6 -4
- package/dist/rest/RESTEndpointHandler.d.ts.map +1 -1
- package/dist/rest/RESTEndpointHandler.js +15 -11
- package/dist/rest/RESTEndpointHandler.js.map +1 -1
- package/dist/rest/RealtimeSdpBrokerHandler.d.ts +5 -1
- package/dist/rest/RealtimeSdpBrokerHandler.d.ts.map +1 -1
- package/dist/rest/RealtimeSdpBrokerHandler.js +12 -4
- package/dist/rest/RealtimeSdpBrokerHandler.js.map +1 -1
- package/dist/rest/SignatureWebhookHandler.d.ts +2 -0
- package/dist/rest/SignatureWebhookHandler.d.ts.map +1 -1
- package/dist/rest/SignatureWebhookHandler.js +5 -1
- package/dist/rest/SignatureWebhookHandler.js.map +1 -1
- package/dist/rest/UploadTokenManager.d.ts +24 -8
- package/dist/rest/UploadTokenManager.d.ts.map +1 -1
- package/dist/rest/UploadTokenManager.js +53 -21
- package/dist/rest/UploadTokenManager.js.map +1 -1
- package/dist/rest/ViewOperationsHandler.d.ts +20 -0
- package/dist/rest/ViewOperationsHandler.d.ts.map +1 -1
- package/dist/rest/ViewOperationsHandler.js +20 -4
- package/dist/rest/ViewOperationsHandler.js.map +1 -1
- package/dist/rest/mediaRange.d.ts +4 -0
- package/dist/rest/mediaRange.d.ts.map +1 -1
- package/dist/rest/mediaRange.js +10 -2
- package/dist/rest/mediaRange.js.map +1 -1
- package/dist/rest/setupRESTEndpoints.d.ts +4 -0
- package/dist/rest/setupRESTEndpoints.d.ts.map +1 -1
- package/dist/rest/setupRESTEndpoints.js +13 -7
- package/dist/rest/setupRESTEndpoints.js.map +1 -1
- package/dist/serverExtensionReservedRoots.d.ts +2 -0
- package/dist/serverExtensionReservedRoots.d.ts.map +1 -1
- package/dist/serverExtensionReservedRoots.js +5 -1
- package/dist/serverExtensionReservedRoots.js.map +1 -1
- package/dist/services/MJStorageBlobStore.d.ts +41 -0
- package/dist/services/MJStorageBlobStore.d.ts.map +1 -0
- package/dist/services/MJStorageBlobStore.js +160 -0
- package/dist/services/MJStorageBlobStore.js.map +1 -0
- package/dist/services/TaskGraphActionRunner.d.ts +13 -8
- package/dist/services/TaskGraphActionRunner.d.ts.map +1 -1
- package/dist/services/TaskGraphActionRunner.js +16 -15
- package/dist/services/TaskGraphActionRunner.js.map +1 -1
- package/dist/types.d.ts +2 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +7 -3
- package/dist/types.js.map +1 -1
- package/dist/util.d.ts +12 -3
- package/dist/util.d.ts.map +1 -1
- package/dist/util.js +19 -8
- package/dist/util.js.map +1 -1
- package/package.json +92 -92
- package/src/__tests__/FileResolverPeaks.test.ts +13 -13
- package/src/__tests__/ListOperationsResolver.test.ts +17 -17
- package/src/__tests__/MediaStreamHandler.test.ts +28 -28
- package/src/__tests__/NoLog.test.ts +11 -11
- package/src/__tests__/PrimaryKeyAlwaysMapped.test.ts +10 -10
- package/src/__tests__/RESTEndpointHandler.test.ts +14 -14
- package/src/__tests__/RealtimeBridgeResolver.test.ts +4 -2
- package/src/__tests__/RealtimeClientSessionResolver.test.ts +229 -0
- package/src/__tests__/RealtimeSdpBrokerHandler.test.ts +6 -6
- package/src/__tests__/RefreshAdoptsNewColumns.test.ts +8 -8
- package/src/__tests__/RemoteBrowserGoalEngine.test.ts +18 -18
- package/src/__tests__/ResolverBase.isaPromotionCreate.test.ts +412 -0
- package/src/__tests__/RunAIAgentResolver.historyFrom.test.ts +228 -0
- package/src/__tests__/SearchEntitiesResolver.readOnlyFallback.test.ts +78 -0
- package/src/__tests__/TaskGraphActionRunner.durablePayload.test.ts +129 -0
- package/src/__tests__/TaskGraphFrameResolver.test.ts +2 -2
- package/src/__tests__/TransactionGroupResolver.refusals.test.ts +290 -0
- package/src/__tests__/WidgetSessionService.test.ts +8 -4
- package/src/__tests__/adhoc-query-helpers.test.ts +21 -21
- package/src/__tests__/bootAudit.test.ts +11 -11
- package/src/__tests__/boundaryLogPayload.test.ts +6 -6
- package/src/__tests__/databaseAbstraction.test.ts +12 -6
- package/src/__tests__/getDataTokenRedaction.test.ts +7 -7
- package/src/__tests__/initializeProviders.refresh.test.ts +4 -4
- package/src/__tests__/magicLink.test.ts +73 -134
- package/src/__tests__/magicLinkService.consume.test.ts +224 -0
- package/src/__tests__/meetingRecordingRegistration.test.ts +12 -12
- package/src/__tests__/metadataRefreshSignal.test.ts +78 -0
- package/src/__tests__/multiTenancy.security.test.ts +40 -40
- package/src/__tests__/multiTenancy.test.ts +18 -18
- package/src/__tests__/newUsers.test.ts +24 -23
- package/src/__tests__/principals.test.ts +22 -22
- package/src/__tests__/pushStatusResolver.filter.test.ts +8 -8
- package/src/__tests__/secretRedactor.failOpen.test.ts +12 -12
- package/src/__tests__/secretRedactor.test.ts +12 -12
- package/src/__tests__/serverExtensionReservedRoots.test.ts +7 -7
- package/src/__tests__/subscriptionRedaction.test.ts +5 -5
- package/src/__tests__/unifiedAuth.test.ts +15 -9
- package/src/__tests__/visitorIdentity.resolve.test.ts +6 -6
- package/src/__tests__/widget.test.ts +45 -45
- package/src/__tests__/widgetGuestElevation.test.ts +7 -7
- package/src/__tests__/widgetHostIdentity.test.ts +14 -14
- package/src/agentSessions/ReturningVisitorRecap.ts +12 -1
- package/src/agentSessions/SessionManager.ts +2 -2
- package/src/agentSessions/remoteBrowserGoalEngine.ts +30 -5
- package/src/auth/AuthProviderCatalogRouter.ts +6 -1
- package/src/auth/__tests__/backward-compatibility.test.ts +3 -3
- package/src/auth/exampleNewUserSubClass.ts +2 -2
- package/src/auth/index.ts +43 -28
- package/src/auth/initializeProviders.ts +25 -5
- package/src/auth/magicLink/MagicLinkRouter.ts +28 -10
- package/src/auth/magicLink/MagicLinkService.ts +71 -52
- package/src/auth/magicLink/index.ts +7 -7
- package/src/auth/magicLink/magicLinkCore.ts +96 -64
- package/src/auth/magicLink/redeemLanding.ts +14 -4
- package/src/auth/magicLink/types.ts +54 -54
- package/src/auth/newUsers.ts +1 -1
- package/src/auth/principals.ts +11 -2
- package/src/cache.ts +4 -1
- package/src/config.ts +14 -4
- package/src/context.ts +37 -24
- package/src/directives/Public.ts +4 -1
- package/src/directives/RequireSystemUser.ts +4 -1
- package/src/generated/graphql-schemas/__mj.ts +933 -5
- package/src/generic/CacheInvalidationResolver.ts +12 -4
- package/src/generic/FireAndForgetHeartbeat.ts +19 -14
- package/src/generic/PushStatusResolver.ts +21 -7
- package/src/generic/ResolverBase.ts +66 -5
- package/src/generic/RunViewResolver.ts +22 -22
- package/src/generic/__tests__/FireAndForgetHeartbeat.test.ts +7 -7
- package/src/index.ts +34 -7
- package/src/integration/CustomColumnPromoter.ts +6 -1
- package/src/integration/EntityMapLifecycle.ts +20 -2
- package/src/logging/NoLog.ts +16 -2
- package/src/logging/StartupLogger.ts +6 -1
- package/src/logging/bootAudit.ts +26 -16
- package/src/logging/boundaryLogPayload.ts +8 -1
- package/src/logging/secretRedactor.ts +14 -9
- package/src/logging/shortenForLog.ts +8 -3
- package/src/logging/variablesLoggingMiddleware.ts +9 -6
- package/src/metadataRefreshSignal.ts +45 -0
- package/src/middleware/MJTenantFilterMiddleware.ts +4 -4
- package/src/multiTenancy/index.ts +29 -5
- package/src/realtimeWidget/WidgetRouter.ts +15 -10
- package/src/realtimeWidget/WidgetSessionService.ts +76 -76
- package/src/realtimeWidget/host-identity.ts +24 -10
- package/src/realtimeWidget/index.ts +3 -3
- package/src/realtimeWidget/visitorIdentity.ts +39 -8
- package/src/realtimeWidget/widgetCore.ts +75 -10
- package/src/realtimeWidget/widgetGuestElevation.ts +25 -12
- package/src/resolvers/AdhocQueryResolver.ts +3 -3
- package/src/resolvers/AgentSessionResolver.ts +3 -3
- package/src/resolvers/ColorResolver.ts +6 -6
- package/src/resolvers/ComponentRegistryResolver.ts +36 -36
- package/src/resolvers/EntityCommunicationsResolver.ts +4 -4
- package/src/resolvers/ExecuteRemoteOperationResolver.ts +9 -9
- package/src/resolvers/FetchEntityVectorsResolver.ts +2 -2
- package/src/resolvers/FileResolver.ts +42 -42
- package/src/resolvers/GeoResolver.ts +4 -4
- package/src/resolvers/GetDataResolver.ts +50 -15
- package/src/resolvers/IntegrationDiscoveryResolver.ts +2 -2
- package/src/resolvers/ListOperationsResolver.ts +78 -28
- package/src/resolvers/MCPResolver.ts +30 -15
- package/src/resolvers/QueryResolver.ts +13 -13
- package/src/resolvers/QuerySystemUserResolver.ts +2 -1
- package/src/resolvers/RealtimeBridgeResolver.ts +3 -3
- package/src/resolvers/RealtimeClientSessionResolver.ts +126 -13
- package/src/resolvers/RemoteBrowserActionResolver.ts +5 -5
- package/src/resolvers/RunAIAgentResolver.ts +118 -76
- package/src/resolvers/RunAIPromptResolver.ts +22 -22
- package/src/resolvers/RunTemplateResolver.ts +4 -4
- package/src/resolvers/RunTestResolver.ts +25 -25
- package/src/resolvers/SearchEntitiesResolver.ts +3 -1
- package/src/resolvers/SearchKnowledgeResolver.ts +4 -0
- package/src/resolvers/SearchKnowledgeSystemUserResolver.ts +1 -0
- package/src/resolvers/SqlLoggingConfigResolver.ts +56 -56
- package/src/resolvers/TaskGraphFrameResolver.ts +37 -28
- package/src/resolvers/TelemetryResolver.ts +79 -79
- package/src/resolvers/TransactionGroupResolver.ts +57 -4
- package/src/resolvers/__tests__/GetDataResolver.tokens.test.ts +40 -38
- package/src/resolvers/__tests__/artifact-routing.test.ts +23 -23
- package/src/resolvers/adhoc-query-helpers.ts +19 -2
- package/src/resolvers/artifact-routing.ts +19 -19
- package/src/resolvers/meetingRecordingRegistration.ts +44 -10
- package/src/resolvers/peaksSidecar.ts +12 -2
- package/src/rest/EntityCRUDHandler.ts +24 -4
- package/src/rest/MediaStreamHandler.ts +11 -6
- package/src/rest/OAuthCallbackHandler.ts +25 -7
- package/src/rest/RESTEndpointHandler.ts +20 -15
- package/src/rest/RealtimeSdpBrokerHandler.ts +14 -4
- package/src/rest/SignatureWebhookHandler.ts +6 -1
- package/src/rest/UploadTokenManager.ts +66 -30
- package/src/rest/ViewOperationsHandler.ts +24 -4
- package/src/rest/mediaRange.ts +16 -6
- package/src/rest/setupRESTEndpoints.ts +24 -12
- package/src/serverExtensionReservedRoots.ts +6 -1
- package/src/services/MJStorageBlobStore.ts +209 -0
- package/src/services/TaskGraphActionRunner.ts +16 -15
- package/src/types.ts +21 -16
- package/src/util.ts +22 -9
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* IS-A PROMOTION over the wire — the server half.
|
|
3
|
+
*
|
|
4
|
+
* Promotion = an EXISTING parent row gains a subtype ("this Animal is now also a Dog"). Over
|
|
5
|
+
* GraphQL it arrives as the child's CREATE mutation carrying the EXISTING parent's primary key,
|
|
6
|
+
* because in IS-A the shared key IS the relationship — the child has no key of its own to mint.
|
|
7
|
+
*
|
|
8
|
+
* The defect: `ResolverBase.CreateRecord` did `GetEntityObject` + `NewRecord()` + `SetMany(input)`.
|
|
9
|
+
* `NewRecord()` recurses into the parent chain and flags the PARENT as new too; `SetMany` then only
|
|
10
|
+
* overwrites the key. The parent therefore saved as a CREATE — a second copy of a row that already
|
|
11
|
+
* exists — instead of the child being attached to the existing row.
|
|
12
|
+
*
|
|
13
|
+
* These tests drive the REAL `CreateRecord` with REAL `BaseEntity` / `EntityInfo` objects over a
|
|
14
|
+
* fake provider that (a) constructs entities exactly as `ProviderBase.GetEntityObject` does
|
|
15
|
+
* (construct → Config → InitializeParentEntity → NewRecord), (b) holds one existing Animal row for
|
|
16
|
+
* `Load`, and (c) records every `Save` it receives — entity, create-vs-update, key — which is the
|
|
17
|
+
* observable the bug corrupts.
|
|
18
|
+
*/
|
|
19
|
+
// ResolverBase transitively pulls in type-graphql decorators, which need the
|
|
20
|
+
// Reflect.metadata polyfill at import time.
|
|
21
|
+
import 'reflect-metadata';
|
|
22
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
23
|
+
import {
|
|
24
|
+
BaseEntity,
|
|
25
|
+
CompositeKey,
|
|
26
|
+
DatabaseProviderBase,
|
|
27
|
+
EntityInfo,
|
|
28
|
+
EntitySaveOptions,
|
|
29
|
+
FieldPermissionAccess,
|
|
30
|
+
IMetadataProvider,
|
|
31
|
+
Metadata,
|
|
32
|
+
UserInfo,
|
|
33
|
+
} from '@memberjunction/core';
|
|
34
|
+
import type { PubSubEngine } from 'type-graphql';
|
|
35
|
+
import { ResolverBase } from '../generic/ResolverBase.js';
|
|
36
|
+
import type { UserPayload } from '../types.js';
|
|
37
|
+
|
|
38
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
39
|
+
// Fixtures — a real two-level IS-A pair: Animals (root) ← Dogs (child), plus a standalone entity
|
|
40
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
41
|
+
|
|
42
|
+
const ROLE_ID = 'D1D2D3D4-0000-4000-8000-00000000R0LE';
|
|
43
|
+
const USER_ID = 'D1D2D3D4-0000-4000-8000-00000000USER';
|
|
44
|
+
const ANIMAL_ENTITY_ID = 'D1D2D3D4-0000-4000-8000-000000000001';
|
|
45
|
+
const DOG_ENTITY_ID = 'D1D2D3D4-0000-4000-8000-000000000002';
|
|
46
|
+
const TOY_ENTITY_ID = 'D1D2D3D4-0000-4000-8000-000000000003';
|
|
47
|
+
/** The Animal row that already exists in the database. */
|
|
48
|
+
const EXISTING_ANIMAL_ID = 'E1E2E3E4-0000-4000-8000-00000000AAAA';
|
|
49
|
+
/** A well-formed key that matches NO row. */
|
|
50
|
+
const MISSING_ANIMAL_ID = 'E1E2E3E4-0000-4000-8000-00000000FFFF';
|
|
51
|
+
|
|
52
|
+
interface FieldSpec {
|
|
53
|
+
EntityID: string;
|
|
54
|
+
Name: string;
|
|
55
|
+
Type: string;
|
|
56
|
+
Sequence: number;
|
|
57
|
+
IsPrimaryKey?: boolean;
|
|
58
|
+
AllowsNull?: boolean;
|
|
59
|
+
AllowUpdateAPI?: boolean;
|
|
60
|
+
IsVirtual?: boolean;
|
|
61
|
+
/** Field-level security rows (only read on an entity with EnableFieldLevelSecurity). */
|
|
62
|
+
EntityFieldPermissions?: Record<string, unknown>[];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function field(spec: FieldSpec): Record<string, unknown> {
|
|
66
|
+
return {
|
|
67
|
+
ID: `EF-${spec.EntityID}-${spec.Sequence}`,
|
|
68
|
+
EntityID: spec.EntityID,
|
|
69
|
+
Name: spec.Name,
|
|
70
|
+
Type: spec.Type,
|
|
71
|
+
Length: null,
|
|
72
|
+
IsPrimaryKey: spec.IsPrimaryKey ?? false,
|
|
73
|
+
IsSoftPrimaryKey: false,
|
|
74
|
+
IsSoftForeignKey: false,
|
|
75
|
+
AllowsNull: spec.AllowsNull ?? true,
|
|
76
|
+
AllowUpdateAPI: spec.AllowUpdateAPI ?? false,
|
|
77
|
+
DefaultValue: null,
|
|
78
|
+
AutoIncrement: false,
|
|
79
|
+
IsVirtual: spec.IsVirtual ?? false,
|
|
80
|
+
IsNameField: false,
|
|
81
|
+
ValueListType: 'None',
|
|
82
|
+
Sequence: spec.Sequence,
|
|
83
|
+
Status: 'Active',
|
|
84
|
+
EntityFieldValues: [],
|
|
85
|
+
EntityFieldPermissions: spec.EntityFieldPermissions ?? [],
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* One field-level permission row for the test role. On an FLS-enabled entity a field with NO rows
|
|
91
|
+
* is denied, so every non-key field of such an entity gets an explicit row.
|
|
92
|
+
*/
|
|
93
|
+
function fieldPermission(fieldName: string, access: { read?: string; update?: string; create?: string }): Record<string, unknown> {
|
|
94
|
+
const sequence: Record<string, number> = { Name: 2, MicrochipNumber: 3 }; // matches field()'s `EF-<entity>-<sequence>` ids
|
|
95
|
+
return {
|
|
96
|
+
ID: `fperm-${fieldName}`,
|
|
97
|
+
EntityFieldID: `EF-${ANIMAL_ENTITY_ID}-${sequence[fieldName]}`,
|
|
98
|
+
RoleID: ROLE_ID,
|
|
99
|
+
ReadAccess: access.read ?? FieldPermissionAccess.Allow,
|
|
100
|
+
UpdateAccess: access.update ?? FieldPermissionAccess.Allow,
|
|
101
|
+
CreateAccess: access.create ?? FieldPermissionAccess.Allow,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function permission(entityID: string): Record<string, unknown> {
|
|
106
|
+
return { ID: `perm-${entityID}`, EntityID: entityID, RoleID: ROLE_ID, CanCreate: true, CanRead: true, CanUpdate: true, CanDelete: true };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function entityData(overrides: {
|
|
110
|
+
ID: string; Name: string; BaseTable: string; ParentID: string | null; EntityFields: Record<string, unknown>[];
|
|
111
|
+
EnableFieldLevelSecurity?: boolean; AllowMultipleSubtypes?: boolean;
|
|
112
|
+
}): Record<string, unknown> {
|
|
113
|
+
return {
|
|
114
|
+
ID: overrides.ID,
|
|
115
|
+
Name: overrides.Name,
|
|
116
|
+
BaseTable: overrides.BaseTable,
|
|
117
|
+
BaseView: `vw${overrides.BaseTable}s`,
|
|
118
|
+
SchemaName: 'Shelter',
|
|
119
|
+
VirtualEntity: false,
|
|
120
|
+
AllowCreateAPI: true,
|
|
121
|
+
AllowUpdateAPI: true,
|
|
122
|
+
AllowDeleteAPI: true,
|
|
123
|
+
IncludeInAPI: true,
|
|
124
|
+
AllowMultipleSubtypes: overrides.AllowMultipleSubtypes ?? false,
|
|
125
|
+
EnableFieldLevelSecurity: overrides.EnableFieldLevelSecurity ?? false,
|
|
126
|
+
ParentID: overrides.ParentID,
|
|
127
|
+
Status: 'Active',
|
|
128
|
+
EntityFields: overrides.EntityFields,
|
|
129
|
+
EntityPermissions: [permission(overrides.ID)],
|
|
130
|
+
EntityRelationships: [],
|
|
131
|
+
EntitySettings: [],
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Fixture options. `nameLockedByFls`: Animals runs with field-level security ON and the test role
|
|
137
|
+
* may READ but neither UPDATE nor CREATE `Name` (every other field stays open). `overlapping`:
|
|
138
|
+
* Animals allows multiple subtypes.
|
|
139
|
+
*/
|
|
140
|
+
function buildEntities(options: { nameLockedByFls?: boolean; overlapping?: boolean } = {}): EntityInfo[] {
|
|
141
|
+
const fls = options.nameLockedByFls === true;
|
|
142
|
+
const animals = entityData({
|
|
143
|
+
ID: ANIMAL_ENTITY_ID, Name: 'Animals', BaseTable: 'Animal', ParentID: null,
|
|
144
|
+
EnableFieldLevelSecurity: fls,
|
|
145
|
+
AllowMultipleSubtypes: options.overlapping === true,
|
|
146
|
+
EntityFields: [
|
|
147
|
+
field({ EntityID: ANIMAL_ENTITY_ID, Name: 'ID', Type: 'uniqueidentifier', IsPrimaryKey: true, AllowsNull: false, Sequence: 1 }),
|
|
148
|
+
field({ EntityID: ANIMAL_ENTITY_ID, Name: 'Name', Type: 'nvarchar', AllowsNull: false, AllowUpdateAPI: true, Sequence: 2,
|
|
149
|
+
EntityFieldPermissions: fls ? [fieldPermission('Name', { update: FieldPermissionAccess.Deny, create: FieldPermissionAccess.Deny })] : [] }),
|
|
150
|
+
field({ EntityID: ANIMAL_ENTITY_ID, Name: 'MicrochipNumber', Type: 'nvarchar', AllowsNull: true, AllowUpdateAPI: true, Sequence: 3,
|
|
151
|
+
EntityFieldPermissions: fls ? [fieldPermission('MicrochipNumber', {})] : [] }),
|
|
152
|
+
],
|
|
153
|
+
});
|
|
154
|
+
// Mirrors what CodeGen emits for an IS-A child: its OWN `ID` PK (shared with the parent), its
|
|
155
|
+
// own columns, and the parent's data fields as virtual, updatable mirrors.
|
|
156
|
+
const dogs = entityData({
|
|
157
|
+
ID: DOG_ENTITY_ID, Name: 'Dogs', BaseTable: 'Dog', ParentID: ANIMAL_ENTITY_ID,
|
|
158
|
+
EntityFields: [
|
|
159
|
+
field({ EntityID: DOG_ENTITY_ID, Name: 'ID', Type: 'uniqueidentifier', IsPrimaryKey: true, AllowsNull: false, Sequence: 1 }),
|
|
160
|
+
field({ EntityID: DOG_ENTITY_ID, Name: 'IsHouseTrained', Type: 'bit', AllowsNull: true, AllowUpdateAPI: true, Sequence: 2 }),
|
|
161
|
+
field({ EntityID: DOG_ENTITY_ID, Name: 'Name', Type: 'nvarchar', AllowsNull: false, AllowUpdateAPI: true, IsVirtual: true, Sequence: 3 }),
|
|
162
|
+
field({ EntityID: DOG_ENTITY_ID, Name: 'MicrochipNumber', Type: 'nvarchar', AllowsNull: true, AllowUpdateAPI: true, IsVirtual: true, Sequence: 4 }),
|
|
163
|
+
],
|
|
164
|
+
});
|
|
165
|
+
const toys = entityData({
|
|
166
|
+
ID: TOY_ENTITY_ID, Name: 'Toys', BaseTable: 'Toy', ParentID: null,
|
|
167
|
+
EntityFields: [
|
|
168
|
+
field({ EntityID: TOY_ENTITY_ID, Name: 'ID', Type: 'uniqueidentifier', IsPrimaryKey: true, AllowsNull: false, Sequence: 1 }),
|
|
169
|
+
field({ EntityID: TOY_ENTITY_ID, Name: 'Name', Type: 'nvarchar', AllowsNull: false, AllowUpdateAPI: true, Sequence: 2 }),
|
|
170
|
+
],
|
|
171
|
+
});
|
|
172
|
+
return [animals, dogs, toys].map(d => new EntityInfo(d));
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function buildUser(): UserInfo {
|
|
176
|
+
return new UserInfo(null, {
|
|
177
|
+
ID: USER_ID, Name: 'Test User', Email: 'test.user@example.com',
|
|
178
|
+
FirstName: 'Test', LastName: 'User', IsActive: true,
|
|
179
|
+
UserRoles: [{ UserID: USER_ID, RoleID: ROLE_ID, RoleName: 'Admin' }],
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
184
|
+
// Fake provider — real entity construction, an in-memory row store, a Save log
|
|
185
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
186
|
+
|
|
187
|
+
/** Concrete BaseEntity (mirrors a generated entity class). */
|
|
188
|
+
class TestEntity extends BaseEntity {}
|
|
189
|
+
|
|
190
|
+
interface SaveRecord {
|
|
191
|
+
Entity: string;
|
|
192
|
+
Type: 'create' | 'update';
|
|
193
|
+
ID: unknown;
|
|
194
|
+
Values: Record<string, unknown>;
|
|
195
|
+
/** Fields field-level security told the provider to OMIT from an INSERT (CreateSuppressed). */
|
|
196
|
+
SuppressedOnCreate: string[];
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
class FakeIsaProvider {
|
|
200
|
+
public readonly Entities: EntityInfo[];
|
|
201
|
+
public readonly CurrentUser: UserInfo;
|
|
202
|
+
/** Rows the "database" holds, keyed by entity name then primary key. */
|
|
203
|
+
public rows: Record<string, Record<string, Record<string, unknown>>> = {};
|
|
204
|
+
/** Every Save the ORM handed us, in order — the observable the defect corrupts. */
|
|
205
|
+
public saves: SaveRecord[] = [];
|
|
206
|
+
/** Every Load the ORM asked for, in order. */
|
|
207
|
+
public loads: { Entity: string; ID: unknown }[] = [];
|
|
208
|
+
|
|
209
|
+
constructor(entities: EntityInfo[], user: UserInfo) {
|
|
210
|
+
this.Entities = entities;
|
|
211
|
+
this.CurrentUser = user;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
public EntityByName(name: string): EntityInfo | undefined {
|
|
215
|
+
return this.Entities.find(e => e.Name.trim().toLowerCase() === name.trim().toLowerCase());
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
public EntityByID(id: string): EntityInfo | undefined {
|
|
219
|
+
return this.Entities.find(e => e.ID === id);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/** The same construction sequence as ProviderBase.GetEntityObject (minus the class factory). */
|
|
223
|
+
public async GetEntityObject(entityName: string, contextUser?: UserInfo): Promise<BaseEntity> {
|
|
224
|
+
const info = this.EntityByName(entityName);
|
|
225
|
+
if (!info) throw new Error(`Entity ${entityName} not found`);
|
|
226
|
+
const entity = new TestEntity(info, this as unknown as ConstructorParameters<typeof TestEntity>[1]);
|
|
227
|
+
await entity.Config(contextUser ?? this.CurrentUser);
|
|
228
|
+
await entity.InitializeParentEntity();
|
|
229
|
+
entity.NewRecord();
|
|
230
|
+
return entity;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
public async Load(entity: BaseEntity, key: CompositeKey): Promise<Record<string, unknown> | null> {
|
|
234
|
+
const id = key.GetValueByFieldName('ID') as string;
|
|
235
|
+
this.loads.push({ Entity: entity.EntityInfo.Name, ID: id });
|
|
236
|
+
const row = this.rows[entity.EntityInfo.Name]?.[id];
|
|
237
|
+
return row ? { ...row } : null;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
public async Save(entity: BaseEntity, _user: UserInfo, _options: EntitySaveOptions): Promise<Record<string, unknown>> {
|
|
241
|
+
this.saves.push({
|
|
242
|
+
Entity: entity.EntityInfo.Name,
|
|
243
|
+
Type: entity.IsSaved ? 'update' : 'create',
|
|
244
|
+
ID: entity.Get('ID'),
|
|
245
|
+
Values: entity.GetAll(),
|
|
246
|
+
SuppressedOnCreate: entity.Fields.filter(f => f.CreateSuppressed).map(f => f.Name),
|
|
247
|
+
});
|
|
248
|
+
return entity.GetAll();
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
public SetCachedRecordName(): void { /* cache is irrelevant here */ }
|
|
252
|
+
public GetCachedRecordName(): string | null { return null; }
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/** Exposes the REAL protected CreateRecord; pub/sub wiring is out of scope for a unit test. */
|
|
256
|
+
class CreateProbe extends ResolverBase {
|
|
257
|
+
public Create(entityName: string, input: Record<string, unknown>, provider: FakeIsaProvider, payload: UserPayload): Promise<Record<string, unknown> | null> {
|
|
258
|
+
return this.CreateRecord(
|
|
259
|
+
entityName,
|
|
260
|
+
input,
|
|
261
|
+
provider as unknown as DatabaseProviderBase,
|
|
262
|
+
payload,
|
|
263
|
+
undefined as unknown as PubSubEngine
|
|
264
|
+
) as Promise<Record<string, unknown> | null>;
|
|
265
|
+
}
|
|
266
|
+
protected override ListenForEntityMessages(): void { /* no pub/sub in a unit test */ }
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
describe('ResolverBase.CreateRecord — IS-A promotion attaches the new child to the EXISTING parent', () => {
|
|
270
|
+
let provider: FakeIsaProvider;
|
|
271
|
+
let payload: UserPayload;
|
|
272
|
+
let previousGlobalProvider: IMetadataProvider;
|
|
273
|
+
|
|
274
|
+
/** (Re)build the fake provider over a fixture variant and publish it as the global provider. */
|
|
275
|
+
function useFixture(options: { nameLockedByFls?: boolean; overlapping?: boolean } = {}): void {
|
|
276
|
+
const user = buildUser();
|
|
277
|
+
provider = new FakeIsaProvider(buildEntities(options), user);
|
|
278
|
+
provider.rows = {
|
|
279
|
+
Animals: {
|
|
280
|
+
[EXISTING_ANIMAL_ID]: { ID: EXISTING_ANIMAL_ID, Name: 'Biscuit', MicrochipNumber: '985112000000001' },
|
|
281
|
+
},
|
|
282
|
+
Dogs: {},
|
|
283
|
+
};
|
|
284
|
+
Metadata.Provider = provider as unknown as IMetadataProvider;
|
|
285
|
+
payload = { email: user.Email, userRecord: user, sessionId: 'test-session' };
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
beforeEach(() => {
|
|
289
|
+
// EntityInfo resolves ParentEntityInfo / ChildEntities through the GLOBAL provider (a
|
|
290
|
+
// stateless info-class proxy), and MapFieldNamesToCodeNames falls back to it too.
|
|
291
|
+
previousGlobalProvider = Metadata.Provider;
|
|
292
|
+
useFixture();
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
afterEach(() => {
|
|
296
|
+
Metadata.Provider = previousGlobalProvider;
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
it('promotion: an existing parent key on a child create UPDATES nothing on the parent and INSERTs only the child', async () => {
|
|
300
|
+
const result = await new CreateProbe().Create('Dogs', { ID: EXISTING_ANIMAL_ID, IsHouseTrained: true }, provider, payload);
|
|
301
|
+
|
|
302
|
+
// The parent row was looked up by the incoming key...
|
|
303
|
+
expect(provider.loads).toEqual([{ Entity: 'Animals', ID: EXISTING_ANIMAL_ID }]);
|
|
304
|
+
// ...and NOT re-created. Before the fix this list began with
|
|
305
|
+
// { Entity: 'Animals', Type: 'create' } — a duplicate copy of Biscuit.
|
|
306
|
+
expect(provider.saves.filter(s => s.Entity === 'Animals')).toEqual([]);
|
|
307
|
+
// The child is created on the SHARED key.
|
|
308
|
+
expect(provider.saves).toEqual([
|
|
309
|
+
expect.objectContaining({ Entity: 'Dogs', Type: 'create', ID: EXISTING_ANIMAL_ID }),
|
|
310
|
+
]);
|
|
311
|
+
expect(provider.saves[0].Values['IsHouseTrained']).toBe(true);
|
|
312
|
+
expect(result?.['ID']).toBe(EXISTING_ANIMAL_ID);
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
it('promotion: parent fields sent alongside the key are applied as an UPDATE of the existing parent', async () => {
|
|
316
|
+
await new CreateProbe().Create('Dogs', { ID: EXISTING_ANIMAL_ID, Name: 'Biscuit II', IsHouseTrained: true }, provider, payload);
|
|
317
|
+
|
|
318
|
+
expect(provider.saves).toEqual([
|
|
319
|
+
expect.objectContaining({ Entity: 'Animals', Type: 'update', ID: EXISTING_ANIMAL_ID }),
|
|
320
|
+
expect.objectContaining({ Entity: 'Dogs', Type: 'create', ID: EXISTING_ANIMAL_ID }),
|
|
321
|
+
]);
|
|
322
|
+
expect(provider.saves[0].Values['Name']).toBe('Biscuit II');
|
|
323
|
+
// The loaded value the input did NOT touch survives — the parent was loaded, not blanked.
|
|
324
|
+
expect(provider.saves[0].Values['MicrochipNumber']).toBe('985112000000001');
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
it('whole-chain create: no key in the input creates parent AND child on one fresh key, without a lookup', async () => {
|
|
328
|
+
await new CreateProbe().Create('Dogs', { Name: 'Pretzel', IsHouseTrained: false }, provider, payload);
|
|
329
|
+
|
|
330
|
+
expect(provider.loads).toEqual([]);
|
|
331
|
+
expect(provider.saves.map(s => [s.Entity, s.Type])).toEqual([['Animals', 'create'], ['Dogs', 'create']]);
|
|
332
|
+
const sharedKey = provider.saves[0].ID;
|
|
333
|
+
expect(typeof sharedKey).toBe('string');
|
|
334
|
+
expect(sharedKey).not.toBe(EXISTING_ANIMAL_ID);
|
|
335
|
+
expect(provider.saves[1].ID).toBe(sharedKey);
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
it('whole-chain create with a caller-supplied key that matches no row still creates the full chain on that key', async () => {
|
|
339
|
+
await new CreateProbe().Create('Dogs', { ID: MISSING_ANIMAL_ID, Name: 'Ghost', IsHouseTrained: false }, provider, payload);
|
|
340
|
+
|
|
341
|
+
expect(provider.loads).toEqual([{ Entity: 'Animals', ID: MISSING_ANIMAL_ID }]);
|
|
342
|
+
expect(provider.saves).toEqual([
|
|
343
|
+
expect.objectContaining({ Entity: 'Animals', Type: 'create', ID: MISSING_ANIMAL_ID }),
|
|
344
|
+
expect.objectContaining({ Entity: 'Dogs', Type: 'create', ID: MISSING_ANIMAL_ID }),
|
|
345
|
+
]);
|
|
346
|
+
expect(provider.saves[0].Values['Name']).toBe('Ghost');
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
it('non-IS-A create is untouched: no lookup, one create', async () => {
|
|
350
|
+
await new CreateProbe().Create('Toys', { Name: 'Squeaky Bone' }, provider, payload);
|
|
351
|
+
|
|
352
|
+
expect(provider.loads).toEqual([]);
|
|
353
|
+
expect(provider.saves.map(s => [s.Entity, s.Type])).toEqual([['Toys', 'create']]);
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
it('overlapping subtypes (AllowMultipleSubtypes): promotion still attaches to the existing parent and creates only the child', async () => {
|
|
357
|
+
// Overlapping hierarchies exist precisely to be added to incrementally, so promotion is the
|
|
358
|
+
// natural operation there. The disjoint guard does not run; the attach path is the same.
|
|
359
|
+
useFixture({ overlapping: true });
|
|
360
|
+
|
|
361
|
+
const result = await new CreateProbe().Create('Dogs', { ID: EXISTING_ANIMAL_ID, IsHouseTrained: true }, provider, payload);
|
|
362
|
+
|
|
363
|
+
expect(provider.loads).toEqual([{ Entity: 'Animals', ID: EXISTING_ANIMAL_ID }]);
|
|
364
|
+
expect(provider.saves.filter(s => s.Entity === 'Animals')).toEqual([]);
|
|
365
|
+
expect(provider.saves).toEqual([
|
|
366
|
+
expect.objectContaining({ Entity: 'Dogs', Type: 'create', ID: EXISTING_ANIMAL_ID }),
|
|
367
|
+
]);
|
|
368
|
+
expect(result?.['ID']).toBe(EXISTING_ANIMAL_ID);
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
describe('field-level security: the same parent field on a child create meets two different verbs', () => {
|
|
372
|
+
// With FLS on, parent fields sent on a child-create input are enforced by whichever verb the
|
|
373
|
+
// PARENT's save runs — and that is decided by whether the parent row already exists:
|
|
374
|
+
// promotion → the parent is LOADED, saves as an UPDATE → CheckFieldLevelUpdatePermissions REJECTS;
|
|
375
|
+
// whole-chain create → the parent is NEW, saves as a CREATE → ApplyFieldLevelCreateSuppression OMITS.
|
|
376
|
+
// That fork is deliberate (a promotion really is an update of the parent) and these pin it.
|
|
377
|
+
|
|
378
|
+
it('promotion: a parent field the user cannot UPDATE rejects the whole save, before any SQL', async () => {
|
|
379
|
+
useFixture({ nameLockedByFls: true });
|
|
380
|
+
|
|
381
|
+
await expect(
|
|
382
|
+
new CreateProbe().Create('Dogs', { ID: EXISTING_ANIMAL_ID, Name: 'Biscuit II', IsHouseTrained: true }, provider, payload)
|
|
383
|
+
).rejects.toThrow(/permission to update field 'Name'/);
|
|
384
|
+
|
|
385
|
+
expect(provider.loads).toEqual([{ Entity: 'Animals', ID: EXISTING_ANIMAL_ID }]);
|
|
386
|
+
expect(provider.saves).toEqual([]);
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
it('promotion: parent fields the user CAN update still go through as an UPDATE', async () => {
|
|
390
|
+
useFixture({ nameLockedByFls: true });
|
|
391
|
+
|
|
392
|
+
await new CreateProbe().Create('Dogs', { ID: EXISTING_ANIMAL_ID, MicrochipNumber: '985112000000002', IsHouseTrained: true }, provider, payload);
|
|
393
|
+
|
|
394
|
+
expect(provider.saves).toEqual([
|
|
395
|
+
expect.objectContaining({ Entity: 'Animals', Type: 'update', ID: EXISTING_ANIMAL_ID }),
|
|
396
|
+
expect.objectContaining({ Entity: 'Dogs', Type: 'create', ID: EXISTING_ANIMAL_ID }),
|
|
397
|
+
]);
|
|
398
|
+
expect(provider.saves[0].Values['MicrochipNumber']).toBe('985112000000002');
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
it('whole-chain create: the same parent field the user cannot CREATE is silently OMITTED, and the create proceeds', async () => {
|
|
402
|
+
useFixture({ nameLockedByFls: true });
|
|
403
|
+
|
|
404
|
+
await new CreateProbe().Create('Dogs', { Name: 'Pretzel', MicrochipNumber: '985112000000003', IsHouseTrained: false }, provider, payload);
|
|
405
|
+
|
|
406
|
+
expect(provider.loads).toEqual([]);
|
|
407
|
+
expect(provider.saves.map(s => [s.Entity, s.Type])).toEqual([['Animals', 'create'], ['Dogs', 'create']]);
|
|
408
|
+
expect(provider.saves[0].SuppressedOnCreate).toEqual(['Name']);
|
|
409
|
+
expect(provider.saves[0].Values['MicrochipNumber']).toBe('985112000000003');
|
|
410
|
+
});
|
|
411
|
+
});
|
|
412
|
+
});
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview The agent's history floor on `RunAIAgentFromConversationDetail`.
|
|
3
|
+
*
|
|
4
|
+
* `agentHistoryFrom` is the one server change behind the conversations host rules: an optional
|
|
5
|
+
* ISO-8601 timestamp, the first moment of the conversation a run may read. These tests pin that
|
|
6
|
+
* the mutation parses it before anything loads, fails the request on an unreadable value instead
|
|
7
|
+
* of dropping it, loads history from it (in the query AND in the window assembly), and hands it to
|
|
8
|
+
* the agent run as `ConversationHistoryFrom` on both the synchronous and fire-and-forget paths —
|
|
9
|
+
* and that leaving it out changes nothing.
|
|
10
|
+
*/
|
|
11
|
+
import 'reflect-metadata'; // must precede any type-graphql decorator evaluation
|
|
12
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
13
|
+
import type { PubSubEngine } from 'type-graphql';
|
|
14
|
+
|
|
15
|
+
// type-graphql's real decorators need emitDecoratorMetadata, which vitest's esbuild transform
|
|
16
|
+
// does not emit; the schema is irrelevant here, so every decorator is a no-op.
|
|
17
|
+
vi.mock('type-graphql', () => {
|
|
18
|
+
const decoratorFactory = (..._args: unknown[]) => (..._decorated: unknown[]) => undefined;
|
|
19
|
+
const exportNames = [
|
|
20
|
+
'Resolver', 'Query', 'Mutation', 'Subscription', 'Arg', 'Args', 'ArgsType', 'Ctx', 'Root',
|
|
21
|
+
'Info', 'Field', 'FieldResolver', 'ObjectType', 'InputType', 'InterfaceType', 'Authorized',
|
|
22
|
+
'UseMiddleware', 'Extensions', 'Directive', 'ID', 'Int', 'Float', 'GraphQLISODateTime',
|
|
23
|
+
'GraphQLTimestamp', 'registerEnumType', 'createMethodDecorator', 'createParamDecorator',
|
|
24
|
+
'buildSchema', 'buildSchemaSync', 'PubSub',
|
|
25
|
+
];
|
|
26
|
+
return Object.fromEntries(exportNames.map((name) => [name, decoratorFactory]));
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
const hoisted = vi.hoisted(() => ({
|
|
30
|
+
runAgentInConversation: vi.fn(),
|
|
31
|
+
}));
|
|
32
|
+
|
|
33
|
+
vi.mock('@memberjunction/ai-agents', () => ({
|
|
34
|
+
AgentRunner: class {
|
|
35
|
+
RunAgentInConversation(...args: unknown[]) {
|
|
36
|
+
return hoisted.runAgentInConversation(...args);
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
ArtifactToolManager: class {},
|
|
40
|
+
}));
|
|
41
|
+
|
|
42
|
+
vi.mock('../realtimeWidget/widgetGuestElevation.js', () => ({
|
|
43
|
+
ResolveWidgetGuestRunContext: vi.fn().mockResolvedValue(null),
|
|
44
|
+
ElevateUserPayload: vi.fn(),
|
|
45
|
+
}));
|
|
46
|
+
|
|
47
|
+
import { ConversationEngine } from '@memberjunction/core-entities';
|
|
48
|
+
import { RunAIAgentResolver } from '../resolvers/RunAIAgentResolver.js';
|
|
49
|
+
import type { AppContext, UserPayload } from '../types.js';
|
|
50
|
+
|
|
51
|
+
const FLOOR_ISO = '2026-09-01T12:00:00.000Z';
|
|
52
|
+
const USER = { ID: 'user-1', Email: 'user@example.com' };
|
|
53
|
+
|
|
54
|
+
type Fn = ReturnType<typeof vi.fn>;
|
|
55
|
+
|
|
56
|
+
/** The private members this test reaches, through an explicit shape (no `any`). */
|
|
57
|
+
interface ResolverSeams {
|
|
58
|
+
CheckAPIKeyScopeAuthorization: Fn;
|
|
59
|
+
GetUserFromPayload: Fn;
|
|
60
|
+
loadConversationHistoryWithAttachments: Fn;
|
|
61
|
+
executeAIAgent: Fn;
|
|
62
|
+
executeAgentInBackground: Fn;
|
|
63
|
+
loadInputArtifactsBatch: Fn;
|
|
64
|
+
validateAgent: Fn;
|
|
65
|
+
persistInFlightAgentFailure: Fn;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function makeResolver(): { resolver: RunAIAgentResolver; seams: ResolverSeams } {
|
|
69
|
+
const resolver = new RunAIAgentResolver();
|
|
70
|
+
const seams = resolver as unknown as ResolverSeams;
|
|
71
|
+
seams.CheckAPIKeyScopeAuthorization = vi.fn().mockResolvedValue(undefined);
|
|
72
|
+
seams.GetUserFromPayload = vi.fn().mockReturnValue(USER);
|
|
73
|
+
return { resolver, seams };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function makeContext(): AppContext {
|
|
77
|
+
const provider = {
|
|
78
|
+
GetEntityObject: vi.fn().mockResolvedValue({
|
|
79
|
+
ConversationID: 'conv-1',
|
|
80
|
+
Load: vi.fn().mockResolvedValue(true),
|
|
81
|
+
}),
|
|
82
|
+
};
|
|
83
|
+
return {
|
|
84
|
+
userPayload: { sessionId: 'session-1', userRecord: USER } as unknown as UserPayload,
|
|
85
|
+
providers: [{ type: 'Read-Write', provider }],
|
|
86
|
+
dataSource: {},
|
|
87
|
+
} as unknown as AppContext;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const pubSub = { publish: vi.fn() } as unknown as PubSubEngine;
|
|
91
|
+
|
|
92
|
+
async function runMutation(
|
|
93
|
+
resolver: RunAIAgentResolver,
|
|
94
|
+
options: { agentHistoryFrom?: string; fireAndForget?: boolean }
|
|
95
|
+
) {
|
|
96
|
+
return resolver.RunAIAgentFromConversationDetail(
|
|
97
|
+
'detail-1', 'agent-1', makeContext(), 'session-1', pubSub,
|
|
98
|
+
undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined,
|
|
99
|
+
undefined, undefined, options.fireAndForget, undefined, undefined,
|
|
100
|
+
options.agentHistoryFrom
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
afterEach(() => {
|
|
105
|
+
vi.restoreAllMocks();
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
describe('RunAIAgentFromConversationDetail — agentHistoryFrom', () => {
|
|
109
|
+
let resolver: RunAIAgentResolver;
|
|
110
|
+
let seams: ResolverSeams;
|
|
111
|
+
|
|
112
|
+
beforeEach(() => {
|
|
113
|
+
({ resolver, seams } = makeResolver());
|
|
114
|
+
seams.loadConversationHistoryWithAttachments = vi.fn().mockResolvedValue([]);
|
|
115
|
+
seams.executeAIAgent = vi.fn().mockResolvedValue({ success: true, result: '{}' });
|
|
116
|
+
seams.executeAgentInBackground = vi.fn();
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('loads history from the floor and hands the floor to the run', async () => {
|
|
120
|
+
const result = await runMutation(resolver, { agentHistoryFrom: FLOOR_ISO });
|
|
121
|
+
|
|
122
|
+
expect(result.success).toBe(true);
|
|
123
|
+
const loaderArgs = seams.loadConversationHistoryWithAttachments.mock.calls[0];
|
|
124
|
+
expect(loaderArgs[0]).toBe('conv-1');
|
|
125
|
+
expect(loaderArgs[4]).toEqual(['detail-1']);
|
|
126
|
+
expect((loaderArgs[5] as Date).toISOString()).toBe(FLOOR_ISO);
|
|
127
|
+
const runFloor = seams.executeAIAgent.mock.calls[0].at(-1) as Date;
|
|
128
|
+
expect(runFloor.toISOString()).toBe(FLOOR_ISO);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it('hands the floor to a fire-and-forget run too', async () => {
|
|
132
|
+
const result = await runMutation(resolver, { agentHistoryFrom: FLOOR_ISO, fireAndForget: true });
|
|
133
|
+
|
|
134
|
+
expect(result.success).toBe(true);
|
|
135
|
+
expect(seams.executeAIAgent).not.toHaveBeenCalled();
|
|
136
|
+
const runFloor = seams.executeAgentInBackground.mock.calls[0].at(-1) as Date;
|
|
137
|
+
expect(runFloor.toISOString()).toBe(FLOOR_ISO);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('left out, reads the whole conversation as before', async () => {
|
|
141
|
+
await runMutation(resolver, {});
|
|
142
|
+
|
|
143
|
+
expect(seams.loadConversationHistoryWithAttachments.mock.calls[0][5]).toBeUndefined();
|
|
144
|
+
expect(seams.executeAIAgent.mock.calls[0].at(-1)).toBeUndefined();
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('fails the request on an unreadable floor, before loading or running anything', async () => {
|
|
148
|
+
const result = await runMutation(resolver, { agentHistoryFrom: 'last tuesday' });
|
|
149
|
+
|
|
150
|
+
expect(result.success).toBe(false);
|
|
151
|
+
expect(result.errorMessage).toContain('agentHistoryFrom');
|
|
152
|
+
expect(seams.loadConversationHistoryWithAttachments).not.toHaveBeenCalled();
|
|
153
|
+
expect(seams.executeAIAgent).not.toHaveBeenCalled();
|
|
154
|
+
expect(seams.executeAgentInBackground).not.toHaveBeenCalled();
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it('treats an empty string as unreadable too, never as "no floor"', async () => {
|
|
158
|
+
const result = await runMutation(resolver, { agentHistoryFrom: '' });
|
|
159
|
+
|
|
160
|
+
expect(result.success).toBe(false);
|
|
161
|
+
expect(seams.executeAIAgent).not.toHaveBeenCalled();
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
describe('loadConversationHistoryWithAttachments — history floor', () => {
|
|
166
|
+
it('passes the floor to the fresh loader and to the window assembly', async () => {
|
|
167
|
+
const { resolver, seams } = makeResolver();
|
|
168
|
+
seams.loadInputArtifactsBatch = vi.fn().mockResolvedValue(new Map());
|
|
169
|
+
const loadRows = vi.spyOn(ConversationEngine, 'LoadWindowRowsFresh').mockResolvedValue([]);
|
|
170
|
+
const assemble = vi.spyOn(ConversationEngine, 'AssembleContextWindow');
|
|
171
|
+
const floor = new Date(FLOOR_ISO);
|
|
172
|
+
const provider = {};
|
|
173
|
+
|
|
174
|
+
const load = (resolver as unknown as {
|
|
175
|
+
loadConversationHistoryWithAttachments(
|
|
176
|
+
conversationId: string, user: unknown, max: number, provider: unknown, exclude?: string[], historyFrom?: Date
|
|
177
|
+
): Promise<unknown[]>;
|
|
178
|
+
}).loadConversationHistoryWithAttachments.bind(resolver);
|
|
179
|
+
await load('conv-1', USER, 20, provider, ['detail-1'], floor);
|
|
180
|
+
|
|
181
|
+
expect(loadRows).toHaveBeenCalledWith('conv-1', USER, provider, floor);
|
|
182
|
+
expect(assemble.mock.calls[0][1]).toEqual({ maxTailMessages: 20, excludeDetailIds: ['detail-1'], historyFrom: floor });
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
describe('executeAIAgent — ConversationHistoryFrom', () => {
|
|
187
|
+
function wire() {
|
|
188
|
+
const { resolver, seams } = makeResolver();
|
|
189
|
+
seams.validateAgent = vi.fn().mockResolvedValue({ ID: 'agent-1', Name: 'Test Agent' });
|
|
190
|
+
seams.persistInFlightAgentFailure = vi.fn().mockResolvedValue(undefined);
|
|
191
|
+
// Stop right after the params are captured — the rest of the run is out of scope, and so
|
|
192
|
+
// is the failure it logs.
|
|
193
|
+
hoisted.runAgentInConversation.mockReset().mockRejectedValue(new Error('stop here'));
|
|
194
|
+
vi.spyOn(console, 'error').mockImplementation(() => undefined);
|
|
195
|
+
vi.spyOn(console, 'log').mockImplementation(() => undefined);
|
|
196
|
+
const execute = (resolver as unknown as {
|
|
197
|
+
executeAIAgent(...args: unknown[]): Promise<{ success: boolean }>;
|
|
198
|
+
}).executeAIAgent.bind(resolver);
|
|
199
|
+
return execute;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function positional(conversationHistoryFrom?: Date): unknown[] {
|
|
203
|
+
const userPayload = { sessionId: 'session-1', userRecord: USER };
|
|
204
|
+
return [
|
|
205
|
+
{}, {}, 'agent-1', userPayload, '[{"role":"user","content":"hi"}]', 'session-1', pubSub,
|
|
206
|
+
undefined, undefined, undefined, undefined, undefined, undefined, 'detail-1', false, false,
|
|
207
|
+
undefined, undefined, 'conv-1', undefined, undefined, undefined, undefined,
|
|
208
|
+
conversationHistoryFrom,
|
|
209
|
+
];
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
it('puts the floor on the run\'s params', async () => {
|
|
213
|
+
const execute = wire();
|
|
214
|
+
const floor = new Date(FLOOR_ISO);
|
|
215
|
+
await execute(...positional(floor));
|
|
216
|
+
|
|
217
|
+
const params = hoisted.runAgentInConversation.mock.calls[0][0] as Record<string, unknown>;
|
|
218
|
+
expect(params.ConversationHistoryFrom).toBe(floor);
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
it('leaves it unset without a floor', async () => {
|
|
222
|
+
const execute = wire();
|
|
223
|
+
await execute(...positional());
|
|
224
|
+
|
|
225
|
+
const params = hoisted.runAgentInConversation.mock.calls[0][0] as Record<string, unknown>;
|
|
226
|
+
expect(params.ConversationHistoryFrom).toBeUndefined();
|
|
227
|
+
});
|
|
228
|
+
});
|