@memberjunction/server 6.2.0-edge.5 → 6.2.0-edge.6
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 +27 -1
- package/dist/.tsbuildinfo +1 -1
- package/dist/agentSessions/SessionJanitor.d.ts +3 -3
- package/dist/agentSessions/SessionJanitor.js +3 -3
- package/dist/agentSessions/SessionManager.d.ts +15 -5
- package/dist/agentSessions/SessionManager.d.ts.map +1 -1
- package/dist/agentSessions/SessionManager.js +34 -12
- package/dist/agentSessions/SessionManager.js.map +1 -1
- package/dist/auth/AuthProviderCatalogRouter.d.ts.map +1 -1
- package/dist/auth/AuthProviderCatalogRouter.js +2 -0
- package/dist/auth/AuthProviderCatalogRouter.js.map +1 -1
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/index.js +51 -10
- package/dist/auth/index.js.map +1 -1
- package/dist/auth/initializeProviders.d.ts +10 -0
- package/dist/auth/initializeProviders.d.ts.map +1 -1
- package/dist/auth/initializeProviders.js +37 -1
- package/dist/auth/initializeProviders.js.map +1 -1
- package/dist/auth/localDevAuth.d.ts +79 -0
- package/dist/auth/localDevAuth.d.ts.map +1 -0
- package/dist/auth/localDevAuth.js +439 -0
- package/dist/auth/localDevAuth.js.map +1 -0
- package/dist/auth/magicLink/MagicLinkKeys.d.ts +2 -2
- package/dist/auth/magicLink/MagicLinkKeys.d.ts.map +1 -1
- package/dist/auth/magicLink/MagicLinkKeys.js +1 -1
- package/dist/auth/magicLink/MagicLinkKeys.js.map +1 -1
- package/dist/auth/magicLink/types.d.ts +12 -0
- package/dist/auth/magicLink/types.d.ts.map +1 -1
- package/dist/auth/sessionUserClone.d.ts +1 -1
- package/dist/auth/sessionUserClone.d.ts.map +1 -1
- package/dist/auth/sessionUserClone.js +3 -2
- package/dist/auth/sessionUserClone.js.map +1 -1
- package/dist/broadcast/BaseCacheInvalidationBroadcastProvider.d.ts +292 -0
- package/dist/broadcast/BaseCacheInvalidationBroadcastProvider.d.ts.map +1 -0
- package/dist/broadcast/BaseCacheInvalidationBroadcastProvider.js +442 -0
- package/dist/broadcast/BaseCacheInvalidationBroadcastProvider.js.map +1 -0
- package/dist/broadcast/index.d.ts +6 -0
- package/dist/broadcast/index.d.ts.map +1 -0
- package/dist/broadcast/index.js +6 -0
- package/dist/broadcast/index.js.map +1 -0
- package/dist/broadcast/providers/AWSIoTCoreBroadcastProvider.d.ts +43 -0
- package/dist/broadcast/providers/AWSIoTCoreBroadcastProvider.d.ts.map +1 -0
- package/dist/broadcast/providers/AWSIoTCoreBroadcastProvider.js +144 -0
- package/dist/broadcast/providers/AWSIoTCoreBroadcastProvider.js.map +1 -0
- package/dist/broadcast/providers/AzureWebPubSubBroadcastProvider.d.ts +46 -0
- package/dist/broadcast/providers/AzureWebPubSubBroadcastProvider.d.ts.map +1 -0
- package/dist/broadcast/providers/AzureWebPubSubBroadcastProvider.js +149 -0
- package/dist/broadcast/providers/AzureWebPubSubBroadcastProvider.js.map +1 -0
- package/dist/broadcast/providers/GCPPubSubBroadcastProvider.d.ts +49 -0
- package/dist/broadcast/providers/GCPPubSubBroadcastProvider.d.ts.map +1 -0
- package/dist/broadcast/providers/GCPPubSubBroadcastProvider.js +131 -0
- package/dist/broadcast/providers/GCPPubSubBroadcastProvider.js.map +1 -0
- package/dist/broadcast/providers/SequencedRoleBucketBroadcastProvider.d.ts +24 -0
- package/dist/broadcast/providers/SequencedRoleBucketBroadcastProvider.d.ts.map +1 -0
- package/dist/broadcast/providers/SequencedRoleBucketBroadcastProvider.js +51 -0
- package/dist/broadcast/providers/SequencedRoleBucketBroadcastProvider.js.map +1 -0
- package/dist/cache.d.ts +3 -2
- package/dist/cache.d.ts.map +1 -1
- package/dist/cache.js.map +1 -1
- package/dist/config.d.ts +496 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +61 -0
- package/dist/config.js.map +1 -1
- package/dist/context.d.ts +3 -1
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +29 -8
- package/dist/context.js.map +1 -1
- package/dist/coreRoutes.d.ts.map +1 -1
- package/dist/coreRoutes.js +14 -0
- package/dist/coreRoutes.js.map +1 -1
- package/dist/generated/graphql-schemas/__mj.d.ts +635 -14
- package/dist/generated/graphql-schemas/__mj.d.ts.map +1 -1
- package/dist/generated/graphql-schemas/__mj.js +3366 -418
- package/dist/generated/graphql-schemas/__mj.js.map +1 -1
- package/dist/generic/CacheInvalidationResolver.d.ts +50 -30
- package/dist/generic/CacheInvalidationResolver.d.ts.map +1 -1
- package/dist/generic/CacheInvalidationResolver.js +154 -79
- package/dist/generic/CacheInvalidationResolver.js.map +1 -1
- package/dist/generic/OrphanedConversationDetailReconciler.d.ts.map +1 -1
- package/dist/generic/OrphanedConversationDetailReconciler.js +2 -0
- package/dist/generic/OrphanedConversationDetailReconciler.js.map +1 -1
- package/dist/generic/PubSubManager.d.ts +10 -0
- package/dist/generic/PubSubManager.d.ts.map +1 -1
- package/dist/generic/PubSubManager.js +44 -1
- package/dist/generic/PubSubManager.js.map +1 -1
- package/dist/generic/PushStatusResolver.d.ts +39 -6
- package/dist/generic/PushStatusResolver.d.ts.map +1 -1
- package/dist/generic/PushStatusResolver.js +93 -11
- package/dist/generic/PushStatusResolver.js.map +1 -1
- package/dist/generic/ResolverBase.d.ts +65 -25
- package/dist/generic/ResolverBase.d.ts.map +1 -1
- package/dist/generic/ResolverBase.js +201 -101
- package/dist/generic/ResolverBase.js.map +1 -1
- package/dist/generic/RunViewResolver.d.ts +11 -3
- package/dist/generic/RunViewResolver.d.ts.map +1 -1
- package/dist/generic/RunViewResolver.js +79 -13
- package/dist/generic/RunViewResolver.js.map +1 -1
- package/dist/generic/actionOutputFieldSecurity.d.ts +26 -0
- package/dist/generic/actionOutputFieldSecurity.d.ts.map +1 -0
- package/dist/generic/actionOutputFieldSecurity.js +47 -0
- package/dist/generic/actionOutputFieldSecurity.js.map +1 -0
- package/dist/generic/fieldSecurityTransport.d.ts +30 -0
- package/dist/generic/fieldSecurityTransport.d.ts.map +1 -0
- package/dist/generic/fieldSecurityTransport.js +30 -0
- package/dist/generic/fieldSecurityTransport.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +122 -66
- package/dist/index.js.map +1 -1
- package/dist/integration/CustomColumnPromoter.d.ts.map +1 -1
- package/dist/integration/CustomColumnPromoter.js +6 -1
- package/dist/integration/CustomColumnPromoter.js.map +1 -1
- package/dist/logging/variablesLoggingMiddleware.d.ts +12 -0
- package/dist/logging/variablesLoggingMiddleware.d.ts.map +1 -1
- package/dist/logging/variablesLoggingMiddleware.js +50 -1
- package/dist/logging/variablesLoggingMiddleware.js.map +1 -1
- package/dist/realtimeSessions/SessionVerificationPortsBase.d.ts.map +1 -1
- package/dist/realtimeSessions/SessionVerificationPortsBase.js +1 -0
- package/dist/realtimeSessions/SessionVerificationPortsBase.js.map +1 -1
- package/dist/realtimeSessions/ownedRealtimeSession.d.ts +8 -6
- package/dist/realtimeSessions/ownedRealtimeSession.d.ts.map +1 -1
- package/dist/realtimeSessions/ownedRealtimeSession.js +15 -6
- package/dist/realtimeSessions/ownedRealtimeSession.js.map +1 -1
- package/dist/realtimeSessions/sessionAccess.d.ts +88 -11
- package/dist/realtimeSessions/sessionAccess.d.ts.map +1 -1
- package/dist/realtimeSessions/sessionAccess.js +125 -22
- package/dist/realtimeSessions/sessionAccess.js.map +1 -1
- package/dist/realtimeSessions/sessionConfigStamps.js +1 -1
- package/dist/realtimeSessions/sessionConfigStamps.js.map +1 -1
- package/dist/realtimeSessions/subscriptionAuthorization.d.ts.map +1 -1
- package/dist/realtimeSessions/subscriptionAuthorization.js +5 -2
- package/dist/realtimeSessions/subscriptionAuthorization.js.map +1 -1
- package/dist/realtimeSessions/verificationWorkflow.d.ts +2 -0
- package/dist/realtimeSessions/verificationWorkflow.d.ts.map +1 -1
- package/dist/realtimeSessions/verificationWorkflow.js +1 -1
- package/dist/realtimeSessions/verificationWorkflow.js.map +1 -1
- package/dist/realtimeWidget/WidgetRouter.d.ts.map +1 -1
- package/dist/realtimeWidget/WidgetRouter.js +19 -3
- package/dist/realtimeWidget/WidgetRouter.js.map +1 -1
- package/dist/realtimeWidget/WidgetSessionService.d.ts +67 -7
- package/dist/realtimeWidget/WidgetSessionService.d.ts.map +1 -1
- package/dist/realtimeWidget/WidgetSessionService.js +201 -19
- package/dist/realtimeWidget/WidgetSessionService.js.map +1 -1
- package/dist/realtimeWidget/host-identity.d.ts +1 -1
- package/dist/realtimeWidget/host-identity.js +1 -1
- package/dist/realtimeWidget/widgetCore.d.ts +20 -1
- package/dist/realtimeWidget/widgetCore.d.ts.map +1 -1
- package/dist/realtimeWidget/widgetCore.js +17 -1
- package/dist/realtimeWidget/widgetCore.js.map +1 -1
- package/dist/realtimeWidget/widgetGuestElevation.d.ts +1 -1
- package/dist/realtimeWidget/widgetGuestElevation.d.ts.map +1 -1
- package/dist/realtimeWidget/widgetGuestElevation.js +2 -1
- package/dist/realtimeWidget/widgetGuestElevation.js.map +1 -1
- package/dist/resolvers/ActionResolver.d.ts +19 -1
- package/dist/resolvers/ActionResolver.d.ts.map +1 -1
- package/dist/resolvers/ActionResolver.js +63 -12
- package/dist/resolvers/ActionResolver.js.map +1 -1
- package/dist/resolvers/AdhocQueryResolver.d.ts.map +1 -1
- package/dist/resolvers/AdhocQueryResolver.js +1 -0
- package/dist/resolvers/AdhocQueryResolver.js.map +1 -1
- package/dist/resolvers/AgentRunStatusPublisher.d.ts +18 -4
- package/dist/resolvers/AgentRunStatusPublisher.d.ts.map +1 -1
- package/dist/resolvers/AgentRunStatusPublisher.js +32 -8
- package/dist/resolvers/AgentRunStatusPublisher.js.map +1 -1
- package/dist/resolvers/AgentSessionResolver.d.ts +10 -4
- package/dist/resolvers/AgentSessionResolver.d.ts.map +1 -1
- package/dist/resolvers/AgentSessionResolver.js +40 -17
- package/dist/resolvers/AgentSessionResolver.js.map +1 -1
- package/dist/resolvers/EntityCommunicationsResolver.d.ts +13 -1
- package/dist/resolvers/EntityCommunicationsResolver.d.ts.map +1 -1
- package/dist/resolvers/EntityCommunicationsResolver.js +50 -14
- package/dist/resolvers/EntityCommunicationsResolver.js.map +1 -1
- package/dist/resolvers/FetchEntityVectorsResolver.d.ts +28 -0
- package/dist/resolvers/FetchEntityVectorsResolver.d.ts.map +1 -1
- package/dist/resolvers/FetchEntityVectorsResolver.js +117 -21
- package/dist/resolvers/FetchEntityVectorsResolver.js.map +1 -1
- package/dist/resolvers/FileCategoryResolver.d.ts.map +1 -1
- package/dist/resolvers/FileCategoryResolver.js +1 -0
- package/dist/resolvers/FileCategoryResolver.js.map +1 -1
- package/dist/resolvers/FileResolver.d.ts +5 -0
- package/dist/resolvers/FileResolver.d.ts.map +1 -1
- package/dist/resolvers/FileResolver.js +13 -4
- package/dist/resolvers/FileResolver.js.map +1 -1
- package/dist/resolvers/IntegrationDiscoveryResolver.d.ts.map +1 -1
- package/dist/resolvers/IntegrationDiscoveryResolver.js +45 -7
- package/dist/resolvers/IntegrationDiscoveryResolver.js.map +1 -1
- package/dist/resolvers/MergeRecordsResolver.d.ts +8 -1
- package/dist/resolvers/MergeRecordsResolver.d.ts.map +1 -1
- package/dist/resolvers/MergeRecordsResolver.js +31 -3
- package/dist/resolvers/MergeRecordsResolver.js.map +1 -1
- package/dist/resolvers/PipelineProgressResolver.d.ts +6 -0
- package/dist/resolvers/PipelineProgressResolver.d.ts.map +1 -1
- package/dist/resolvers/PipelineProgressResolver.js +5 -3
- package/dist/resolvers/PipelineProgressResolver.js.map +1 -1
- package/dist/resolvers/QueryResolver.d.ts.map +1 -1
- package/dist/resolvers/QueryResolver.js +20 -6
- package/dist/resolvers/QueryResolver.js.map +1 -1
- package/dist/resolvers/QuerySystemUserResolver.d.ts +2 -0
- package/dist/resolvers/QuerySystemUserResolver.d.ts.map +1 -1
- package/dist/resolvers/QuerySystemUserResolver.js +15 -6
- package/dist/resolvers/QuerySystemUserResolver.js.map +1 -1
- package/dist/resolvers/RSUResolver.d.ts.map +1 -1
- package/dist/resolvers/RSUResolver.js +12 -0
- package/dist/resolvers/RSUResolver.js.map +1 -1
- package/dist/resolvers/RealtimeClientSessionResolver.d.ts +16 -2
- package/dist/resolvers/RealtimeClientSessionResolver.d.ts.map +1 -1
- package/dist/resolvers/RealtimeClientSessionResolver.js +82 -28
- package/dist/resolvers/RealtimeClientSessionResolver.js.map +1 -1
- package/dist/resolvers/RemoteBrowserActionResolver.d.ts.map +1 -1
- package/dist/resolvers/RemoteBrowserActionResolver.js +11 -9
- package/dist/resolvers/RemoteBrowserActionResolver.js.map +1 -1
- package/dist/resolvers/RunAIAgentResolver.d.ts +56 -8
- package/dist/resolvers/RunAIAgentResolver.d.ts.map +1 -1
- package/dist/resolvers/RunAIAgentResolver.js +209 -64
- package/dist/resolvers/RunAIAgentResolver.js.map +1 -1
- package/dist/resolvers/RunTemplateResolver.d.ts.map +1 -1
- package/dist/resolvers/RunTemplateResolver.js +4 -4
- package/dist/resolvers/RunTemplateResolver.js.map +1 -1
- package/dist/resolvers/RunTestResolver.d.ts.map +1 -1
- package/dist/resolvers/RunTestResolver.js +3 -1
- package/dist/resolvers/RunTestResolver.js.map +1 -1
- package/dist/resolvers/SyncDataResolver.d.ts.map +1 -1
- package/dist/resolvers/SyncDataResolver.js +4 -0
- package/dist/resolvers/SyncDataResolver.js.map +1 -1
- package/dist/resolvers/TestQuerySQLResolver.d.ts.map +1 -1
- package/dist/resolvers/TestQuerySQLResolver.js +1 -0
- package/dist/resolvers/TestQuerySQLResolver.js.map +1 -1
- package/dist/resolvers/TransactionGroupResolver.d.ts +17 -2
- package/dist/resolvers/TransactionGroupResolver.d.ts.map +1 -1
- package/dist/resolvers/TransactionGroupResolver.js +60 -15
- package/dist/resolvers/TransactionGroupResolver.js.map +1 -1
- package/dist/resolvers/UserViewResolver.d.ts +1 -1
- package/dist/resolvers/UserViewResolver.d.ts.map +1 -1
- package/dist/resolvers/UserViewResolver.js +2 -1
- package/dist/resolvers/UserViewResolver.js.map +1 -1
- package/dist/rest/EntityCRUDHandler.d.ts +6 -0
- package/dist/rest/EntityCRUDHandler.d.ts.map +1 -1
- package/dist/rest/EntityCRUDHandler.js +19 -5
- package/dist/rest/EntityCRUDHandler.js.map +1 -1
- package/dist/rest/RESTEndpointHandler.d.ts.map +1 -1
- package/dist/rest/RESTEndpointHandler.js +8 -5
- package/dist/rest/RESTEndpointHandler.js.map +1 -1
- package/dist/rest/RealtimeRecordingHandler.d.ts +3 -3
- package/dist/rest/RealtimeRecordingHandler.d.ts.map +1 -1
- package/dist/rest/RealtimeRecordingHandler.js +7 -7
- package/dist/rest/RealtimeRecordingHandler.js.map +1 -1
- package/dist/rest/ViewOperationsHandler.d.ts +6 -0
- package/dist/rest/ViewOperationsHandler.d.ts.map +1 -1
- package/dist/rest/ViewOperationsHandler.js +22 -5
- package/dist/rest/ViewOperationsHandler.js.map +1 -1
- package/dist/serverRoleConfigUnits.d.ts +29 -0
- package/dist/serverRoleConfigUnits.d.ts.map +1 -0
- package/dist/serverRoleConfigUnits.js +33 -0
- package/dist/serverRoleConfigUnits.js.map +1 -0
- package/dist/services/TaskGraphContinuationDeliverer.d.ts +5 -0
- package/dist/services/TaskGraphContinuationDeliverer.d.ts.map +1 -1
- package/dist/services/TaskGraphContinuationDeliverer.js +42 -15
- package/dist/services/TaskGraphContinuationDeliverer.js.map +1 -1
- package/dist/types.d.ts +23 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +98 -98
- package/src/__tests__/ActionResolver.fieldSecurity.test.ts +237 -0
- package/src/__tests__/AdhocQueryResolver.dialect.test.ts +137 -0
- package/src/__tests__/AgentRunStatusPublisher.test.ts +49 -0
- package/src/__tests__/AgentSessionResolver.ownership.test.ts +137 -0
- package/src/__tests__/CustomColumnPromoterOnePass.test.ts +15 -1
- package/src/__tests__/EntityCommunicationsResolver.actingUser.test.ts +151 -0
- package/src/__tests__/EvolutionActsOnWhatItDetects.test.ts +113 -0
- package/src/__tests__/FetchEntityVectorsResolver.test.ts +206 -3
- package/src/__tests__/FileResolver.fieldSecurity.test.ts +82 -0
- package/src/__tests__/PipelineProgressResolver.filter.test.ts +58 -0
- package/src/__tests__/QuerySystemUserResolver.fieldSecurity.test.ts +49 -0
- package/src/__tests__/RESTEndpointHandler.test.ts +25 -0
- package/src/__tests__/RealtimeClientSessionResolver.configTamper.test.ts +23 -3
- package/src/__tests__/RealtimeClientSessionResolver.test.ts +324 -46
- package/src/__tests__/RemoteBrowserActionResolver.ownership.test.ts +98 -0
- package/src/__tests__/ResolverBase.clientOldValues.test.ts +37 -4
- package/src/__tests__/ResolverBase.deleteReturnProjection.test.ts +52 -0
- package/src/__tests__/ResolverBase.entityMessageRelay.test.ts +176 -0
- package/src/__tests__/ResolverBase.includeBinaryFields.test.ts +23 -1
- package/src/__tests__/ResolverBase.publishStatusUpdate.test.ts +68 -0
- package/src/__tests__/ResolverBase.userViewCacheAndDedup.test.ts +130 -0
- package/src/__tests__/RunAIAgentResolver.branch.test.ts +68 -13
- package/src/__tests__/RunAIAgentResolver.conversationWriter.test.ts +210 -0
- package/src/__tests__/RunAIAgentResolver.fireAndForgetCompletion.test.ts +209 -0
- package/src/__tests__/RunAIAgentResolver.historyFrom.test.ts +17 -27
- package/src/__tests__/RunAIAgentResolver.runDataGuard.test.ts +8 -2
- package/src/__tests__/RunAIAgentResolver.serverReply.test.ts +358 -0
- package/src/__tests__/RunAIAgentResolver.stoppedPredecessor.test.ts +4 -1
- package/src/__tests__/RunTemplateResolver.test.ts +35 -2
- package/src/__tests__/RunViewResolver.cacheCheckForwarding.test.ts +11 -0
- package/src/__tests__/SessionManager.test.ts +95 -0
- package/src/__tests__/TaskGraphContinuationDeliverer.test.ts +114 -3
- package/src/__tests__/TransactionGroupResolver.fieldSecurity.test.ts +199 -0
- package/src/__tests__/UserViewResolver.updateWhereClause.test.ts +15 -0
- package/src/__tests__/ViewOperationsHandler.test.ts +7 -2
- package/src/__tests__/WidgetSessionService.test.ts +583 -4
- package/src/__tests__/apiRootBoot.test.ts +1 -1
- package/src/__tests__/buildMagicLinkSessionUser.test.ts +96 -0
- package/src/__tests__/cacheBroadcastProviders.test.ts +403 -0
- package/src/__tests__/cacheInvalidationRecordData.test.ts +45 -0
- package/src/__tests__/fieldSecurityFixtures.ts +91 -0
- package/src/__tests__/globalCors.test.ts +3 -3
- package/src/__tests__/localDevAuth.test.ts +196 -0
- package/src/__tests__/orphanedConversationDetailReconciler.test.ts +10 -0
- package/src/__tests__/pushStatusResolver.filter.test.ts +57 -1
- package/src/__tests__/realtimeRecordingHandler.test.ts +19 -2
- package/src/__tests__/realtimeSessions/RealtimeSessionEventsResolver.test.ts +29 -1
- package/src/__tests__/realtimeSessions/ownedRealtimeSession.anonymous.test.ts +31 -0
- package/src/__tests__/realtimeSessions/ownedRealtimeSession.namedScoped.test.ts +37 -0
- package/src/__tests__/realtimeSessions/sessionAccess.test.ts +146 -7
- package/src/__tests__/realtimeSessions/sessionConfigStamps.test.ts +1 -1
- package/src/__tests__/realtimeSessions/verificationWorkflow.test.ts +25 -4
- package/src/__tests__/resolverBase.cloudEventChangedFields.test.ts +157 -0
- package/src/__tests__/resolverBase.fieldSecurityTransport.test.ts +222 -0
- package/src/__tests__/resolverBase.fls.test.ts +146 -97
- package/src/__tests__/restTransport.fieldSecurity.test.ts +148 -0
- package/src/__tests__/runParamsTimeZone.test.ts +179 -0
- package/src/__tests__/runStreams.fieldSecurity.test.ts +100 -0
- package/src/__tests__/runViewProperties.test.ts +114 -0
- package/src/__tests__/scalabilityBenchmarks.test.ts +765 -0
- package/src/__tests__/scalabilityDatabaseIntegration.test.ts +207 -0
- package/src/__tests__/sessionUserClone.test.ts +5 -1
- package/src/__tests__/startupSweepOrder.test.ts +40 -0
- package/src/__tests__/unifiedAuth.test.ts +28 -0
- package/src/__tests__/widgetGuestElevation.test.ts +2 -0
- package/src/agentSessions/SessionJanitor.ts +3 -3
- package/src/agentSessions/SessionManager.ts +35 -12
- package/src/auth/AuthProviderCatalogRouter.ts +3 -0
- package/src/auth/index.ts +56 -12
- package/src/auth/initializeProviders.ts +43 -1
- package/src/auth/localDevAuth.ts +528 -0
- package/src/auth/magicLink/MagicLinkKeys.ts +2 -2
- package/src/auth/magicLink/types.ts +10 -0
- package/src/auth/sessionUserClone.ts +3 -2
- package/src/broadcast/BaseCacheInvalidationBroadcastProvider.ts +674 -0
- package/src/broadcast/index.ts +5 -0
- package/src/broadcast/providers/AWSIoTCoreBroadcastProvider.ts +180 -0
- package/src/broadcast/providers/AzureWebPubSubBroadcastProvider.ts +182 -0
- package/src/broadcast/providers/GCPPubSubBroadcastProvider.ts +169 -0
- package/src/broadcast/providers/SequencedRoleBucketBroadcastProvider.ts +57 -0
- package/src/cache.ts +2 -1
- package/src/config.ts +69 -0
- package/src/context.ts +32 -8
- package/src/coreRoutes.ts +15 -0
- package/src/generated/graphql-schemas/__mj.ts +2239 -102
- package/src/generic/CacheInvalidationResolver.ts +135 -78
- package/src/generic/OrphanedConversationDetailReconciler.ts +4 -2
- package/src/generic/PubSubManager.ts +54 -1
- package/src/generic/PushStatusResolver.ts +108 -11
- package/src/generic/ResolverBase.ts +237 -107
- package/src/generic/RunViewResolver.ts +80 -15
- package/src/generic/actionOutputFieldSecurity.ts +75 -0
- package/src/generic/fieldSecurityTransport.ts +62 -0
- package/src/index.ts +127 -68
- package/src/integration/CustomColumnPromoter.ts +6 -1
- package/src/logging/variablesLoggingMiddleware.ts +59 -3
- package/src/realtimeSessions/README.md +7 -3
- package/src/realtimeSessions/SessionVerificationPortsBase.ts +1 -0
- package/src/realtimeSessions/ownedRealtimeSession.ts +20 -6
- package/src/realtimeSessions/sessionAccess.ts +181 -25
- package/src/realtimeSessions/sessionConfigStamps.ts +1 -1
- package/src/realtimeSessions/subscriptionAuthorization.ts +4 -2
- package/src/realtimeSessions/verificationWorkflow.ts +3 -1
- package/src/realtimeWidget/WidgetRouter.ts +22 -4
- package/src/realtimeWidget/WidgetSessionService.ts +252 -17
- package/src/realtimeWidget/host-identity.ts +1 -1
- package/src/realtimeWidget/widgetCore.ts +30 -1
- package/src/realtimeWidget/widgetGuestElevation.ts +2 -1
- package/src/resolvers/ActionResolver.ts +78 -15
- package/src/resolvers/AdhocQueryResolver.ts +1 -0
- package/src/resolvers/AgentRunStatusPublisher.ts +36 -8
- package/src/resolvers/AgentSessionResolver.ts +46 -18
- package/src/resolvers/EntityCommunicationsResolver.ts +56 -15
- package/src/resolvers/FetchEntityVectorsResolver.ts +132 -21
- package/src/resolvers/FileCategoryResolver.ts +1 -0
- package/src/resolvers/FileResolver.ts +14 -4
- package/src/resolvers/IntegrationDiscoveryResolver.ts +49 -7
- package/src/resolvers/MergeRecordsResolver.ts +23 -2
- package/src/resolvers/PipelineProgressResolver.ts +12 -3
- package/src/resolvers/QueryResolver.ts +20 -6
- package/src/resolvers/QuerySystemUserResolver.ts +15 -6
- package/src/resolvers/RSUResolver.ts +12 -0
- package/src/resolvers/RealtimeClientSessionResolver.ts +104 -33
- package/src/resolvers/RemoteBrowserActionResolver.ts +15 -10
- package/src/resolvers/RunAIAgentResolver.ts +258 -82
- package/src/resolvers/RunTemplateResolver.ts +6 -4
- package/src/resolvers/RunTestResolver.ts +3 -1
- package/src/resolvers/SyncDataResolver.ts +4 -0
- package/src/resolvers/TestQuerySQLResolver.ts +1 -0
- package/src/resolvers/TransactionGroupResolver.ts +66 -15
- package/src/resolvers/UserViewResolver.ts +2 -1
- package/src/resolvers/__tests__/MergeRecordsResolver.test.ts +43 -0
- package/src/rest/EntityCRUDHandler.ts +22 -5
- package/src/rest/RESTEndpointHandler.ts +8 -5
- package/src/rest/RealtimeRecordingHandler.ts +13 -10
- package/src/rest/ViewOperationsHandler.ts +24 -6
- package/src/serverRoleConfigUnits.ts +48 -0
- package/src/services/TaskGraphContinuationDeliverer.ts +52 -20
- package/src/types.ts +23 -1
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
3
|
+
import type { IMetadataProvider, UserInfo } from '@memberjunction/core';
|
|
4
|
+
import type { MJAIAgentSessionEntity } from '@memberjunction/core-entities';
|
|
5
|
+
|
|
6
|
+
vi.mock('type-graphql', () => {
|
|
7
|
+
const noopDecorator = () => () => undefined;
|
|
8
|
+
return {
|
|
9
|
+
Resolver: noopDecorator,
|
|
10
|
+
Mutation: noopDecorator,
|
|
11
|
+
Query: noopDecorator,
|
|
12
|
+
Subscription: noopDecorator,
|
|
13
|
+
ObjectType: noopDecorator,
|
|
14
|
+
InputType: noopDecorator,
|
|
15
|
+
Field: noopDecorator,
|
|
16
|
+
Arg: noopDecorator,
|
|
17
|
+
Args: noopDecorator,
|
|
18
|
+
Ctx: noopDecorator,
|
|
19
|
+
PubSub: noopDecorator,
|
|
20
|
+
Root: noopDecorator,
|
|
21
|
+
Float: class {},
|
|
22
|
+
Int: class {},
|
|
23
|
+
ID: class {},
|
|
24
|
+
};
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
vi.mock('@memberjunction/remote-browser-server', () => ({
|
|
28
|
+
NormalizeInstanceKey: (key?: string) => key,
|
|
29
|
+
RemoteBrowserEngine: { Instance: {} },
|
|
30
|
+
}));
|
|
31
|
+
vi.mock('@memberjunction/aiengine', () => ({ AIEngine: { Instance: { Config: vi.fn(), Prompts: [] } } }));
|
|
32
|
+
vi.mock('@memberjunction/ai-prompts', async (importOriginal) => ({
|
|
33
|
+
...(await importOriginal<typeof import('@memberjunction/ai-prompts')>()),
|
|
34
|
+
AIPromptRunner: class {},
|
|
35
|
+
}));
|
|
36
|
+
vi.mock('@memberjunction/ai-core-plus', async (importOriginal) => ({
|
|
37
|
+
...(await importOriginal<typeof import('@memberjunction/ai-core-plus')>()),
|
|
38
|
+
AIPromptParams: class {},
|
|
39
|
+
}));
|
|
40
|
+
|
|
41
|
+
import { RemoteBrowserActionResolver } from '../resolvers/RemoteBrowserActionResolver.js';
|
|
42
|
+
|
|
43
|
+
const uuid = (a: string, b: string) =>
|
|
44
|
+
[a.repeat(8), b.repeat(4), '4' + b.repeat(3), '8' + b.repeat(3), b.repeat(12)].join('-');
|
|
45
|
+
const ANON = uuid('A', '0');
|
|
46
|
+
const SESSION = uuid('1', '1');
|
|
47
|
+
const CONVERSATION = uuid('2', '2');
|
|
48
|
+
|
|
49
|
+
const guest = (visitorId: string) =>
|
|
50
|
+
({
|
|
51
|
+
ID: ANON,
|
|
52
|
+
IsMagicLinkAnonymous: true,
|
|
53
|
+
// An unscoped multi-use invite: redemptions are told apart by mj_sid (a resource scope would be shared).
|
|
54
|
+
AnonymousVisitorID: visitorId,
|
|
55
|
+
}) as unknown as UserInfo;
|
|
56
|
+
|
|
57
|
+
function makeProvider(sessionFound = true, externalId = 'visitor-1'): IMetadataProvider {
|
|
58
|
+
return {
|
|
59
|
+
GetEntityObject: vi.fn(async (entityName: string) =>
|
|
60
|
+
entityName === 'MJ: AI Agent Sessions'
|
|
61
|
+
? { ID: SESSION, UserID: ANON, ConversationID: CONVERSATION, Load: vi.fn(async () => sessionFound) }
|
|
62
|
+
: { ExternalID: externalId, Load: vi.fn(async () => true) },
|
|
63
|
+
),
|
|
64
|
+
} as unknown as IMetadataProvider;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
class TestableRemoteBrowserResolver extends RemoteBrowserActionResolver {
|
|
68
|
+
public async LoadOwned(
|
|
69
|
+
agentSessionID: string,
|
|
70
|
+
contextUser: UserInfo,
|
|
71
|
+
provider: IMetadataProvider,
|
|
72
|
+
): Promise<MJAIAgentSessionEntity> {
|
|
73
|
+
return this.loadOwnedSession(agentSessionID, contextUser, provider);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
describe('RemoteBrowserActionResolver.loadOwnedSession anonymous visitor ownership', () => {
|
|
78
|
+
it('allows the matching anonymous visitor and refuses a second anonymous visitor', async () => {
|
|
79
|
+
const resolver = new TestableRemoteBrowserResolver();
|
|
80
|
+
const provider = makeProvider(true, 'visitor-1');
|
|
81
|
+
|
|
82
|
+
const loaded = await resolver.LoadOwned(SESSION, guest('visitor-1'), provider);
|
|
83
|
+
expect(loaded.ID).toBe(SESSION);
|
|
84
|
+
|
|
85
|
+
await expect(resolver.LoadOwned(SESSION, guest('visitor-2'), provider)).rejects.toThrow(
|
|
86
|
+
`Not authorized: remote-browser session ${SESSION} is not owned by you.`,
|
|
87
|
+
);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('throws the expected not-found message when the session does not exist', async () => {
|
|
91
|
+
const resolver = new TestableRemoteBrowserResolver();
|
|
92
|
+
const provider = makeProvider(false);
|
|
93
|
+
|
|
94
|
+
await expect(resolver.LoadOwned(SESSION, guest('visitor-1'), provider)).rejects.toThrow(
|
|
95
|
+
`Remote-browser session ${SESSION} not found.`,
|
|
96
|
+
);
|
|
97
|
+
});
|
|
98
|
+
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Reflect.metadata polyfill at import time.
|
|
2
2
|
import 'reflect-metadata';
|
|
3
|
-
import { describe, it, expect } from 'vitest';
|
|
3
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
4
4
|
import { BaseEntity, EntityFieldTSType, EntityInfo, UserInfo, type EntityFieldInfo, type IMetadataProvider } from '@memberjunction/core';
|
|
5
5
|
import { ResolverBase } from '../generic/ResolverBase.js';
|
|
6
6
|
|
|
@@ -97,6 +97,11 @@ describe('ResolverBase.MustLoadTruthFromDatabase & hasCompleteOldValues', () =>
|
|
|
97
97
|
expect(probe.MustLoad({ Name: ' mj: conversation details ', TrackRecordChanges: false }, true)).toBe(true);
|
|
98
98
|
});
|
|
99
99
|
|
|
100
|
+
it("always loads a conversation and an agent session, so client OldValues cannot hide a change to the anonymous ownership key (#4931)", () => {
|
|
101
|
+
expect(probe.MustLoad({ Name: 'MJ: Conversations', TrackRecordChanges: false }, true)).toBe(true);
|
|
102
|
+
expect(probe.MustLoad({ Name: 'MJ: AI Agent Sessions', TrackRecordChanges: false }, true)).toBe(true);
|
|
103
|
+
});
|
|
104
|
+
|
|
100
105
|
it('still trusts OldValues on other untracked entities without field security when OldValues___ covers all writable fields', () => {
|
|
101
106
|
const fields = buildTagEntityFields();
|
|
102
107
|
const completeOldValues = [
|
|
@@ -149,15 +154,16 @@ describe('ResolverBase.TestAndSetClientOldValuesToDBValues — partial delta Upd
|
|
|
149
154
|
EntityFields: buildTagEntityFields(),
|
|
150
155
|
});
|
|
151
156
|
|
|
152
|
-
function createLoadedTagEntity(errorLogSaves: Array<Record<string, unknown>> = []): { entity: BaseEntity; user: UserInfo } {
|
|
157
|
+
function createLoadedTagEntity(errorLogSaves: Array<Record<string, unknown>> = [], failSaveWith?: string): { entity: BaseEntity; user: UserInfo } {
|
|
153
158
|
const fakeProvider = {
|
|
154
159
|
Entities: [tagEntityInfo],
|
|
155
160
|
GetEntityObject: async () => {
|
|
156
161
|
const errEntity: Record<string, unknown> = {
|
|
157
162
|
Save: async () => {
|
|
158
163
|
errorLogSaves.push({ ...errEntity });
|
|
159
|
-
return
|
|
164
|
+
return failSaveWith === undefined;
|
|
160
165
|
},
|
|
166
|
+
LatestResult: failSaveWith === undefined ? null : { CompleteMessage: failSaveWith },
|
|
161
167
|
};
|
|
162
168
|
return errEntity;
|
|
163
169
|
},
|
|
@@ -222,12 +228,39 @@ describe('ResolverBase.TestAndSetClientOldValuesToDBValues — partial delta Upd
|
|
|
222
228
|
|
|
223
229
|
// Warning is logged to ErrorLog, and save still applies the new value
|
|
224
230
|
expect(errorLogSaves).toHaveLength(1);
|
|
225
|
-
expect(errorLogSaves[0].Code).toBe('
|
|
231
|
+
expect(errorLogSaves[0].Code).toBe('SAVE_INCONSISTENCY');
|
|
232
|
+
// ErrorLog.Code is NCHAR(20)
|
|
233
|
+
expect(String(errorLogSaves[0].Code).length).toBeLessThanOrEqual(20);
|
|
226
234
|
expect(String(errorLogSaves[0].Message)).toContain('Display_Name');
|
|
227
235
|
expect(entity.Get('Display Name')).toBe('Client New Display');
|
|
228
236
|
expect(entity.Get('Description')).toBe('Untouched DB Description');
|
|
229
237
|
});
|
|
230
238
|
|
|
239
|
+
it('reports the entity name and the reason when the ErrorLog save fails', async () => {
|
|
240
|
+
const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
241
|
+
const { entity, user } = createLoadedTagEntity([], 'value too long for Code');
|
|
242
|
+
try {
|
|
243
|
+
await probe.TestAndSet(
|
|
244
|
+
entity,
|
|
245
|
+
{
|
|
246
|
+
ID: '11111111-2222-3333-4444-555555555555',
|
|
247
|
+
Display_Name: 'Client New Display',
|
|
248
|
+
OldValues___: [
|
|
249
|
+
{ Key: 'ID', Value: '11111111-2222-3333-4444-555555555555' },
|
|
250
|
+
{ Key: 'Display_Name', Value: 'Stale Client Display' },
|
|
251
|
+
],
|
|
252
|
+
},
|
|
253
|
+
user
|
|
254
|
+
);
|
|
255
|
+
const message = errorSpy.mock.calls.map((c) => String(c[0])).join('\n');
|
|
256
|
+
expect(message).toContain('Failed to save ErrorLog record');
|
|
257
|
+
expect(message).toContain('value too long for Code');
|
|
258
|
+
expect(message).toContain(entity.EntityInfo.Name);
|
|
259
|
+
} finally {
|
|
260
|
+
errorSpy.mockRestore();
|
|
261
|
+
}
|
|
262
|
+
});
|
|
263
|
+
|
|
231
264
|
it('does not false-positive when a non-overlapping field differs in the DB', async () => {
|
|
232
265
|
const errorLogSaves: Array<Record<string, unknown>> = [];
|
|
233
266
|
const { entity, user } = createLoadedTagEntity(errorLogSaves);
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The GraphQL delete returns the record's last state. It is projected for the caller, so an
|
|
3
|
+
* entity object that holds a full-width row (loaded as another user, or not narrowed at its
|
|
4
|
+
* source) never hands the caller a value they are denied.
|
|
5
|
+
*/
|
|
6
|
+
import 'reflect-metadata';
|
|
7
|
+
import { describe, it, expect } from 'vitest';
|
|
8
|
+
import type { UserInfo } from '@memberjunction/core';
|
|
9
|
+
import { EmployeeEntity, FullWidthEmployeeRow, HRUser, Intern, SECRET_SALARY } from './fieldSecurityFixtures.js';
|
|
10
|
+
|
|
11
|
+
/** Drives the real `ResolverBase.DeleteRecord` with its unrelated collaborators stubbed on the instance. */
|
|
12
|
+
async function deleteAs(user: UserInfo): Promise<Record<string, unknown>> {
|
|
13
|
+
const { ResolverBase } = await import('../generic/ResolverBase.js');
|
|
14
|
+
const entityObject = {
|
|
15
|
+
EntityInfo: EmployeeEntity(true),
|
|
16
|
+
InnerLoad: async () => true,
|
|
17
|
+
GetAll: () => FullWidthEmployeeRow(),
|
|
18
|
+
Delete: async () => true,
|
|
19
|
+
LatestResult: { Message: '' },
|
|
20
|
+
};
|
|
21
|
+
const provider = { GetEntityObject: async () => entityObject, Entities: [] };
|
|
22
|
+
const resolver = Object.create(ResolverBase.prototype) as Record<string, unknown> & {
|
|
23
|
+
DeleteRecord: (...args: unknown[]) => Promise<Record<string, unknown>>;
|
|
24
|
+
};
|
|
25
|
+
resolver.CheckAPIKeyScopeAuthorization = async () => undefined;
|
|
26
|
+
resolver.BeforeDelete = async () => true;
|
|
27
|
+
resolver.AfterDelete = async () => undefined;
|
|
28
|
+
resolver.ListenForEntityMessages = () => undefined;
|
|
29
|
+
resolver.GetUserFromPayload = () => user;
|
|
30
|
+
return resolver.DeleteRecord(
|
|
31
|
+
'Employees',
|
|
32
|
+
{ KeyValuePairs: [{ FieldName: 'ID', Value: 'emp-1' }] },
|
|
33
|
+
{},
|
|
34
|
+
provider,
|
|
35
|
+
{ email: 'x@example.test', userRecord: user },
|
|
36
|
+
{ publish: async () => undefined },
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
describe('ResolverBase.DeleteRecord: the returned last state is projected for the caller', () => {
|
|
41
|
+
it('REGRESSION: returns no denied value even when the entity object holds the full row', async () => {
|
|
42
|
+
const returned = await deleteAs(Intern());
|
|
43
|
+
expect(returned).not.toHaveProperty('Salary');
|
|
44
|
+
expect(JSON.stringify(returned)).not.toContain(String(SECRET_SALARY));
|
|
45
|
+
expect(returned.ID).toBe('emp-1');
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('returns every value to a caller who is denied nothing', async () => {
|
|
49
|
+
const returned = await deleteAs(HRUser());
|
|
50
|
+
expect(returned.Salary).toBe(SECRET_SALARY);
|
|
51
|
+
});
|
|
52
|
+
});
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The entity-message relay in `ResolverBase.ListenForEntityMessages`.
|
|
3
|
+
*
|
|
4
|
+
* There is one listener per entity name per process, and it relays to the session that created
|
|
5
|
+
* it. So it must relay only events raised by an object that belongs to that session's user, and
|
|
6
|
+
* never the record values a payload can carry: a delete's payload is the whole deleted row.
|
|
7
|
+
*/
|
|
8
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
9
|
+
|
|
10
|
+
const { statusUpdates } = vi.hoisted(() => ({
|
|
11
|
+
statusUpdates: [] as Array<{ sessionId: string; ownerUserId: string; message: string | undefined }>,
|
|
12
|
+
}));
|
|
13
|
+
|
|
14
|
+
vi.mock('@memberjunction/sqlserver-dataprovider', () => ({ SQLServerDataProvider: class {} }));
|
|
15
|
+
vi.mock('@memberjunction/generic-database-provider', () => ({ UserCache: { Users: [] } }));
|
|
16
|
+
vi.mock('cloudevents', () => ({
|
|
17
|
+
CloudEvent: class {},
|
|
18
|
+
httpTransport: () => () => undefined,
|
|
19
|
+
emitterFor: () => () => undefined,
|
|
20
|
+
}));
|
|
21
|
+
vi.mock('type-graphql', () => {
|
|
22
|
+
const noop = () => () => undefined;
|
|
23
|
+
return {
|
|
24
|
+
Resolver: noop, Mutation: noop, Query: noop, Subscription: noop, Ctx: noop, Arg: noop, PubSub: noop,
|
|
25
|
+
Root: noop, ObjectType: noop, InputType: noop, Field: noop, FieldResolver: noop,
|
|
26
|
+
Int: undefined, Float: undefined, registerEnumType: () => undefined,
|
|
27
|
+
};
|
|
28
|
+
});
|
|
29
|
+
vi.mock('graphql', () => ({ GraphQLError: class extends Error {} }));
|
|
30
|
+
vi.mock('mssql', () => ({}));
|
|
31
|
+
vi.mock('@memberjunction/api-keys', () => ({ GetAPIKeyEngine: vi.fn() }));
|
|
32
|
+
vi.mock('@memberjunction/encryption', () => ({ EncryptionEngine: { Instance: {} } }));
|
|
33
|
+
vi.mock('@memberjunction/graphql-dataprovider', () => ({ FieldMapper: class { MapFieldName(n: string) { return n; } ReverseMapFieldName(n: string) { return n; } } }));
|
|
34
|
+
vi.mock('../generic/PubSubManager.js', () => ({ PubSubManager: { Instance: { Publish: () => undefined } } }));
|
|
35
|
+
vi.mock('../generic/PushStatusResolver.js', () => ({
|
|
36
|
+
PUSH_STATUS_UPDATES_TOPIC: 'test-push-topic',
|
|
37
|
+
PublishStatusUpdate: (_pubSub: unknown, update: { sessionId: string; ownerUserId: string; message: string | undefined }) => {
|
|
38
|
+
statusUpdates.push(update);
|
|
39
|
+
},
|
|
40
|
+
// Mirrors the real StatusUpdateOwnerKey/StatusUpdateOwnerID (#4931): the elevated principal's
|
|
41
|
+
// key when the payload stands in for one, else the request user's. Anonymous visitors key on
|
|
42
|
+
// `id|scope`; these relay cases are all named users, so the ID is the whole key.
|
|
43
|
+
StatusUpdateOwnerKey: (user: { ID?: string } | null | undefined) => user?.ID ?? '',
|
|
44
|
+
StatusUpdateOwnerID: (payload: { ElevatedFrom?: { ID?: string }; userRecord?: { ID?: string } } | undefined) =>
|
|
45
|
+
payload?.ElevatedFrom?.ID ?? payload?.userRecord?.ID ?? '',
|
|
46
|
+
}));
|
|
47
|
+
vi.mock('../generic/RunViewResolver.js', () => ({ RunViewByIDInput: class {}, RunViewByNameInput: class {}, RunDynamicViewInput: class {} }));
|
|
48
|
+
vi.mock('../generic/DeleteOptionsInput.js', () => ({ DeleteOptionsInput: class {} }));
|
|
49
|
+
vi.mock('../types.js', () => ({ RunViewGenericParams: class {} }));
|
|
50
|
+
vi.mock('@memberjunction/core-entities', () => ({}));
|
|
51
|
+
vi.mock('@memberjunction/core', async () => {
|
|
52
|
+
const actual = await vi.importActual<typeof import('@memberjunction/core')>('@memberjunction/core');
|
|
53
|
+
return { ...actual, LogError: vi.fn(), LogStatus: vi.fn(), LogDebug: vi.fn() };
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
import { BaseEntity, BaseEntityEvent, EntityInfo, UserInfo } from '@memberjunction/core';
|
|
57
|
+
import { MJEventType, MJGlobal } from '@memberjunction/global';
|
|
58
|
+
import type { PubSubEngine } from 'type-graphql';
|
|
59
|
+
import { Subscription } from 'rxjs';
|
|
60
|
+
import { ResolverBase } from '../generic/ResolverBase';
|
|
61
|
+
import type { UserPayload } from '../types.js';
|
|
62
|
+
import { BuildUser, EmployeeEntity, FullWidthEmployeeRow, HR_ROLE_ID, SECRET_SALARY } from './fieldSecurityFixtures.js';
|
|
63
|
+
|
|
64
|
+
const SUBSCRIPTIONS_KEY = '___MJServer___ResolverBase___EventSubscriptions';
|
|
65
|
+
|
|
66
|
+
/** An entity object of `entityInfo` loaded by `owner`, holding the full row. */
|
|
67
|
+
function entityOwnedBy(entityInfo: EntityInfo, owner: UserInfo) {
|
|
68
|
+
const row = FullWidthEmployeeRow();
|
|
69
|
+
return {
|
|
70
|
+
EntityInfo: entityInfo,
|
|
71
|
+
ContextCurrentUser: owner,
|
|
72
|
+
GetAll: () => ({ ...row }),
|
|
73
|
+
PrimaryKey: { KeyValuePairs: [{ FieldName: 'ID', Value: row.ID }] },
|
|
74
|
+
Fields: entityInfo.Fields.map(f => ({ Name: f.Name, Dirty: false })),
|
|
75
|
+
Get: (name: string) => (row as Record<string, unknown>)[name],
|
|
76
|
+
GetDirtyFieldNames: (): string[] => [],
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
type OwnedEntity = ReturnType<typeof entityOwnedBy>;
|
|
80
|
+
|
|
81
|
+
class TestResolver extends ResolverBase {
|
|
82
|
+
public Listen(entity: OwnedEntity, sessionUser: UserInfo) {
|
|
83
|
+
this.ListenForEntityMessages(entity as unknown as BaseEntity, {} as PubSubEngine, { sessionId: 'session-a', userRecord: sessionUser } as UserPayload);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function raise(entity: OwnedEntity, type: BaseEntityEvent['type'], payload: unknown): void {
|
|
88
|
+
const args = new BaseEntityEvent();
|
|
89
|
+
args.type = type;
|
|
90
|
+
args.payload = payload;
|
|
91
|
+
args.baseEntity = entity as unknown as BaseEntity;
|
|
92
|
+
MJGlobal.Instance.RaiseEvent({ component: null, event: MJEventType.ComponentEvent, eventCode: BaseEntity.BaseEventCode, args });
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** Lets the async listener run for every event raised so far. */
|
|
96
|
+
async function settle(): Promise<void> {
|
|
97
|
+
await new Promise(resolve => setTimeout(resolve, 10));
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function resetListeners(): void {
|
|
101
|
+
const store = MJGlobal.Instance.GetGlobalObjectStore();
|
|
102
|
+
const subscriptions = store[SUBSCRIPTIONS_KEY] as Map<string, Subscription> | undefined;
|
|
103
|
+
subscriptions?.forEach(s => s.unsubscribe());
|
|
104
|
+
subscriptions?.clear();
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
let userA: UserInfo;
|
|
108
|
+
let userB: UserInfo;
|
|
109
|
+
|
|
110
|
+
beforeEach(() => {
|
|
111
|
+
statusUpdates.length = 0;
|
|
112
|
+
resetListeners();
|
|
113
|
+
userA = BuildUser([HR_ROLE_ID], 'user-a');
|
|
114
|
+
userB = BuildUser([HR_ROLE_ID], 'user-b');
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
describe('ResolverBase.ListenForEntityMessages: relay to the listening session', () => {
|
|
118
|
+
it("REGRESSION: never relays another user's delete to the session that created the listener", async () => {
|
|
119
|
+
const entityInfo = EmployeeEntity(false);
|
|
120
|
+
new TestResolver().Listen(entityOwnedBy(entityInfo, userA), userA);
|
|
121
|
+
|
|
122
|
+
raise(entityOwnedBy(entityInfo, userB), 'delete', { OldValues: FullWidthEmployeeRow() });
|
|
123
|
+
await settle();
|
|
124
|
+
|
|
125
|
+
expect(statusUpdates).toHaveLength(0);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('REGRESSION: relays the session user\'s own delete with the key and type, never the deleted row', async () => {
|
|
129
|
+
const entityInfo = EmployeeEntity(false);
|
|
130
|
+
new TestResolver().Listen(entityOwnedBy(entityInfo, userA), userA);
|
|
131
|
+
|
|
132
|
+
raise(entityOwnedBy(entityInfo, userA), 'delete', { OldValues: FullWidthEmployeeRow() });
|
|
133
|
+
await settle();
|
|
134
|
+
|
|
135
|
+
expect(statusUpdates).toHaveLength(1);
|
|
136
|
+
const message = JSON.parse(statusUpdates[0].message!) as Record<string, unknown>;
|
|
137
|
+
expect(message.entityName).toBe('Employees');
|
|
138
|
+
expect(message.eventType).toBe('delete');
|
|
139
|
+
expect(message.message).toBeUndefined();
|
|
140
|
+
expect(statusUpdates[0].message).not.toContain(String(SECRET_SALARY));
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it('REGRESSION: an entity under field security never relays a payload, even to its own user', async () => {
|
|
144
|
+
const entityInfo = EmployeeEntity(true);
|
|
145
|
+
new TestResolver().Listen(entityOwnedBy(entityInfo, userA), userA);
|
|
146
|
+
|
|
147
|
+
raise(entityOwnedBy(entityInfo, userA), 'other', { Salary: SECRET_SALARY });
|
|
148
|
+
await settle();
|
|
149
|
+
|
|
150
|
+
expect(statusUpdates).toHaveLength(1);
|
|
151
|
+
expect(JSON.parse(statusUpdates[0].message!).message).toBeUndefined();
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it('REGRESSION: MJ: Record Changes never relays a payload', async () => {
|
|
155
|
+
const recordChanges = new EntityInfo({ ID: 'rc', Name: 'MJ: Record Changes', SchemaName: '__mj', BaseTable: 'RecordChange', BaseView: 'vwRecordChanges', Fields: [] });
|
|
156
|
+
new TestResolver().Listen(entityOwnedBy(recordChanges, userA), userA);
|
|
157
|
+
|
|
158
|
+
raise(entityOwnedBy(recordChanges, userA), 'other', { ChangesJSON: `{"Salary":${SECRET_SALARY}}` });
|
|
159
|
+
await settle();
|
|
160
|
+
|
|
161
|
+
expect(statusUpdates).toHaveLength(1);
|
|
162
|
+
expect(statusUpdates[0].message).not.toContain(String(SECRET_SALARY));
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it("still relays a progress message from the session user's own object", async () => {
|
|
166
|
+
const entityInfo = EmployeeEntity(false);
|
|
167
|
+
new TestResolver().Listen(entityOwnedBy(entityInfo, userA), userA);
|
|
168
|
+
|
|
169
|
+
raise(entityOwnedBy(entityInfo, userA), 'other', 'step 2 of 5');
|
|
170
|
+
await settle();
|
|
171
|
+
|
|
172
|
+
expect(statusUpdates).toHaveLength(1);
|
|
173
|
+
expect(JSON.parse(statusUpdates[0].message!).message).toBe('step 2 of 5');
|
|
174
|
+
expect(statusUpdates[0].ownerUserId).toBe('user-a');
|
|
175
|
+
});
|
|
176
|
+
});
|
|
@@ -158,7 +158,7 @@ describe('IncludeBinaryFields forwarding — batch resolver path', () => {
|
|
|
158
158
|
it('RunViewsGeneric → RunViewsGenericInternal forwards the per-view flag to provider.RunViews', async () => {
|
|
159
159
|
const { provider, runViewsCalls } = recordingProvider();
|
|
160
160
|
const inputs = [
|
|
161
|
-
{ EntityName: ENTITY_NAME, ExtraFilter: '', OrderBy: '', IncludeBinaryFields: true },
|
|
161
|
+
{ EntityName: ENTITY_NAME, ExtraFilter: '', OrderBy: '', IncludeBinaryFields: true, AlternateBaseView: 'Active Vectors' },
|
|
162
162
|
{ EntityName: ENTITY_NAME, ExtraFilter: '', OrderBy: '' },
|
|
163
163
|
] as (RunViewByNameInput & RunViewByIDInput & RunDynamicViewInput)[];
|
|
164
164
|
|
|
@@ -167,7 +167,29 @@ describe('IncludeBinaryFields forwarding — batch resolver path', () => {
|
|
|
167
167
|
expect(runViewsCalls).toHaveLength(1);
|
|
168
168
|
expect(runViewsCalls[0]).toHaveLength(2);
|
|
169
169
|
expect(runViewsCalls[0][0].IncludeBinaryFields).toBe(true);
|
|
170
|
+
expect(runViewsCalls[0][0].AlternateBaseView).toBe('Active Vectors');
|
|
170
171
|
expect(runViewsCalls[0][1].IncludeBinaryFields).toBeUndefined();
|
|
172
|
+
expect(runViewsCalls[0][1].AlternateBaseView).toBeUndefined();
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it('RunDynamicViewGeneric, RunViewByNameGeneric, and RunViewByIDGeneric forward AlternateBaseView to provider.RunView', async () => {
|
|
176
|
+
const { provider, runViewCalls } = recordingProvider();
|
|
177
|
+
const probe = new Probe();
|
|
178
|
+
|
|
179
|
+
await probe.RunDynamicViewGeneric(
|
|
180
|
+
{ EntityName: ENTITY_NAME, ExtraFilter: '', OrderBy: '', AlternateBaseView: 'Active Vectors' } as RunDynamicViewInput,
|
|
181
|
+
provider, fakePayload(), undefined as never,
|
|
182
|
+
);
|
|
183
|
+
await probe.RunViewByNameGeneric(
|
|
184
|
+
{ ViewName: 'Saved View', ExtraFilter: '', OrderBy: '', AlternateBaseView: '' } as RunViewByNameInput,
|
|
185
|
+
provider, fakePayload(), undefined as never,
|
|
186
|
+
);
|
|
187
|
+
await probe.RunViewByIDGeneric(
|
|
188
|
+
{ ViewID: 'view-1', ExtraFilter: '', OrderBy: '', AlternateBaseView: 'Draft Vectors' } as RunViewByIDInput,
|
|
189
|
+
provider, fakePayload(), undefined as never,
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
expect(runViewCalls.map(c => c.AlternateBaseView)).toEqual(['Active Vectors', '', 'Draft Vectors']);
|
|
171
193
|
});
|
|
172
194
|
});
|
|
173
195
|
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
// ResolverBase transitively pulls in type-graphql decorators, which need the
|
|
2
|
+
// Reflect.metadata polyfill at import time.
|
|
3
|
+
import 'reflect-metadata';
|
|
4
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
5
|
+
import type { PubSubEngine } from 'type-graphql';
|
|
6
|
+
import { UserInfo } from '@memberjunction/core';
|
|
7
|
+
import { ResolverBase } from '../generic/ResolverBase.js';
|
|
8
|
+
import { PUSH_STATUS_UPDATES_TOPIC, StatusUpdateOwnerID } from '../generic/PushStatusResolver.js';
|
|
9
|
+
import type { UserPayload } from '../types.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* `ResolverBase.PublishStatusUpdate` stamps the owner the caller's socket is authenticated as. For a widget guest
|
|
13
|
+
* whose run is elevated to the system user, that is the guest's owner key: a push stamped with the elevated user's ID
|
|
14
|
+
* never passes the subscription filter on the guest's socket.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const ANONYMOUS_USER_ID = 'AAAAAAAA-0000-4000-8000-0000000000A0';
|
|
18
|
+
const SYSTEM_USER_ID = 'BBBBBBBB-0000-4000-8000-0000000000B0';
|
|
19
|
+
|
|
20
|
+
/** Reaches the protected publish wrapper. */
|
|
21
|
+
class Probe extends ResolverBase {
|
|
22
|
+
public Publish(pubSub: PubSubEngine, userPayload: UserPayload): void {
|
|
23
|
+
this.PublishStatusUpdate(pubSub, 'session-1', 'progress', userPayload);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function elevatedGuestPayload(): UserPayload {
|
|
28
|
+
const guest = new UserInfo(null, { ID: ANONYMOUS_USER_ID, Email: 'anonymous@magic-link.local' });
|
|
29
|
+
guest.IsMagicLinkAnonymous = true;
|
|
30
|
+
guest.AnonymousVisitorID = 'sid-guest-1';
|
|
31
|
+
return {
|
|
32
|
+
sessionId: 'session-1',
|
|
33
|
+
email: 'system@example.com',
|
|
34
|
+
userRecord: new UserInfo(null, { ID: SYSTEM_USER_ID, Email: 'system@example.com' }),
|
|
35
|
+
ElevatedFrom: guest,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
describe('ResolverBase.PublishStatusUpdate — the owner it stamps', () => {
|
|
40
|
+
it("stamps an elevated widget guest's own owner key, not the system user it was elevated to", () => {
|
|
41
|
+
const publish = vi.fn().mockResolvedValue(undefined);
|
|
42
|
+
const pubSub: PubSubEngine = { publish, subscribe: vi.fn(), unsubscribe: vi.fn(), asyncIterator: vi.fn() };
|
|
43
|
+
const userPayload = elevatedGuestPayload();
|
|
44
|
+
|
|
45
|
+
new Probe().Publish(pubSub, userPayload);
|
|
46
|
+
|
|
47
|
+
expect(publish).toHaveBeenCalledOnce();
|
|
48
|
+
expect(publish.mock.calls[0][0]).toBe(PUSH_STATUS_UPDATES_TOPIC);
|
|
49
|
+
const payload = publish.mock.calls[0][1] as { sessionId: string; ownerUserId: string; message: string };
|
|
50
|
+
expect(payload).toMatchObject({ sessionId: 'session-1', message: 'progress' });
|
|
51
|
+
expect(payload.ownerUserId).toBe(StatusUpdateOwnerID(userPayload));
|
|
52
|
+
expect(payload.ownerUserId).toBe(`${ANONYMOUS_USER_ID.toLowerCase()}|sid-guest-1`);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('stamps a signed-in user with their user ID', () => {
|
|
56
|
+
const publish = vi.fn().mockResolvedValue(undefined);
|
|
57
|
+
const pubSub: PubSubEngine = { publish, subscribe: vi.fn(), unsubscribe: vi.fn(), asyncIterator: vi.fn() };
|
|
58
|
+
const userPayload: UserPayload = {
|
|
59
|
+
sessionId: 'session-1',
|
|
60
|
+
email: 'user@example.com',
|
|
61
|
+
userRecord: new UserInfo(null, { ID: 'CCCCCCCC-0000-4000-8000-0000000000C0', Email: 'user@example.com' }),
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
new Probe().Publish(pubSub, userPayload);
|
|
65
|
+
|
|
66
|
+
expect((publish.mock.calls[0][1] as { ownerUserId: string }).ownerUserId).toBe('CCCCCCCC-0000-4000-8000-0000000000C0');
|
|
67
|
+
});
|
|
68
|
+
});
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
import { describe, it, expect, vi, afterEach } from 'vitest';
|
|
3
|
+
import { UserViewEngine } from '@memberjunction/core-entities';
|
|
4
|
+
import type { MJUserViewEntityExtended } from '@memberjunction/core-entities';
|
|
5
|
+
import { RunView } from '@memberjunction/core';
|
|
6
|
+
import type { IMetadataProvider, IRunViewProvider, RunViewParams, UserInfo } from '@memberjunction/core';
|
|
7
|
+
import { ResolverBase } from '../generic/ResolverBase.js';
|
|
8
|
+
import { RunViewResolver, RunViewByNameInput, RunViewByIDInput } from '../generic/RunViewResolver.js';
|
|
9
|
+
|
|
10
|
+
class UserViewProbe extends ResolverBase {
|
|
11
|
+
public async TestRunViewByID(
|
|
12
|
+
viewInput: RunViewByIDInput,
|
|
13
|
+
provider: IMetadataProvider,
|
|
14
|
+
userPayload: { userRecord: UserInfo; email: string }
|
|
15
|
+
) {
|
|
16
|
+
return this.RunViewByIDGeneric(viewInput, provider, userPayload as never);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
public async TestRunViewByName(
|
|
20
|
+
viewInput: RunViewByNameInput,
|
|
21
|
+
provider: IMetadataProvider,
|
|
22
|
+
userPayload: { userRecord: UserInfo; email: string }
|
|
23
|
+
) {
|
|
24
|
+
return this.RunViewByNameGeneric(viewInput, provider, userPayload as never);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
describe('ResolverBase & RunViewResolver UserViewEngine cache and ViewEntity forwarding (MJ-CORE-9)', () => {
|
|
29
|
+
afterEach(() => {
|
|
30
|
+
vi.restoreAllMocks();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('resolves MJ: User Views from UserViewEngine cache first, forwards ViewEntity into RunViewParams, and reuses _resolvedViewEntity in RunViewResolver', async () => {
|
|
34
|
+
const mockViewEntity = {
|
|
35
|
+
ID: 'view-123',
|
|
36
|
+
Name: 'Active Widgets',
|
|
37
|
+
EntityID: 'entity-widgets',
|
|
38
|
+
Entity: 'Widgets',
|
|
39
|
+
WhereClause: 'Status = 1',
|
|
40
|
+
GridState: null,
|
|
41
|
+
SortState: null,
|
|
42
|
+
} as unknown as MJUserViewEntityExtended;
|
|
43
|
+
|
|
44
|
+
vi.spyOn(UserViewEngine.Instance, 'Loaded', 'get').mockReturnValue(true);
|
|
45
|
+
const getViewByIdSpy = vi.spyOn(UserViewEngine.Instance, 'GetViewById').mockReturnValue(mockViewEntity);
|
|
46
|
+
const getViewByNameSpy = vi.spyOn(UserViewEngine.Instance, 'GetViewByName').mockReturnValue(mockViewEntity);
|
|
47
|
+
|
|
48
|
+
const capturedRunViewParams: RunViewParams[] = [];
|
|
49
|
+
const providerRunViewSpy = vi.fn().mockImplementation(async (params: RunViewParams) => {
|
|
50
|
+
capturedRunViewParams.push(params);
|
|
51
|
+
return {
|
|
52
|
+
Success: true,
|
|
53
|
+
Results: [{ ID: 'w-1', Name: 'Widget A' }],
|
|
54
|
+
RowCount: 1,
|
|
55
|
+
TotalRowCount: 1,
|
|
56
|
+
ExecutionTime: 2,
|
|
57
|
+
ErrorMessage: '',
|
|
58
|
+
UserViewRunID: '',
|
|
59
|
+
};
|
|
60
|
+
});
|
|
61
|
+
const fakeProvider = {
|
|
62
|
+
Entities: [
|
|
63
|
+
{
|
|
64
|
+
ID: 'entity-widgets',
|
|
65
|
+
Name: 'Widgets',
|
|
66
|
+
PrimaryKeys: [{ Name: 'ID', CodeName: 'ID' }],
|
|
67
|
+
Fields: [
|
|
68
|
+
{ Name: 'ID', CodeName: 'ID', AllowInView: true, DefaultInView: true, IsBinaryFieldType: false },
|
|
69
|
+
{ Name: 'Name', CodeName: 'Name', AllowInView: true, DefaultInView: true, IsBinaryFieldType: false },
|
|
70
|
+
],
|
|
71
|
+
EncryptedFields: [],
|
|
72
|
+
},
|
|
73
|
+
],
|
|
74
|
+
EntityByName: () => ({
|
|
75
|
+
ID: 'entity-widgets',
|
|
76
|
+
Name: 'Widgets',
|
|
77
|
+
PrimaryKeys: [{ Name: 'ID', CodeName: 'ID' }],
|
|
78
|
+
Fields: [
|
|
79
|
+
{ Name: 'ID', CodeName: 'ID', AllowInView: true, DefaultInView: true, IsBinaryFieldType: false },
|
|
80
|
+
{ Name: 'Name', CodeName: 'Name', AllowInView: true, DefaultInView: true, IsBinaryFieldType: false },
|
|
81
|
+
],
|
|
82
|
+
EncryptedFields: [],
|
|
83
|
+
}),
|
|
84
|
+
EntityByID: () => ({
|
|
85
|
+
ID: 'entity-widgets',
|
|
86
|
+
Name: 'Widgets',
|
|
87
|
+
PrimaryKeys: [{ Name: 'ID', CodeName: 'ID' }],
|
|
88
|
+
Fields: [
|
|
89
|
+
{ Name: 'ID', CodeName: 'ID', AllowInView: true, DefaultInView: true, IsBinaryFieldType: false },
|
|
90
|
+
{ Name: 'Name', CodeName: 'Name', AllowInView: true, DefaultInView: true, IsBinaryFieldType: false },
|
|
91
|
+
],
|
|
92
|
+
EncryptedFields: [],
|
|
93
|
+
}),
|
|
94
|
+
RunView: providerRunViewSpy,
|
|
95
|
+
} as unknown as IMetadataProvider & IRunViewProvider;
|
|
96
|
+
|
|
97
|
+
const userPayload = {
|
|
98
|
+
email: 'tester@example.com',
|
|
99
|
+
userRecord: { ID: 'u-1', Email: 'tester@example.com' } as unknown as UserInfo,
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const resolver = new RunViewResolver();
|
|
103
|
+
const findBySpy = vi.spyOn(
|
|
104
|
+
resolver as unknown as { findBy: (...args: unknown[]) => Promise<unknown> },
|
|
105
|
+
'findBy'
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
const byIdResult = await resolver.RunViewByID(
|
|
109
|
+
{ ViewID: 'view-123' } as RunViewByIDInput,
|
|
110
|
+
{ providers: [{ provider: fakeProvider, type: 'Read-Only' }], userPayload } as never
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
expect(getViewByIdSpy).toHaveBeenCalledWith('view-123');
|
|
114
|
+
expect(findBySpy).not.toHaveBeenCalled();
|
|
115
|
+
expect(capturedRunViewParams).toHaveLength(1);
|
|
116
|
+
expect(capturedRunViewParams[0].ViewEntity).toBe(mockViewEntity);
|
|
117
|
+
expect(byIdResult?.Results).toHaveLength(1);
|
|
118
|
+
|
|
119
|
+
const byNameResult = await resolver.RunViewByName(
|
|
120
|
+
{ ViewName: 'Active Widgets' } as RunViewByNameInput,
|
|
121
|
+
{ providers: [{ provider: fakeProvider, type: 'Read-Only' }], userPayload } as never
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
expect(getViewByNameSpy).toHaveBeenCalledWith('Active Widgets');
|
|
125
|
+
expect(findBySpy).not.toHaveBeenCalled();
|
|
126
|
+
expect(capturedRunViewParams).toHaveLength(2);
|
|
127
|
+
expect(capturedRunViewParams[1].ViewEntity).toBe(mockViewEntity);
|
|
128
|
+
expect(byNameResult?.Results).toHaveLength(1);
|
|
129
|
+
});
|
|
130
|
+
});
|