@memberjunction/server 6.2.0-edge.3 → 6.2.0-edge.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (346) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/agentSessions/BridgeHostInstance.d.ts +25 -0
  3. package/dist/agentSessions/BridgeHostInstance.d.ts.map +1 -0
  4. package/dist/agentSessions/BridgeHostInstance.js +30 -0
  5. package/dist/agentSessions/BridgeHostInstance.js.map +1 -0
  6. package/dist/agentSessions/HostInstance.d.ts +75 -11
  7. package/dist/agentSessions/HostInstance.d.ts.map +1 -1
  8. package/dist/agentSessions/HostInstance.js +99 -28
  9. package/dist/agentSessions/HostInstance.js.map +1 -1
  10. package/dist/agentSessions/ReturningVisitorRecap.d.ts.map +1 -1
  11. package/dist/agentSessions/ReturningVisitorRecap.js +7 -3
  12. package/dist/agentSessions/ReturningVisitorRecap.js.map +1 -1
  13. package/dist/agentSessions/SessionJanitor.d.ts +173 -25
  14. package/dist/agentSessions/SessionJanitor.d.ts.map +1 -1
  15. package/dist/agentSessions/SessionJanitor.js +452 -54
  16. package/dist/agentSessions/SessionJanitor.js.map +1 -1
  17. package/dist/agentSessions/SessionManager.d.ts +43 -2
  18. package/dist/agentSessions/SessionManager.d.ts.map +1 -1
  19. package/dist/agentSessions/SessionManager.js +99 -8
  20. package/dist/agentSessions/SessionManager.js.map +1 -1
  21. package/dist/agentSessions/SessionRunIDVerifier.d.ts +63 -0
  22. package/dist/agentSessions/SessionRunIDVerifier.d.ts.map +1 -0
  23. package/dist/agentSessions/SessionRunIDVerifier.js +129 -0
  24. package/dist/agentSessions/SessionRunIDVerifier.js.map +1 -0
  25. package/dist/agentSessions/index.d.ts +2 -0
  26. package/dist/agentSessions/index.d.ts.map +1 -1
  27. package/dist/agentSessions/index.js +10 -3
  28. package/dist/agentSessions/index.js.map +1 -1
  29. package/dist/auth/actionAuthorization.d.ts +40 -0
  30. package/dist/auth/actionAuthorization.d.ts.map +1 -0
  31. package/dist/auth/actionAuthorization.js +94 -0
  32. package/dist/auth/actionAuthorization.js.map +1 -0
  33. package/dist/auth/index.d.ts.map +1 -1
  34. package/dist/auth/index.js +3 -10
  35. package/dist/auth/index.js.map +1 -1
  36. package/dist/auth/magicLink/MagicLinkService.d.ts.map +1 -1
  37. package/dist/auth/magicLink/MagicLinkService.js +6 -3
  38. package/dist/auth/magicLink/MagicLinkService.js.map +1 -1
  39. package/dist/auth/newUsers.d.ts +10 -0
  40. package/dist/auth/newUsers.d.ts.map +1 -1
  41. package/dist/auth/newUsers.js +80 -11
  42. package/dist/auth/newUsers.js.map +1 -1
  43. package/dist/config.d.ts +2378 -0
  44. package/dist/config.d.ts.map +1 -1
  45. package/dist/config.js +123 -0
  46. package/dist/config.js.map +1 -1
  47. package/dist/context.d.ts +8 -0
  48. package/dist/context.d.ts.map +1 -1
  49. package/dist/context.js +14 -11
  50. package/dist/context.js.map +1 -1
  51. package/dist/generated/graphql-schemas/__mj.d.ts +135 -0
  52. package/dist/generated/graphql-schemas/__mj.d.ts.map +1 -1
  53. package/dist/generated/graphql-schemas/__mj.js +706 -0
  54. package/dist/generated/graphql-schemas/__mj.js.map +1 -1
  55. package/dist/generic/OrphanedConversationDetailReconciler.js +3 -3
  56. package/dist/generic/OrphanedConversationDetailReconciler.js.map +1 -1
  57. package/dist/generic/ResolverBase.d.ts +20 -4
  58. package/dist/generic/ResolverBase.d.ts.map +1 -1
  59. package/dist/generic/ResolverBase.js +39 -10
  60. package/dist/generic/ResolverBase.js.map +1 -1
  61. package/dist/generic/RunViewResolver.d.ts.map +1 -1
  62. package/dist/generic/RunViewResolver.js +4 -0
  63. package/dist/generic/RunViewResolver.js.map +1 -1
  64. package/dist/index.d.ts +6 -0
  65. package/dist/index.d.ts.map +1 -1
  66. package/dist/index.js +120 -131
  67. package/dist/index.js.map +1 -1
  68. package/dist/isolatedProvider.d.ts +20 -0
  69. package/dist/isolatedProvider.d.ts.map +1 -0
  70. package/dist/isolatedProvider.js +22 -0
  71. package/dist/isolatedProvider.js.map +1 -0
  72. package/dist/realtimeProxy/RealtimeProxyServer.d.ts +44 -4
  73. package/dist/realtimeProxy/RealtimeProxyServer.d.ts.map +1 -1
  74. package/dist/realtimeProxy/RealtimeProxyServer.js +121 -16
  75. package/dist/realtimeProxy/RealtimeProxyServer.js.map +1 -1
  76. package/dist/realtimeProxy/RealtimeRelayTunnel.d.ts +171 -0
  77. package/dist/realtimeProxy/RealtimeRelayTunnel.d.ts.map +1 -0
  78. package/dist/realtimeProxy/RealtimeRelayTunnel.js +532 -0
  79. package/dist/realtimeProxy/RealtimeRelayTunnel.js.map +1 -0
  80. package/dist/realtimeSessions/RealtimeSessionEventService.d.ts +148 -0
  81. package/dist/realtimeSessions/RealtimeSessionEventService.d.ts.map +1 -0
  82. package/dist/realtimeSessions/RealtimeSessionEventService.js +191 -0
  83. package/dist/realtimeSessions/RealtimeSessionEventService.js.map +1 -0
  84. package/dist/realtimeSessions/RealtimeSessionVerificationService.d.ts +66 -0
  85. package/dist/realtimeSessions/RealtimeSessionVerificationService.d.ts.map +1 -0
  86. package/dist/realtimeSessions/RealtimeSessionVerificationService.js +138 -0
  87. package/dist/realtimeSessions/RealtimeSessionVerificationService.js.map +1 -0
  88. package/dist/realtimeSessions/RealtimeVerifyRouter.d.ts +38 -0
  89. package/dist/realtimeSessions/RealtimeVerifyRouter.d.ts.map +1 -0
  90. package/dist/realtimeSessions/RealtimeVerifyRouter.js +98 -0
  91. package/dist/realtimeSessions/RealtimeVerifyRouter.js.map +1 -0
  92. package/dist/realtimeSessions/SessionVerificationPortsBase.d.ts +61 -0
  93. package/dist/realtimeSessions/SessionVerificationPortsBase.d.ts.map +1 -0
  94. package/dist/realtimeSessions/SessionVerificationPortsBase.js +149 -0
  95. package/dist/realtimeSessions/SessionVerificationPortsBase.js.map +1 -0
  96. package/dist/realtimeSessions/SlidingWindowRateLimiter.d.ts +57 -0
  97. package/dist/realtimeSessions/SlidingWindowRateLimiter.d.ts.map +1 -0
  98. package/dist/realtimeSessions/SlidingWindowRateLimiter.js +69 -0
  99. package/dist/realtimeSessions/SlidingWindowRateLimiter.js.map +1 -0
  100. package/dist/realtimeSessions/index.d.ts +17 -0
  101. package/dist/realtimeSessions/index.d.ts.map +1 -0
  102. package/dist/realtimeSessions/index.js +17 -0
  103. package/dist/realtimeSessions/index.js.map +1 -0
  104. package/dist/realtimeSessions/sessionAccess.d.ts +86 -0
  105. package/dist/realtimeSessions/sessionAccess.d.ts.map +1 -0
  106. package/dist/realtimeSessions/sessionAccess.js +90 -0
  107. package/dist/realtimeSessions/sessionAccess.js.map +1 -0
  108. package/dist/realtimeSessions/sessionConfigStamps.d.ts +70 -0
  109. package/dist/realtimeSessions/sessionConfigStamps.d.ts.map +1 -0
  110. package/dist/realtimeSessions/sessionConfigStamps.js +93 -0
  111. package/dist/realtimeSessions/sessionConfigStamps.js.map +1 -0
  112. package/dist/realtimeSessions/subscriptionAuthorization.d.ts +65 -0
  113. package/dist/realtimeSessions/subscriptionAuthorization.d.ts.map +1 -0
  114. package/dist/realtimeSessions/subscriptionAuthorization.js +71 -0
  115. package/dist/realtimeSessions/subscriptionAuthorization.js.map +1 -0
  116. package/dist/realtimeSessions/verificationCore.d.ts +243 -0
  117. package/dist/realtimeSessions/verificationCore.d.ts.map +1 -0
  118. package/dist/realtimeSessions/verificationCore.js +356 -0
  119. package/dist/realtimeSessions/verificationCore.js.map +1 -0
  120. package/dist/realtimeSessions/verificationPages.d.ts +66 -0
  121. package/dist/realtimeSessions/verificationPages.d.ts.map +1 -0
  122. package/dist/realtimeSessions/verificationPages.js +110 -0
  123. package/dist/realtimeSessions/verificationPages.js.map +1 -0
  124. package/dist/realtimeSessions/verificationPolicy.d.ts +147 -0
  125. package/dist/realtimeSessions/verificationPolicy.d.ts.map +1 -0
  126. package/dist/realtimeSessions/verificationPolicy.js +249 -0
  127. package/dist/realtimeSessions/verificationPolicy.js.map +1 -0
  128. package/dist/realtimeSessions/verificationWorkflow.d.ts +263 -0
  129. package/dist/realtimeSessions/verificationWorkflow.d.ts.map +1 -0
  130. package/dist/realtimeSessions/verificationWorkflow.js +435 -0
  131. package/dist/realtimeSessions/verificationWorkflow.js.map +1 -0
  132. package/dist/realtimeWidget/widgetGuestElevation.d.ts +20 -2
  133. package/dist/realtimeWidget/widgetGuestElevation.d.ts.map +1 -1
  134. package/dist/realtimeWidget/widgetGuestElevation.js +31 -2
  135. package/dist/realtimeWidget/widgetGuestElevation.js.map +1 -1
  136. package/dist/resolvers/ActionResolver.d.ts +31 -5
  137. package/dist/resolvers/ActionResolver.d.ts.map +1 -1
  138. package/dist/resolvers/ActionResolver.js +75 -14
  139. package/dist/resolvers/ActionResolver.js.map +1 -1
  140. package/dist/resolvers/AdhocQueryResolver.d.ts +14 -1
  141. package/dist/resolvers/AdhocQueryResolver.d.ts.map +1 -1
  142. package/dist/resolvers/AdhocQueryResolver.js +21 -5
  143. package/dist/resolvers/AdhocQueryResolver.js.map +1 -1
  144. package/dist/resolvers/CacheStatsResolver.d.ts +27 -0
  145. package/dist/resolvers/CacheStatsResolver.d.ts.map +1 -1
  146. package/dist/resolvers/CacheStatsResolver.js +113 -2
  147. package/dist/resolvers/CacheStatsResolver.js.map +1 -1
  148. package/dist/resolvers/ClientToolRequestResolver.d.ts +4 -2
  149. package/dist/resolvers/ClientToolRequestResolver.d.ts.map +1 -1
  150. package/dist/resolvers/ClientToolRequestResolver.js +28 -10
  151. package/dist/resolvers/ClientToolRequestResolver.js.map +1 -1
  152. package/dist/resolvers/FetchEntityVectorsResolver.d.ts +12 -1
  153. package/dist/resolvers/FetchEntityVectorsResolver.d.ts.map +1 -1
  154. package/dist/resolvers/FetchEntityVectorsResolver.js +83 -2
  155. package/dist/resolvers/FetchEntityVectorsResolver.js.map +1 -1
  156. package/dist/resolvers/HumanHandoffResolver.d.ts.map +1 -1
  157. package/dist/resolvers/HumanHandoffResolver.js +7 -1
  158. package/dist/resolvers/HumanHandoffResolver.js.map +1 -1
  159. package/dist/resolvers/MCPResolver.d.ts.map +1 -1
  160. package/dist/resolvers/MCPResolver.js +9 -4
  161. package/dist/resolvers/MCPResolver.js.map +1 -1
  162. package/dist/resolvers/RealtimeBridgeResolver.d.ts +54 -2
  163. package/dist/resolvers/RealtimeBridgeResolver.d.ts.map +1 -1
  164. package/dist/resolvers/RealtimeBridgeResolver.js +155 -10
  165. package/dist/resolvers/RealtimeBridgeResolver.js.map +1 -1
  166. package/dist/resolvers/RealtimeClientSessionResolver.d.ts +155 -25
  167. package/dist/resolvers/RealtimeClientSessionResolver.d.ts.map +1 -1
  168. package/dist/resolvers/RealtimeClientSessionResolver.js +394 -90
  169. package/dist/resolvers/RealtimeClientSessionResolver.js.map +1 -1
  170. package/dist/resolvers/RealtimeSessionEventsResolver.d.ts +61 -0
  171. package/dist/resolvers/RealtimeSessionEventsResolver.d.ts.map +1 -0
  172. package/dist/resolvers/RealtimeSessionEventsResolver.js +130 -0
  173. package/dist/resolvers/RealtimeSessionEventsResolver.js.map +1 -0
  174. package/dist/resolvers/RealtimeSessionVerificationResolver.d.ts +85 -0
  175. package/dist/resolvers/RealtimeSessionVerificationResolver.d.ts.map +1 -0
  176. package/dist/resolvers/RealtimeSessionVerificationResolver.js +188 -0
  177. package/dist/resolvers/RealtimeSessionVerificationResolver.js.map +1 -0
  178. package/dist/resolvers/RemoteBrowserActionResolver.d.ts +2 -0
  179. package/dist/resolvers/RemoteBrowserActionResolver.d.ts.map +1 -1
  180. package/dist/resolvers/RemoteBrowserActionResolver.js +5 -1
  181. package/dist/resolvers/RemoteBrowserActionResolver.js.map +1 -1
  182. package/dist/resolvers/RunAIAgentResolver.d.ts +10 -0
  183. package/dist/resolvers/RunAIAgentResolver.d.ts.map +1 -1
  184. package/dist/resolvers/RunAIAgentResolver.js +68 -11
  185. package/dist/resolvers/RunAIAgentResolver.js.map +1 -1
  186. package/dist/resolvers/RunTemplateResolver.d.ts.map +1 -1
  187. package/dist/resolvers/RunTemplateResolver.js +12 -11
  188. package/dist/resolvers/RunTemplateResolver.js.map +1 -1
  189. package/dist/resolvers/SyncDataResolver.d.ts.map +1 -1
  190. package/dist/resolvers/SyncDataResolver.js +6 -2
  191. package/dist/resolvers/SyncDataResolver.js.map +1 -1
  192. package/dist/resolvers/SyncRolesUsersResolver.d.ts +2 -0
  193. package/dist/resolvers/SyncRolesUsersResolver.d.ts.map +1 -1
  194. package/dist/resolvers/SyncRolesUsersResolver.js +23 -6
  195. package/dist/resolvers/SyncRolesUsersResolver.js.map +1 -1
  196. package/dist/resolvers/UserAvatarResolver.d.ts +16 -0
  197. package/dist/resolvers/UserAvatarResolver.d.ts.map +1 -0
  198. package/dist/resolvers/UserAvatarResolver.js +124 -0
  199. package/dist/resolvers/UserAvatarResolver.js.map +1 -0
  200. package/dist/resolvers/UserResolver.d.ts.map +1 -1
  201. package/dist/resolvers/UserResolver.js +2 -17
  202. package/dist/resolvers/UserResolver.js.map +1 -1
  203. package/dist/resolvers/agentVisionConsentAudit.d.ts +37 -0
  204. package/dist/resolvers/agentVisionConsentAudit.d.ts.map +1 -0
  205. package/dist/resolvers/agentVisionConsentAudit.js +63 -0
  206. package/dist/resolvers/agentVisionConsentAudit.js.map +1 -0
  207. package/dist/resolvers/avatarInputValidation.d.ts +40 -0
  208. package/dist/resolvers/avatarInputValidation.d.ts.map +1 -0
  209. package/dist/resolvers/avatarInputValidation.js +97 -0
  210. package/dist/resolvers/avatarInputValidation.js.map +1 -0
  211. package/dist/resolvers/currentUserRoles.d.ts +29 -0
  212. package/dist/resolvers/currentUserRoles.d.ts.map +1 -1
  213. package/dist/resolvers/currentUserRoles.js +30 -0
  214. package/dist/resolvers/currentUserRoles.js.map +1 -1
  215. package/dist/resolvers/meetings/meetingMutations.d.ts +6 -0
  216. package/dist/resolvers/meetings/meetingMutations.d.ts.map +1 -1
  217. package/dist/resolvers/meetings/meetingMutations.js +21 -0
  218. package/dist/resolvers/meetings/meetingMutations.js.map +1 -1
  219. package/dist/services/TaskGraphContinuationDeliverer.d.ts +4 -3
  220. package/dist/services/TaskGraphContinuationDeliverer.d.ts.map +1 -1
  221. package/dist/services/TaskGraphContinuationDeliverer.js +13 -7
  222. package/dist/services/TaskGraphContinuationDeliverer.js.map +1 -1
  223. package/dist/sharedCache.d.ts +105 -0
  224. package/dist/sharedCache.d.ts.map +1 -0
  225. package/dist/sharedCache.js +276 -0
  226. package/dist/sharedCache.js.map +1 -0
  227. package/dist/types.d.ts +6 -0
  228. package/dist/types.d.ts.map +1 -1
  229. package/dist/types.js.map +1 -1
  230. package/package.json +97 -95
  231. package/src/__tests__/ActionResolver.authorization.test.ts +353 -0
  232. package/src/__tests__/AdhocQueryResolver.sqlAuthorizer.test.ts +104 -0
  233. package/src/__tests__/BridgeHostInstance.test.ts +232 -0
  234. package/src/__tests__/ClientToolRequestResolver.test.ts +116 -0
  235. package/src/__tests__/FetchEntityVectorsResolver.test.ts +285 -0
  236. package/src/__tests__/HostInstance.test.ts +84 -0
  237. package/src/__tests__/HumanHandoffResolver.test.ts +17 -0
  238. package/src/__tests__/MeetingResolver.test.ts +75 -1
  239. package/src/__tests__/RealtimeBridgeResolver.test.ts +415 -6
  240. package/src/__tests__/RealtimeClientSessionResolver.configTamper.test.ts +383 -0
  241. package/src/__tests__/RealtimeClientSessionResolver.test.ts +682 -58
  242. package/src/__tests__/RealtimeProxyServer.test.ts +16 -1
  243. package/src/__tests__/RealtimeRelay.test.ts +876 -0
  244. package/src/__tests__/RemoteBrowserActionResolver.goalRunLink.test.ts +129 -0
  245. package/src/__tests__/ResolverBase.aggregateScreen.test.ts +174 -0
  246. package/src/__tests__/ResolverBase.clientOldValues.test.ts +6 -3
  247. package/src/__tests__/ReturningVisitorRecap.test.ts +115 -0
  248. package/src/__tests__/RunAIAgentResolver.branch.test.ts +169 -0
  249. package/src/__tests__/RunAIAgentResolver.historyFrom.test.ts +4 -4
  250. package/src/__tests__/RunAIAgentResolver.stoppedPredecessor.test.ts +166 -0
  251. package/src/__tests__/RunTemplateResolver.test.ts +132 -0
  252. package/src/__tests__/RunViewResolver.cacheCheckForwarding.test.ts +63 -0
  253. package/src/__tests__/SessionJanitor.test.ts +845 -18
  254. package/src/__tests__/SessionManager.test.ts +213 -17
  255. package/src/__tests__/SessionRunIDVerifier.test.ts +251 -0
  256. package/src/__tests__/SyncRolesUsersResolver.userRoles.test.ts +97 -0
  257. package/src/__tests__/TaskGraphContinuationDeliverer.test.ts +50 -3
  258. package/src/__tests__/UserAvatarResolver.test.ts +255 -0
  259. package/src/__tests__/UserResolver.currentUser.test.ts +16 -1
  260. package/src/__tests__/actionAuthorization.test.ts +130 -0
  261. package/src/__tests__/agentVisionConsentAudit.test.ts +118 -0
  262. package/src/__tests__/avatarInputValidation.test.ts +160 -0
  263. package/src/__tests__/isolatedProviderTransactions.test.ts +169 -0
  264. package/src/__tests__/magicLinkService.consume.test.ts +6 -0
  265. package/src/__tests__/magicLinkService.provisionLog.test.ts +5 -0
  266. package/src/__tests__/newUsers.template.test.ts +151 -0
  267. package/src/__tests__/newUsers.test.ts +74 -5
  268. package/src/__tests__/orphanedConversationDetailReconciler.test.ts +17 -2
  269. package/src/__tests__/realtimeSessions/RealtimeSessionEventService.test.ts +201 -0
  270. package/src/__tests__/realtimeSessions/RealtimeSessionEventsResolver.test.ts +215 -0
  271. package/src/__tests__/realtimeSessions/RealtimeSessionVerificationResolver.test.ts +102 -0
  272. package/src/__tests__/realtimeSessions/RealtimeSessionVerificationService.test.ts +349 -0
  273. package/src/__tests__/realtimeSessions/RealtimeVerifyRouter.test.ts +131 -0
  274. package/src/__tests__/realtimeSessions/SlidingWindowRateLimiter.test.ts +56 -0
  275. package/src/__tests__/realtimeSessions/helpers.ts +4 -0
  276. package/src/__tests__/realtimeSessions/sessionAccess.test.ts +129 -0
  277. package/src/__tests__/realtimeSessions/sessionConfigStamps.test.ts +124 -0
  278. package/src/__tests__/realtimeSessions/verificationCore.test.ts +355 -0
  279. package/src/__tests__/realtimeSessions/verificationPages.test.ts +84 -0
  280. package/src/__tests__/realtimeSessions/verificationPolicy.test.ts +226 -0
  281. package/src/__tests__/realtimeSessions/verificationWorkflow.test.ts +633 -0
  282. package/src/__tests__/resolverBase.fls.test.ts +87 -4
  283. package/src/__tests__/serverExtensionReservedRoots.test.ts +2 -0
  284. package/src/__tests__/sharedCache.test.ts +310 -0
  285. package/src/__tests__/widgetGuestElevation.test.ts +44 -0
  286. package/src/agentSessions/BridgeHostInstance.ts +32 -0
  287. package/src/agentSessions/HostInstance.ts +124 -28
  288. package/src/agentSessions/ReturningVisitorRecap.ts +7 -3
  289. package/src/agentSessions/SessionJanitor.ts +543 -57
  290. package/src/agentSessions/SessionManager.ts +125 -25
  291. package/src/agentSessions/SessionRunIDVerifier.ts +171 -0
  292. package/src/agentSessions/index.ts +10 -3
  293. package/src/auth/actionAuthorization.ts +116 -0
  294. package/src/auth/index.ts +3 -8
  295. package/src/auth/magicLink/MagicLinkService.ts +6 -3
  296. package/src/auth/newUsers.ts +99 -11
  297. package/src/config.ts +125 -0
  298. package/src/context.ts +15 -10
  299. package/src/generated/graphql-schemas/__mj.ts +496 -1
  300. package/src/generic/OrphanedConversationDetailReconciler.ts +3 -3
  301. package/src/generic/ResolverBase.ts +39 -9
  302. package/src/generic/RunViewResolver.ts +4 -0
  303. package/src/index.ts +132 -143
  304. package/src/isolatedProvider.ts +22 -0
  305. package/src/realtimeProxy/RealtimeProxyServer.ts +148 -16
  306. package/src/realtimeProxy/RealtimeRelayTunnel.ts +645 -0
  307. package/src/realtimeSessions/README.md +179 -0
  308. package/src/realtimeSessions/RealtimeSessionEventService.ts +274 -0
  309. package/src/realtimeSessions/RealtimeSessionVerificationService.ts +167 -0
  310. package/src/realtimeSessions/RealtimeVerifyRouter.ts +108 -0
  311. package/src/realtimeSessions/SessionVerificationPortsBase.ts +189 -0
  312. package/src/realtimeSessions/SlidingWindowRateLimiter.ts +92 -0
  313. package/src/realtimeSessions/index.ts +16 -0
  314. package/src/realtimeSessions/sessionAccess.ts +141 -0
  315. package/src/realtimeSessions/sessionConfigStamps.ts +123 -0
  316. package/src/realtimeSessions/subscriptionAuthorization.ts +116 -0
  317. package/src/realtimeSessions/verificationCore.ts +502 -0
  318. package/src/realtimeSessions/verificationPages.ts +149 -0
  319. package/src/realtimeSessions/verificationPolicy.ts +321 -0
  320. package/src/realtimeSessions/verificationWorkflow.ts +702 -0
  321. package/src/realtimeWidget/widgetGuestElevation.ts +34 -2
  322. package/src/resolvers/ActionResolver.ts +84 -21
  323. package/src/resolvers/AdhocQueryResolver.ts +24 -6
  324. package/src/resolvers/CacheStatsResolver.ts +89 -2
  325. package/src/resolvers/ClientToolRequestResolver.ts +25 -7
  326. package/src/resolvers/FetchEntityVectorsResolver.ts +97 -2
  327. package/src/resolvers/HumanHandoffResolver.ts +8 -0
  328. package/src/resolvers/MCPResolver.ts +8 -5
  329. package/src/resolvers/RealtimeBridgeResolver.ts +163 -11
  330. package/src/resolvers/RealtimeClientSessionResolver.ts +501 -96
  331. package/src/resolvers/RealtimeSessionEventsResolver.ts +133 -0
  332. package/src/resolvers/RealtimeSessionVerificationResolver.ts +174 -0
  333. package/src/resolvers/RemoteBrowserActionResolver.ts +11 -1
  334. package/src/resolvers/RunAIAgentResolver.ts +78 -11
  335. package/src/resolvers/RunTemplateResolver.ts +13 -12
  336. package/src/resolvers/SyncDataResolver.ts +6 -2
  337. package/src/resolvers/SyncRolesUsersResolver.ts +24 -6
  338. package/src/resolvers/UserAvatarResolver.ts +111 -0
  339. package/src/resolvers/UserResolver.ts +2 -16
  340. package/src/resolvers/agentVisionConsentAudit.ts +82 -0
  341. package/src/resolvers/avatarInputValidation.ts +123 -0
  342. package/src/resolvers/currentUserRoles.ts +32 -1
  343. package/src/resolvers/meetings/meetingMutations.ts +21 -0
  344. package/src/services/TaskGraphContinuationDeliverer.ts +13 -7
  345. package/src/sharedCache.ts +302 -0
  346. package/src/types.ts +7 -0
@@ -0,0 +1,118 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+ import type { IMetadataProvider, UserInfo } from '@memberjunction/core';
3
+
4
+ const h = vi.hoisted(() => ({
5
+ systemUser: { ID: 'SYS', Name: 'System' } as { ID: string; Name: string } | undefined,
6
+ logError: vi.fn(),
7
+ }));
8
+
9
+ vi.mock('@memberjunction/core', async (importOriginal) => {
10
+ const actual = await importOriginal<Record<string, unknown>>();
11
+ return { ...actual, LogError: h.logError };
12
+ });
13
+
14
+ vi.mock('@memberjunction/generic-database-provider', () => ({
15
+ UserCache: { Instance: { GetSystemUser: () => h.systemUser } },
16
+ }));
17
+
18
+ import { WriteAgentVisionConsentAudit, REALTIME_AGENT_VISION_CONSENT_AUDIT_TYPE } from '../resolvers/agentVisionConsentAudit';
19
+
20
+ /** A fake `MJ: Audit Logs` row that records what is set on it. */
21
+ class FakeAuditRow {
22
+ public UserID = '';
23
+ public AuditLogTypeID = '';
24
+ public Status = '';
25
+ public EntityID: string | null = null;
26
+ public RecordID: string | null = null;
27
+ public Description: string | null = null;
28
+ public Details: string | null = null;
29
+ public IsNew = false;
30
+ constructor(private readonly saveResult: boolean) {}
31
+ NewRecord() {
32
+ this.IsNew = true;
33
+ }
34
+ async Save() {
35
+ return this.saveResult;
36
+ }
37
+ get LatestResult() {
38
+ return { CompleteMessage: 'save failed' };
39
+ }
40
+ }
41
+
42
+ const user = { ID: 'U1', Name: 'Test User' } as unknown as UserInfo;
43
+
44
+ /** A provider with the consent audit type seeded (unless told otherwise), handing out one fake row. */
45
+ function fakeProvider(o: { seeded?: boolean; saveResult?: boolean; getThrows?: boolean } = {}) {
46
+ const row = new FakeAuditRow(o.saveResult ?? true);
47
+ const getEntityObject = vi.fn(async () => {
48
+ if (o.getThrows) {
49
+ throw new Error('database unavailable');
50
+ }
51
+ return row;
52
+ });
53
+ const provider = {
54
+ AuditLogTypes: o.seeded === false ? [] : [{ ID: 'TYPE-1', Name: REALTIME_AGENT_VISION_CONSENT_AUDIT_TYPE }],
55
+ EntityByName: (name: string) => (name === 'MJ: Users' ? { ID: 'USERS-ENTITY' } : undefined),
56
+ GetEntityObject: getEntityObject,
57
+ } as unknown as IMetadataProvider;
58
+ return { provider, row, getEntityObject };
59
+ }
60
+
61
+ describe('WriteAgentVisionConsentAudit', () => {
62
+ beforeEach(() => {
63
+ h.logError.mockClear();
64
+ h.systemUser = { ID: 'SYS', Name: 'System' };
65
+ });
66
+
67
+ it('records an applied choice, saved by the system user, about the person', async () => {
68
+ const { provider, row, getEntityObject } = fakeProvider();
69
+ await WriteAgentVisionConsentAudit({ User: user, RoomName: 'room-1', Allow: true, Applied: true }, provider);
70
+ expect(getEntityObject).toHaveBeenCalledWith('MJ: Audit Logs', h.systemUser);
71
+ expect(row).toMatchObject({
72
+ IsNew: true,
73
+ UserID: 'U1',
74
+ AuditLogTypeID: 'TYPE-1',
75
+ Status: 'Success',
76
+ EntityID: 'USERS-ENTITY',
77
+ RecordID: 'U1',
78
+ Description: 'Let agents see their camera and shared screen in room room-1.',
79
+ });
80
+ expect(JSON.parse(row.Details ?? '')).toEqual({ roomName: 'room-1', allow: true, error: null });
81
+ expect(h.logError).not.toHaveBeenCalled();
82
+ });
83
+
84
+ it('records a refused change as Failed, with the reason', async () => {
85
+ const { provider, row } = fakeProvider();
86
+ await WriteAgentVisionConsentAudit({ User: user, RoomName: 'room-1', Allow: false, Applied: false, ErrorMessage: 'not in the room' }, provider);
87
+ expect(row.Status).toBe('Failed');
88
+ expect(row.Description).toBe('Stopped letting agents see their camera and shared screen in room room-1 (not applied).');
89
+ expect(JSON.parse(row.Details ?? '')).toEqual({ roomName: 'room-1', allow: false, error: 'not in the room' });
90
+ });
91
+
92
+ it('logs the choice instead when the audit log type is not seeded', async () => {
93
+ const { provider, getEntityObject } = fakeProvider({ seeded: false });
94
+ await WriteAgentVisionConsentAudit({ User: user, RoomName: 'room-1', Allow: true, Applied: true }, provider);
95
+ expect(getEntityObject).not.toHaveBeenCalled();
96
+ expect(h.logError).toHaveBeenCalledWith(expect.stringMatching(/is not seeded.*user U1: Let agents see their camera and shared screen in room room-1\./));
97
+ });
98
+
99
+ it('logs the choice instead when there is no system user or no provider', async () => {
100
+ h.systemUser = undefined;
101
+ const { provider, getEntityObject } = fakeProvider();
102
+ await WriteAgentVisionConsentAudit({ User: user, RoomName: 'room-1', Allow: true, Applied: true }, provider);
103
+ await WriteAgentVisionConsentAudit({ User: user, RoomName: 'room-1', Allow: true, Applied: true }, null);
104
+ expect(getEntityObject).not.toHaveBeenCalled();
105
+ expect(h.logError.mock.calls.map((c) => String(c[0]).match(/\((.*?)\)/)?.[1])).toEqual(['no system user', 'no provider']);
106
+ });
107
+
108
+ it('logs a row that does not save, and never throws when the write fails', async () => {
109
+ await WriteAgentVisionConsentAudit({ User: user, RoomName: 'room-1', Allow: true, Applied: true }, fakeProvider({ saveResult: false }).provider);
110
+ await expect(
111
+ WriteAgentVisionConsentAudit({ User: user, RoomName: 'room-1', Allow: true, Applied: true }, fakeProvider({ getThrows: true }).provider),
112
+ ).resolves.toBeUndefined();
113
+ expect(h.logError.mock.calls.map((c) => String(c[0]))).toEqual([
114
+ 'Agent vision consent audit row not saved for user U1: save failed',
115
+ 'Agent vision consent audit write threw for user U1: database unavailable',
116
+ ]);
117
+ });
118
+ });
@@ -0,0 +1,160 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import {
3
+ ValidateAvatarInput,
4
+ AVATAR_MAX_IMAGE_BYTES,
5
+ AVATAR_MAX_URL_LENGTH,
6
+ AVATAR_MAX_ICON_CLASS_LENGTH,
7
+ } from '../resolvers/avatarInputValidation.js';
8
+
9
+ /** A valid base64 data URI whose payload decodes to exactly `bytes` bytes. */
10
+ function dataUri(bytes: number, type = 'png'): string {
11
+ return `data:image/${type};base64,${Buffer.alloc(bytes, 7).toString('base64')}`;
12
+ }
13
+
14
+ function expectRefused(imageURL: string | null, iconClass: string | null = null): string {
15
+ const result = ValidateAvatarInput(imageURL, iconClass);
16
+ expect(result.Valid).toBe(false);
17
+ expect(result.ErrorMessage).toBeTruthy();
18
+ return result.ErrorMessage!;
19
+ }
20
+
21
+ describe('ValidateAvatarInput', () => {
22
+ describe('clearing', () => {
23
+ it('accepts both null as a revert to default', () => {
24
+ expect(ValidateAvatarInput(null, null)).toEqual({ Valid: true, ImageURL: null, IconClass: null });
25
+ });
26
+
27
+ it('treats undefined and empty strings as clear', () => {
28
+ expect(ValidateAvatarInput(undefined, undefined)).toEqual({ Valid: true, ImageURL: null, IconClass: null });
29
+ expect(ValidateAvatarInput('', '')).toEqual({ Valid: true, ImageURL: null, IconClass: null });
30
+ });
31
+ });
32
+
33
+ describe('data URIs', () => {
34
+ it.each(['png', 'jpeg', 'jpg', 'gif', 'webp'])('accepts image/%s', (type) => {
35
+ const uri = dataUri(10, type);
36
+ expect(ValidateAvatarInput(uri, null)).toEqual({ Valid: true, ImageURL: uri, IconClass: null });
37
+ });
38
+
39
+ it('accepts every padding variant', () => {
40
+ for (const bytes of [3, 4, 5]) {
41
+ expect(ValidateAvatarInput(dataUri(bytes), null).Valid).toBe(true);
42
+ }
43
+ });
44
+
45
+ it('accepts a decoded payload of exactly the cap', () => {
46
+ expect(ValidateAvatarInput(dataUri(AVATAR_MAX_IMAGE_BYTES), null).Valid).toBe(true);
47
+ });
48
+
49
+ it('refuses a decoded payload one byte over the cap', () => {
50
+ expect(expectRefused(dataUri(AVATAR_MAX_IMAGE_BYTES + 1))).toContain('200KB');
51
+ });
52
+
53
+ it('refuses an oversized data URI on length alone, even one the pattern would reject anyway', () => {
54
+ const huge = `data:image/png;base64,${'A'.repeat(2 * 1024 * 1024)}!`;
55
+ expect(expectRefused(huge)).toContain('200KB');
56
+ });
57
+
58
+ it('accepts the longest prefix with a payload of exactly the cap', () => {
59
+ expect(ValidateAvatarInput(dataUri(AVATAR_MAX_IMAGE_BYTES, 'jpeg'), null).Valid).toBe(true);
60
+ });
61
+
62
+ it('refuses SVG, which can carry script', () => {
63
+ expectRefused(`data:image/svg+xml;base64,${Buffer.from('<svg onload="alert(1)"/>').toString('base64')}`);
64
+ });
65
+
66
+ it.each([
67
+ 'data:text/html;base64,PHNjcmlwdD4=',
68
+ 'data:application/octet-stream;base64,AAAA',
69
+ 'data:image/png,rawdata',
70
+ 'data:image/png;base64,',
71
+ 'data:image/png;base64,AAA',
72
+ 'data:image/png;base64,AA=A',
73
+ 'data:image/png;base64,AA AA',
74
+ 'data:image/png;base64,AA*A',
75
+ 'data:image/PNG;base64,AAAA',
76
+ `${dataUri(10)}\n`,
77
+ ])('refuses %j', (value) => {
78
+ expectRefused(value);
79
+ });
80
+ });
81
+
82
+ describe('http(s) URLs', () => {
83
+ it.each(['https://example.com/me.png', 'http://example.com/a/b?c=d#e', 'HTTPS://EXAMPLE.COM/x.png'])('accepts %s', (url) => {
84
+ expect(ValidateAvatarInput(url, null)).toEqual({ Valid: true, ImageURL: url, IconClass: null });
85
+ });
86
+
87
+ it('accepts a URL of exactly the length cap', () => {
88
+ const base = 'https://example.com/';
89
+ const url = base + 'a'.repeat(AVATAR_MAX_URL_LENGTH - base.length);
90
+ expect(url).toHaveLength(AVATAR_MAX_URL_LENGTH);
91
+ expect(ValidateAvatarInput(url, null).Valid).toBe(true);
92
+ });
93
+
94
+ it('refuses a URL one character over the length cap', () => {
95
+ const base = 'https://example.com/';
96
+ expectRefused(base + 'a'.repeat(AVATAR_MAX_URL_LENGTH - base.length + 1));
97
+ });
98
+
99
+ it.each([
100
+ 'javascript:alert(1)',
101
+ 'JavaScript:alert(document.cookie)',
102
+ 'vbscript:msgbox(1)',
103
+ 'file:///etc/passwd',
104
+ 'ftp://example.com/me.png',
105
+ 'blob:https://example.com/abc',
106
+ '//example.com/me.png',
107
+ '/assets/user.png',
108
+ 'me.png',
109
+ 'https://exa mple.com/me.png',
110
+ ' https://example.com/me.png',
111
+ 'https://example.com/me.png\t',
112
+ 'https://',
113
+ 'http:example.com',
114
+ ])('refuses %j', (value) => {
115
+ expectRefused(value);
116
+ });
117
+ });
118
+
119
+ describe('icon classes', () => {
120
+ it.each(['fa-solid fa-user', 'fa-regular fa-circle-user', 'fa-user', 'fa-solid fa-user-astronaut fa-2x'])('accepts %j', (icon) => {
121
+ expect(ValidateAvatarInput(null, icon)).toEqual({ Valid: true, ImageURL: null, IconClass: icon });
122
+ });
123
+
124
+ it('accepts an icon class of exactly the length cap', () => {
125
+ const icon = 'fa-' + 'a'.repeat(AVATAR_MAX_ICON_CLASS_LENGTH - 3);
126
+ expect(ValidateAvatarInput(null, icon).Valid).toBe(true);
127
+ });
128
+
129
+ it('refuses an icon class one character over the length cap', () => {
130
+ expectRefused(null, 'fa-' + 'a'.repeat(AVATAR_MAX_ICON_CLASS_LENGTH - 2));
131
+ });
132
+
133
+ it.each([
134
+ 'solid user',
135
+ 'fa-',
136
+ 'fa-solid fa-User',
137
+ 'FA-SOLID FA-USER',
138
+ 'fa-solid fa-user" onmouseover="alert(1)',
139
+ 'fa-solid fa-user<script>',
140
+ 'fa-solid;fa-user',
141
+ 'fa_solid fa_user',
142
+ 'fa-solid\tfa-user',
143
+ 'fa-solid\nfa-user',
144
+ ])('refuses %j', (icon) => {
145
+ expectRefused(null, icon);
146
+ });
147
+ });
148
+
149
+ describe('both values', () => {
150
+ it('accepts a valid image and a valid icon together', () => {
151
+ const uri = dataUri(10);
152
+ expect(ValidateAvatarInput(uri, 'fa-solid fa-user')).toEqual({ Valid: true, ImageURL: uri, IconClass: 'fa-solid fa-user' });
153
+ });
154
+
155
+ it('refuses the whole request when either value is bad, and returns no values to store', () => {
156
+ expect(ValidateAvatarInput('javascript:alert(1)', 'fa-solid fa-user')).toMatchObject({ Valid: false, ImageURL: null, IconClass: null });
157
+ expect(ValidateAvatarInput(dataUri(10), 'bad icon')).toMatchObject({ Valid: false, ImageURL: null, IconClass: null });
158
+ });
159
+ });
160
+ });
@@ -0,0 +1,169 @@
1
+ /**
2
+ * Transactions opened outside a GraphQL request run on their OWN provider, never the shared
3
+ * `Metadata.Provider`.
4
+ *
5
+ * A provider's ambient transaction is per INSTANCE, and the global provider is shared by every
6
+ * request, scheduled job and background service. A transaction opened there nests into any other
7
+ * caller's, and while it is open every global-provider write from anywhere lands inside it. One left
8
+ * open swallows them all until the process restarts (MJ#2140). These tests pin the two call sites
9
+ * that used to open their transaction on the global provider: magic-link provisioning and
10
+ * SyncData's delete-with-filter. New-user creation is pinned in newUsers.test.ts.
11
+ */
12
+ import 'reflect-metadata';
13
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
14
+
15
+ vi.mock('@memberjunction/generic-database-provider', () => ({
16
+ UserCache: { Instance: { Users: [] } },
17
+ }));
18
+ vi.mock('@memberjunction/communication-engine', () => ({
19
+ CommunicationEngine: { Instance: {} },
20
+ }));
21
+ vi.mock('@memberjunction/communication-types', () => ({
22
+ Message: class {},
23
+ }));
24
+ vi.mock('../config.js', () => ({
25
+ configInfo: {},
26
+ }));
27
+
28
+ const factory = vi.hoisted(() => ({ create: vi.fn() }));
29
+ vi.mock('../isolatedProvider.js', () => ({ CreateIsolatedProvider: factory.create }));
30
+
31
+ import { Metadata, RunView, type IMetadataProvider, type RoleInfo, type UserInfo } from '@memberjunction/core';
32
+ import type { MJMagicLinkInviteEntity } from '@memberjunction/core-entities';
33
+ import type { MagicLinkConfig } from '../config.js';
34
+ import { MagicLinkService } from '../auth/magicLink/MagicLinkService.js';
35
+ import { SyncDataResolver, type ActionItemOutputType } from '../resolvers/SyncDataResolver.js';
36
+
37
+ /** A provider that records every call made on it. */
38
+ function recordingProvider(calls: string[], saveSucceeds = true) {
39
+ return {
40
+ BeginTransaction: vi.fn(async () => { calls.push('BeginTransaction'); }),
41
+ CommitTransaction: vi.fn(async () => { calls.push('CommitTransaction'); }),
42
+ RollbackTransaction: vi.fn(async () => { calls.push('RollbackTransaction'); }),
43
+ GetEntityObject: vi.fn(async (entityName: string) => {
44
+ calls.push(`GetEntityObject:${entityName}`);
45
+ const entity: Record<string, unknown> = {
46
+ NewRecord: () => { entity.ID = `id-${entityName}`; },
47
+ Save: async () => saveSucceeds,
48
+ GetAll: () => ({ ID: entity.ID }),
49
+ LatestResult: { CompleteMessage: 'save failed' },
50
+ };
51
+ return entity;
52
+ }),
53
+ };
54
+ }
55
+
56
+ let globalCalls: string[];
57
+ let isolatedCalls: string[];
58
+
59
+ beforeEach(() => {
60
+ vi.spyOn(console, 'log').mockImplementation(() => undefined);
61
+ vi.spyOn(console, 'error').mockImplementation(() => undefined);
62
+ globalCalls = [];
63
+ isolatedCalls = [];
64
+ Metadata.Provider = recordingProvider(globalCalls) as unknown as IMetadataProvider;
65
+ });
66
+
67
+ afterEach(() => {
68
+ Metadata.Provider = null as unknown as IMetadataProvider;
69
+ vi.restoreAllMocks();
70
+ factory.create.mockReset();
71
+ });
72
+
73
+ describe('MagicLinkService.createScopedUser', () => {
74
+ const invite = { Email: 'guest@example.com', RoleID: 'role-1', ApplicationID: 'app-1' } as unknown as MJMagicLinkInviteEntity;
75
+ const role = { ID: 'role-1', Name: 'External Guest' } as RoleInfo;
76
+ const contextUser = { ID: 'ctx-user' } as UserInfo;
77
+ const provision = () =>
78
+ new MagicLinkService('https://mj.example.com', {} as MagicLinkConfig)['createScopedUser'](invite, role, contextUser);
79
+
80
+ it('provisions inside a transaction on its own provider', async () => {
81
+ factory.create.mockResolvedValue(recordingProvider(isolatedCalls));
82
+
83
+ expect((await provision()).success).toBe(true);
84
+ expect(isolatedCalls).toEqual([
85
+ 'BeginTransaction',
86
+ 'GetEntityObject:MJ: Users',
87
+ 'GetEntityObject:MJ: User Roles',
88
+ 'GetEntityObject:MJ: User Applications',
89
+ 'CommitTransaction',
90
+ ]);
91
+ expect(globalCalls).toEqual([]);
92
+ });
93
+
94
+ it('rolls back on its own provider when a save fails', async () => {
95
+ factory.create.mockResolvedValue(recordingProvider(isolatedCalls, false));
96
+
97
+ expect((await provision()).success).toBe(false);
98
+ expect(isolatedCalls).toEqual(['BeginTransaction', 'GetEntityObject:MJ: Users', 'RollbackTransaction']);
99
+ expect(globalCalls).toEqual([]);
100
+ });
101
+ });
102
+
103
+ describe('SyncDataResolver.SyncSingleItemDeleteWithFilter', () => {
104
+ const user = { ID: 'system' } as UserInfo;
105
+
106
+ /** Rows the filtered view returns; each records its Delete on the shared call log. */
107
+ function stubView(rows: number, deleteSucceeds = true) {
108
+ const fromProvider = vi.spyOn(RunView, 'FromMetadataProvider').mockImplementation(
109
+ () =>
110
+ ({
111
+ RunView: async () => ({
112
+ Success: true,
113
+ Results: Array.from({ length: rows }, (_, i) => ({
114
+ Delete: async () => { isolatedCalls.push(`Delete:${i}`); return deleteSucceeds; },
115
+ LatestResult: { CompleteMessage: 'delete failed' },
116
+ })),
117
+ }),
118
+ }) as unknown as RunView,
119
+ );
120
+ return fromProvider;
121
+ }
122
+
123
+ const deleteWithFilter = (providerOverride?: IMetadataProvider) => {
124
+ const result = { Success: false } as ActionItemOutputType;
125
+ const resolver = new SyncDataResolver() as unknown as {
126
+ SyncSingleItemDeleteWithFilter: (...args: unknown[]) => Promise<void>;
127
+ };
128
+ return resolver
129
+ .SyncSingleItemDeleteWithFilter('MJ: Things', "Name = 'x'", result, user, {}, providerOverride)
130
+ .then(() => result);
131
+ };
132
+
133
+ it('with no request provider, loads AND deletes on its own provider inside one transaction', async () => {
134
+ const isolated = recordingProvider(isolatedCalls);
135
+ factory.create.mockResolvedValue(isolated);
136
+ const fromProvider = stubView(2);
137
+
138
+ const result = await deleteWithFilter();
139
+
140
+ expect(result.Success).toBe(true);
141
+ expect(fromProvider).toHaveBeenCalledWith(isolated);
142
+ expect(isolatedCalls).toEqual(['BeginTransaction', 'Delete:0', 'Delete:1', 'CommitTransaction']);
143
+ expect(globalCalls).toEqual([]);
144
+ });
145
+
146
+ it('with a request provider, uses it for both the read and the transaction', async () => {
147
+ const requestProvider = recordingProvider(isolatedCalls);
148
+ const fromProvider = stubView(1);
149
+
150
+ const result = await deleteWithFilter(requestProvider as unknown as IMetadataProvider);
151
+
152
+ expect(result.Success).toBe(true);
153
+ expect(factory.create).not.toHaveBeenCalled();
154
+ expect(fromProvider).toHaveBeenCalledWith(requestProvider);
155
+ expect(isolatedCalls).toEqual(['BeginTransaction', 'Delete:0', 'CommitTransaction']);
156
+ expect(globalCalls).toEqual([]);
157
+ });
158
+
159
+ it('rolls back on the same provider when a delete fails', async () => {
160
+ factory.create.mockResolvedValue(recordingProvider(isolatedCalls));
161
+ stubView(2, false);
162
+
163
+ const result = await deleteWithFilter();
164
+
165
+ expect(result.Success).toBe(false);
166
+ expect(isolatedCalls).toEqual(['BeginTransaction', 'Delete:0', 'RollbackTransaction']);
167
+ expect(globalCalls).toEqual([]);
168
+ });
169
+ });
@@ -15,6 +15,12 @@ vi.mock('@memberjunction/communication-types', () => ({
15
15
  vi.mock('../config.js', () => ({
16
16
  configInfo: {},
17
17
  }));
18
+ // Provisioning is stubbed in this suite; keep the real provider stack out of the module graph.
19
+ vi.mock('../isolatedProvider.js', () => ({
20
+ CreateIsolatedProvider: async () => {
21
+ throw new Error('provisioning is stubbed in this suite');
22
+ },
23
+ }));
18
24
 
19
25
  import { Metadata, RunView, type DatabaseProviderBase, type IMetadataProvider, type UserInfo } from '@memberjunction/core';
20
26
  import type { MJMagicLinkInviteEntity } from '@memberjunction/core-entities';
@@ -16,6 +16,10 @@ vi.mock('@memberjunction/communication-types', () => ({
16
16
  vi.mock('../config.js', () => ({
17
17
  configInfo: {},
18
18
  }));
19
+ const isolated = vi.hoisted(() => ({ provider: null as unknown }));
20
+ vi.mock('../isolatedProvider.js', () => ({
21
+ CreateIsolatedProvider: async () => isolated.provider,
22
+ }));
19
23
 
20
24
  import { Metadata, type IMetadataProvider, type RoleInfo, type UserInfo } from '@memberjunction/core';
21
25
  import type { MJMagicLinkInviteEntity } from '@memberjunction/core-entities';
@@ -62,6 +66,7 @@ describe('MagicLinkService provisioning log policy', () => {
62
66
  RollbackTransaction: async () => undefined,
63
67
  GetEntityObject: async (entityName: string) => makeMockEntity(`mock-${entityName}`),
64
68
  };
69
+ isolated.provider = provider;
65
70
  Metadata.Provider = provider as unknown as IMetadataProvider;
66
71
 
67
72
  service = new MagicLinkService('https://mj.example.com', {} as MagicLinkConfig);
@@ -0,0 +1,151 @@
1
+ /**
2
+ * Tests for userHandling.newUserTemplate: NewUserBase.createNewUser builds the new user as a copy of a
3
+ * template user through the record-cloning engine, and falls back to a plain user whenever the template
4
+ * can't be used (missing, not flagged IsTemplate, or the copy fails), so sign-in never breaks.
5
+ */
6
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
7
+
8
+ const {
9
+ mockConfig,
10
+ mockClone,
11
+ mockRunView,
12
+ mockLogError,
13
+ mockProvider,
14
+ savedUsers,
15
+ } = vi.hoisted(() => {
16
+ const savedUsers: Array<Record<string, unknown>> = [];
17
+ const makeUser = () => {
18
+ const user: Record<string, unknown> & { NewRecord: () => void; Load: (id: string) => Promise<boolean>; Save: () => Promise<boolean> } = {
19
+ NewRecord: vi.fn(() => {
20
+ user.ID = 'plain-user-id';
21
+ user.Created = 'plain';
22
+ }),
23
+ Load: vi.fn(async (id: string) => {
24
+ user.ID = id;
25
+ user.Created = 'template';
26
+ return true;
27
+ }),
28
+ Save: vi.fn(async () => {
29
+ savedUsers.push({ ...user });
30
+ return true;
31
+ }),
32
+ LatestResult: null,
33
+ };
34
+ return user;
35
+ };
36
+ return {
37
+ mockConfig: {
38
+ userHandling: {
39
+ newUserRoles: [] as string[],
40
+ CreateUserApplicationRecords: false,
41
+ UserApplications: [] as string[],
42
+ contextUserForNewUserCreation: '',
43
+ newUserTemplate: { email: 'demo@example.org', preset: 'template' } as { email: string; preset: string } | undefined,
44
+ },
45
+ },
46
+ mockClone: vi.fn(),
47
+ mockRunView: vi.fn(),
48
+ mockLogError: vi.fn(),
49
+ mockProvider: {
50
+ GetEntityObject: vi.fn(async () => makeUser()),
51
+ BeginTransaction: vi.fn(),
52
+ CommitTransaction: vi.fn(),
53
+ RollbackTransaction: vi.fn(),
54
+ Roles: [],
55
+ Applications: [],
56
+ },
57
+ savedUsers,
58
+ };
59
+ });
60
+
61
+ vi.mock('../config.js', () => ({ configInfo: mockConfig }));
62
+ vi.mock('../isolatedProvider.js', () => ({ CreateIsolatedProvider: vi.fn(async () => mockProvider) }));
63
+ vi.mock('../auth/principals.js', () => ({ ResolveConfiguredPrincipal: vi.fn(() => ({ ID: 'system-id', Type: 'Owner' })) }));
64
+ vi.mock('@memberjunction/record-cloning', () => ({
65
+ RecordCloneEngine: class {
66
+ Clone = mockClone;
67
+ },
68
+ }));
69
+ vi.mock('@memberjunction/core', async (importOriginal) => {
70
+ const actual = await importOriginal<typeof import('@memberjunction/core')>();
71
+ return {
72
+ ...actual,
73
+ RunView: { FromMetadataProvider: () => ({ RunView: mockRunView }) },
74
+ LogError: mockLogError,
75
+ LogStatus: vi.fn(),
76
+ };
77
+ });
78
+ vi.mock('@memberjunction/core-entities', () => ({ UserInfoEngine: { GetDefaultApplicationsForNewUser: () => [] } }));
79
+
80
+ import { NewUserBase } from '../auth/newUsers.js';
81
+
82
+ const templateRow = (isTemplate: boolean) => ({ Success: true, Results: [{ ID: 'tpl-id', IsTemplate: isTemplate }] });
83
+ const cloned = { Success: true, ResultCode: 'SUCCESS', Warnings: [], RecordsCloned: 120, Roots: [{ EntityName: 'MJ: Users', SourceKey: { KeyValuePairs: [] }, TargetKey: { KeyValuePairs: [{ FieldName: 'ID', Value: 'new-user-id' }] } }] };
84
+
85
+ describe('NewUserBase.createNewUser with newUserTemplate', () => {
86
+ beforeEach(() => {
87
+ vi.clearAllMocks();
88
+ savedUsers.length = 0;
89
+ mockConfig.userHandling.newUserTemplate = { email: 'demo@example.org', preset: 'template' };
90
+ });
91
+
92
+ it('creates the user as an active copy of the template', async () => {
93
+ mockRunView.mockResolvedValue(templateRow(true));
94
+ mockClone.mockResolvedValue(cloned);
95
+
96
+ const user = await new NewUserBase().createNewUser('Jane', 'Doe', 'jane@example.com');
97
+
98
+ expect(mockClone).toHaveBeenCalledWith(
99
+ expect.objectContaining({
100
+ EntityName: 'MJ: Users',
101
+ SourceRecordKey: expect.objectContaining({ KeyValuePairs: [expect.objectContaining({ FieldName: 'ID', Value: 'tpl-id' })] }),
102
+ Preset: 'template',
103
+ PromptedValues: { Email: 'jane@example.com', FirstName: 'Jane', LastName: 'Doe' },
104
+ }),
105
+ expect.objectContaining({ ID: 'system-id' })
106
+ );
107
+ expect(user?.ID).toBe('new-user-id');
108
+ expect(savedUsers).toEqual([expect.objectContaining({ ID: 'new-user-id', Created: 'template', IsActive: true, LinkedRecordType: 'None' })]);
109
+ expect(mockProvider.BeginTransaction).not.toHaveBeenCalled();
110
+ });
111
+
112
+ it('falls back to a plain user when the template user is not flagged IsTemplate', async () => {
113
+ mockRunView.mockResolvedValue(templateRow(false));
114
+
115
+ const user = await new NewUserBase().createNewUser('Jane', 'Doe', 'jane@example.com');
116
+
117
+ expect(mockClone).not.toHaveBeenCalled();
118
+ expect(mockLogError.mock.calls.flat().join(' ')).toContain('not marked as a template user');
119
+ expect(user?.ID).toBe('plain-user-id');
120
+ expect(savedUsers[0]).toEqual(expect.objectContaining({ Created: 'plain', Email: 'jane@example.com', IsActive: true }));
121
+ });
122
+
123
+ it('falls back to a plain user when no user has the template email', async () => {
124
+ mockRunView.mockResolvedValue({ Success: true, Results: [] });
125
+
126
+ const user = await new NewUserBase().createNewUser('Jane', 'Doe', 'jane@example.com');
127
+
128
+ expect(mockClone).not.toHaveBeenCalled();
129
+ expect(user?.ID).toBe('plain-user-id');
130
+ });
131
+
132
+ it('falls back to a plain user when the copy is blocked', async () => {
133
+ mockRunView.mockResolvedValue(templateRow(true));
134
+ mockClone.mockResolvedValue({ Success: false, ResultCode: 'BLOCKED', Warnings: [{ Severity: 'Error', Code: 'FORBIDDEN', Message: 'needs Clone Records' }] });
135
+
136
+ const user = await new NewUserBase().createNewUser('Jane', 'Doe', 'jane@example.com');
137
+
138
+ expect(mockLogError.mock.calls.flat().join(' ')).toContain('needs Clone Records');
139
+ expect(user?.ID).toBe('plain-user-id');
140
+ });
141
+
142
+ it('creates a plain user, as before, when no template is configured', async () => {
143
+ mockConfig.userHandling.newUserTemplate = undefined;
144
+
145
+ const user = await new NewUserBase().createNewUser('Jane', 'Doe', 'jane@example.com');
146
+
147
+ expect(mockRunView).not.toHaveBeenCalled();
148
+ expect(mockClone).not.toHaveBeenCalled();
149
+ expect(user?.ID).toBe('plain-user-id');
150
+ });
151
+ });