@oneuptime/common 14.0.8 → 14.0.10

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 (976) hide show
  1. package/Models/DatabaseModels/AutoRemediationSuggestion.ts +60 -0
  2. package/Models/DatabaseModels/CephCluster.ts +213 -0
  3. package/Models/DatabaseModels/DatabaseServer.ts +213 -0
  4. package/Models/DatabaseModels/DockerHost.ts +213 -0
  5. package/Models/DatabaseModels/DockerSwarmCluster.ts +213 -0
  6. package/Models/DatabaseModels/GlobalConfig.ts +1 -1
  7. package/Models/DatabaseModels/Host.ts +213 -0
  8. package/Models/DatabaseModels/Index.ts +4 -0
  9. package/Models/DatabaseModels/KubernetesAiAgent.ts +364 -0
  10. package/Models/DatabaseModels/KubernetesCluster.ts +3 -3
  11. package/Models/DatabaseModels/PodmanHost.ts +213 -0
  12. package/Models/DatabaseModels/Project.ts +55 -5
  13. package/Models/DatabaseModels/ProxmoxCluster.ts +213 -0
  14. package/Models/DatabaseModels/ProxmoxResource.ts +49 -1
  15. package/Models/DatabaseModels/ResourceAiAgent.ts +419 -0
  16. package/Models/DatabaseModels/RunnerJob.ts +219 -0
  17. package/Models/DatabaseModels/TeamComplianceSetting.ts +143 -2
  18. package/Models/DatabaseModels/VMwareVCenter.ts +213 -0
  19. package/Models/DatabaseModels/WorkspaceProjectAuthToken.ts +17 -0
  20. package/Server/API/AIChatAPI.ts +6 -3
  21. package/Server/API/AIInvestigationAPI.ts +17 -10
  22. package/Server/API/AutoRemediationAPI.ts +598 -25
  23. package/Server/API/KubernetesClusterAiAccessAPI.ts +623 -21
  24. package/Server/API/MicrosoftTeamsAPI.ts +93 -14
  25. package/Server/API/ProxmoxResourceAPI.ts +244 -14
  26. package/Server/API/ResourceAiAccessAPI.ts +1669 -0
  27. package/Server/Infrastructure/GlobalCache.ts +32 -0
  28. package/Server/Infrastructure/Postgres/SchemaMigrations/1796000000000-AddTeamComplianceRuleScope.ts +99 -0
  29. package/Server/Infrastructure/Postgres/SchemaMigrations/1796100000000-AddKubernetesAiAgentAndAiDefaults.ts +137 -0
  30. package/Server/Infrastructure/Postgres/SchemaMigrations/1796200000000-AddProxmoxResourceNativePushColumns.ts +42 -0
  31. package/Server/Infrastructure/Postgres/SchemaMigrations/1796300000000-AddResourceAiAgents.ts +423 -0
  32. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +8 -0
  33. package/Server/Infrastructure/Semaphore.ts +22 -0
  34. package/Server/Middleware/TelemetryIngest.ts +51 -18
  35. package/Server/Services/AIService.ts +70 -1
  36. package/Server/Services/AlertService.ts +352 -11
  37. package/Server/Services/AlertSeverityService.ts +65 -15
  38. package/Server/Services/AnalyticsDatabaseService.ts +45 -1
  39. package/Server/Services/AutoRemediationRuleEngineService.ts +940 -112
  40. package/Server/Services/AutoRemediationRuleService.ts +1 -1
  41. package/Server/Services/CephClusterService.ts +109 -1
  42. package/Server/Services/DatabaseServerService.ts +116 -1
  43. package/Server/Services/DockerHostService.ts +109 -1
  44. package/Server/Services/DockerSwarmClusterService.ts +113 -1
  45. package/Server/Services/HostService.ts +126 -4
  46. package/Server/Services/IncidentService.ts +280 -104
  47. package/Server/Services/IncidentSeverityService.ts +66 -15
  48. package/Server/Services/KubernetesAiAgentJobService.ts +185 -0
  49. package/Server/Services/KubernetesAiAgentService.ts +1634 -0
  50. package/Server/Services/KubernetesClusterAiAccessService.ts +1118 -451
  51. package/Server/Services/KubernetesClusterService.ts +129 -21
  52. package/Server/Services/MetricRecordingRuleService.ts +47 -0
  53. package/Server/Services/PodmanHostService.ts +109 -1
  54. package/Server/Services/ProjectService.ts +32 -0
  55. package/Server/Services/ProxmoxClusterService.ts +110 -1
  56. package/Server/Services/ProxmoxResourceService.ts +322 -5
  57. package/Server/Services/ResourceAiAccessService.ts +1439 -0
  58. package/Server/Services/ResourceAiAgentJobService.ts +202 -0
  59. package/Server/Services/ResourceAiAgentService.ts +2432 -0
  60. package/Server/Services/RunnerJobService.ts +852 -108
  61. package/Server/Services/RunnerService.ts +1 -1
  62. package/Server/Services/ScheduledMaintenanceService.ts +737 -1
  63. package/Server/Services/ScheduledMaintenanceStateTimelineService.ts +414 -56
  64. package/Server/Services/TeamComplianceSettingService.ts +1125 -9
  65. package/Server/Services/TelemetryUsageBillingService.ts +28 -0
  66. package/Server/Services/TraceRecordingRuleService.ts +33 -0
  67. package/Server/Services/VMwareVCenterService.ts +110 -1
  68. package/Server/Utils/AI/ClusterAccess/ClusterAccessContext.ts +67 -9
  69. package/Server/Utils/AI/ClusterAccess/KubectlInvestigationToolkit.ts +39 -10
  70. package/Server/Utils/AI/ClusterAccess/KubectlJobRunner.ts +14 -3
  71. package/Server/Utils/AI/Remediation/RemediationCommandTools.ts +1475 -51
  72. package/Server/Utils/AI/Remediation/RemediationExecutionRunner.ts +1000 -43
  73. package/Server/Utils/AI/Remediation/RemediationPlanRunner.ts +25 -1
  74. package/Server/Utils/AI/ResourceAccess/InfrastructureInvestigationToolkit.ts +577 -0
  75. package/Server/Utils/AI/ResourceAccess/ResourceAccessContext.ts +271 -0
  76. package/Server/Utils/AI/ResourceAccess/ResourceAccessToolNames.ts +45 -0
  77. package/Server/Utils/AI/ResourceAccess/ResourceAiAccessSettings.ts +964 -0
  78. package/Server/Utils/AI/ResourceAccess/ResourceAiDeleteCleanup.ts +333 -0
  79. package/Server/Utils/AI/ResourceAccess/ResourceCommandJobRunner.ts +873 -0
  80. package/Server/Utils/AI/SRE/AIInvestigationEngine.ts +120 -7
  81. package/Server/Utils/AI/SRE/AlertInvestigationRunner.ts +72 -5
  82. package/Server/Utils/AI/SRE/IncidentInvestigationRunner.ts +72 -5
  83. package/Server/Utils/AI/SRE/IncidentPostmortemRunner.ts +40 -8
  84. package/Server/Utils/AI/SRE/InvestigationEligibility.ts +13 -2
  85. package/Server/Utils/AI/SRE/InvestigationGrader.ts +1 -1
  86. package/Server/Utils/AutoRemediation/CommandPlanExecutor.ts +402 -14
  87. package/Server/Utils/AutoRemediation/RemediationVerifier.ts +35 -2
  88. package/Server/Utils/Database/ProjectScopedReferenceValidator.ts +9 -1
  89. package/Server/Utils/SessionReplay/SessionReplayBudgetMetrics.ts +920 -0
  90. package/Server/Utils/SessionReplay/SessionReplayUsage.ts +84 -6
  91. package/Server/Utils/Telemetry/InventoryEntityRegistry.ts +133 -7
  92. package/Server/Utils/Telemetry/ProxmoxCephSnapshotScan.ts +58 -4
  93. package/Server/Utils/Telemetry/ProxmoxNativeNodeLiveness.ts +447 -0
  94. package/Server/Utils/Telemetry/ProxmoxNativePush.ts +903 -0
  95. package/Server/Utils/Telemetry/TelemetryEntity.ts +87 -27
  96. package/Server/Utils/Workspace/MicrosoftTeams/Actions/Alert.ts +35 -15
  97. package/Server/Utils/Workspace/MicrosoftTeams/Actions/AlertEpisode.ts +33 -15
  98. package/Server/Utils/Workspace/MicrosoftTeams/Actions/Auth.ts +21 -1
  99. package/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.ts +298 -224
  100. package/Server/Utils/Workspace/MicrosoftTeams/Actions/IncidentEpisode.ts +35 -15
  101. package/Server/Utils/Workspace/MicrosoftTeams/Actions/ScheduledMaintenance.ts +389 -175
  102. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +1073 -213
  103. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsActivityDeduplicator.ts +163 -0
  104. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCardChoices.ts +396 -0
  105. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCreateCommands.ts +418 -0
  106. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsMessageSize.ts +152 -0
  107. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsReplies.ts +251 -0
  108. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsTimezone.ts +214 -0
  109. package/Tests/App/AdminDashboard/RowActionPlacementOverrides.test.tsx +278 -0
  110. package/Tests/App/Dashboard/AIChatChartWidgetZoom.test.tsx +738 -0
  111. package/Tests/App/Dashboard/AIFeaturesSettingsPointers.test.tsx +147 -0
  112. package/Tests/App/Dashboard/AIInsightsKubernetesClusterPage.test.tsx +1523 -0
  113. package/Tests/App/Dashboard/AdminUserNotificationMethodsPage.test.tsx +463 -3
  114. package/Tests/App/Dashboard/AffectedResourcesPickerDropdownPlacement.test.tsx +958 -0
  115. package/Tests/App/Dashboard/AffectedResourcesPickerFocusLeave.test.tsx +595 -0
  116. package/Tests/App/Dashboard/AskAiAvailability.test.ts +20 -3
  117. package/Tests/App/Dashboard/AskAiDisabled.test.tsx +8 -3
  118. package/Tests/App/Dashboard/CephRateChartReload.test.tsx +1456 -0
  119. package/Tests/App/Dashboard/CephTimeRangeZoom.test.tsx +1229 -0
  120. package/Tests/App/Dashboard/ChartCardTimeRangeZoom.test.tsx +151 -0
  121. package/Tests/App/Dashboard/ChartCardZoomAffordances.test.tsx +257 -0
  122. package/Tests/App/Dashboard/ChartRefetchFrame.test.tsx +141 -0
  123. package/Tests/App/Dashboard/ChartZoomHarness.tsx +249 -0
  124. package/Tests/App/Dashboard/ChartZoomStandIn.tsx +131 -0
  125. package/Tests/App/Dashboard/CloudServerlessSlowLoads.test.tsx +628 -0
  126. package/Tests/App/Dashboard/CloudServerlessTimeRangeZoom.test.tsx +607 -0
  127. package/Tests/App/Dashboard/ClusterAccessNotice.test.tsx +184 -19
  128. package/Tests/App/Dashboard/CompanionMetricsTabZoom.test.tsx +632 -0
  129. package/Tests/App/Dashboard/ContainerChartZoomStandIn.tsx +195 -0
  130. package/Tests/App/Dashboard/ContainerHostOverviewSlowLoads.test.tsx +542 -0
  131. package/Tests/App/Dashboard/ContainerHostOverviewTimeRangeZoom.test.tsx +848 -0
  132. package/Tests/App/Dashboard/ContainerMetricsTimeRangeZoom.test.tsx +561 -0
  133. package/Tests/App/Dashboard/DashboardBoardWideZoomShell.test.tsx +1147 -0
  134. package/Tests/App/Dashboard/DashboardEditPermissions.test.tsx +3 -2
  135. package/Tests/App/Dashboard/DashboardHistogramWidgetDragCursor.test.tsx +399 -0
  136. package/Tests/App/Dashboard/DashboardHistogramWidgetZoom.test.tsx +985 -0
  137. package/Tests/App/Dashboard/DashboardHistogramWidgetZoomHint.test.tsx +803 -0
  138. package/Tests/App/Dashboard/DashboardLazyPages.test.tsx +1 -0
  139. package/Tests/App/Dashboard/DashboardSettingsPreviewZoom.test.tsx +679 -0
  140. package/Tests/App/Dashboard/DashboardSloAndDataSourceChartZoom.test.tsx +881 -0
  141. package/Tests/App/Dashboard/DashboardTraceStatusWidgets.test.tsx +878 -0
  142. package/Tests/App/Dashboard/DatabaseDocumentationMarkdown.test.ts +18 -6
  143. package/Tests/App/Dashboard/DatabaseMetricChartModalTimeRangeZoom.test.tsx +442 -0
  144. package/Tests/App/Dashboard/DatabaseOverviewTimeRangeZoom.test.tsx +831 -0
  145. package/Tests/App/Dashboard/DatabaseServerComponents.test.tsx +43 -0
  146. package/Tests/App/Dashboard/DockerSwarmInsightsTimeRangeZoom.test.tsx +524 -0
  147. package/Tests/App/Dashboard/DockerSwarmRateChartRefresh.test.tsx +259 -0
  148. package/Tests/App/Dashboard/EdgeDetailPanelHistory.test.tsx +763 -0
  149. package/Tests/App/Dashboard/EdgeDetailPanelTimeRangeZoom.test.tsx +547 -0
  150. package/Tests/App/Dashboard/EdgeHistoryBucketsCases.tsx +565 -0
  151. package/Tests/App/Dashboard/EdgeHistoryBucketsKathmandu.test.tsx +73 -0
  152. package/Tests/App/Dashboard/EdgeHistoryBucketsKolkata.test.tsx +74 -0
  153. package/Tests/App/Dashboard/EdgeHistoryBucketsNewYork.test.tsx +72 -0
  154. package/Tests/App/Dashboard/EdgeHistoryBucketsUtc.test.tsx +72 -0
  155. package/Tests/App/Dashboard/EmbeddedMetricCardRefresh.test.tsx +310 -0
  156. package/Tests/App/Dashboard/EmbeddedMetricCardTimeRangeZoom.test.tsx +536 -0
  157. package/Tests/App/Dashboard/EscalationRuleReadiness.test.tsx +3 -1
  158. package/Tests/App/Dashboard/EscalationRuleRowActions.test.tsx +660 -0
  159. package/Tests/App/Dashboard/EventNotes.test.tsx +2 -2
  160. package/Tests/App/Dashboard/EventOverviewPages.test.tsx +587 -13
  161. package/Tests/App/Dashboard/EventOverviewSnapshotExplorerZoom.test.tsx +1139 -0
  162. package/Tests/App/Dashboard/EventOverviewSnapshotZoomAcrossTabs.test.tsx +1057 -0
  163. package/Tests/App/Dashboard/EventOverviewTelemetrySnapshotZoom.test.tsx +757 -0
  164. package/Tests/App/Dashboard/ExceptionOccurrenceTrendZoom.test.tsx +675 -0
  165. package/Tests/App/Dashboard/ExceptionOccurrencesZoomSurvivesViewSwitch.test.tsx +405 -0
  166. package/Tests/App/Dashboard/ExceptionSignalPages.test.tsx +67 -3
  167. package/Tests/App/Dashboard/ExceptionTablesSpanStatus.test.tsx +515 -0
  168. package/Tests/App/Dashboard/ExplorerOwnZoomRegression.test.tsx +739 -0
  169. package/Tests/App/Dashboard/FlowBandwidthAxisCases.tsx +461 -0
  170. package/Tests/App/Dashboard/FlowBandwidthAxisKathmandu.test.tsx +25 -0
  171. package/Tests/App/Dashboard/FlowBandwidthAxisKolkata.test.tsx +25 -0
  172. package/Tests/App/Dashboard/FlowBandwidthAxisNewYork.test.tsx +25 -0
  173. package/Tests/App/Dashboard/FlowBandwidthAxisUtc.test.tsx +24 -0
  174. package/Tests/App/Dashboard/FlowTopTalkersZoomRefetch.test.tsx +722 -0
  175. package/Tests/App/Dashboard/FlowViewportGuard.test.tsx +2526 -0
  176. package/Tests/App/Dashboard/HostPagesSlowLoads.test.tsx +872 -0
  177. package/Tests/App/Dashboard/HostPagesTimeRangeZoom.test.tsx +1208 -0
  178. package/Tests/App/Dashboard/IncidentRootCauseTimeRangeZoom.test.tsx +358 -0
  179. package/Tests/App/Dashboard/InfrastructureGraph.test.tsx +100 -9
  180. package/Tests/App/Dashboard/InfrastructureMetricsTabTimeRangeZoom.test.tsx +485 -0
  181. package/Tests/App/Dashboard/InvestigationDrawerCompanionZoom.test.tsx +655 -0
  182. package/Tests/App/Dashboard/InvestigationDrawerZoom.test.tsx +830 -0
  183. package/Tests/App/Dashboard/InvestigationInfrastructureTools.test.tsx +506 -0
  184. package/Tests/App/Dashboard/InvestigationNotStarted.test.tsx +338 -2
  185. package/Tests/App/Dashboard/InvoicesPayInvoice.test.tsx +454 -16
  186. package/Tests/App/Dashboard/KubernetesClusterAiAccessSettings.test.ts +799 -819
  187. package/Tests/App/Dashboard/KubernetesClusterAiAgentOverviewCard.test.tsx +367 -0
  188. package/Tests/App/Dashboard/KubernetesClusterAiAgentStatus.test.ts +1202 -0
  189. package/Tests/App/Dashboard/KubernetesClusterAiDocumentationMarkdown.test.ts +106 -0
  190. package/Tests/App/Dashboard/KubernetesClusterAiNavigation.test.tsx +354 -0
  191. package/Tests/App/Dashboard/KubernetesClusterAiPage.test.tsx +1884 -2054
  192. package/Tests/App/Dashboard/KubernetesClusterAiPageServerParity.test.ts +478 -179
  193. package/Tests/App/Dashboard/KubernetesClusterMetricTooltips.test.ts +50 -0
  194. package/Tests/App/Dashboard/KubernetesClusterOverviewTooltips.test.tsx +2 -1
  195. package/Tests/App/Dashboard/KubernetesClusterOverviewZoom.test.tsx +1217 -0
  196. package/Tests/App/Dashboard/KubernetesClusterPagesZoom.test.tsx +731 -0
  197. package/Tests/App/Dashboard/KubernetesCostsLoadRecovery.test.tsx +869 -0
  198. package/Tests/App/Dashboard/KubernetesCostsZoom.test.tsx +1214 -0
  199. package/Tests/App/Dashboard/KubernetesCostsZoomExactHours.test.tsx +692 -0
  200. package/Tests/App/Dashboard/KubernetesNetworkThroughputReload.test.tsx +902 -0
  201. package/Tests/App/Dashboard/KubernetesNetworkZoomBucketEdges.test.tsx +571 -0
  202. package/Tests/App/Dashboard/KubernetesOverviewZoomBucketEdges.test.tsx +697 -0
  203. package/Tests/App/Dashboard/KubernetesResourceMetricsTabZoom.test.tsx +633 -0
  204. package/Tests/App/Dashboard/LogsExplorerTimeRangeZoom.test.tsx +470 -0
  205. package/Tests/App/Dashboard/LogsInsightsTimeRangeZoom.test.tsx +948 -0
  206. package/Tests/App/Dashboard/LogsViewerPinnedWindowZoom.test.tsx +486 -0
  207. package/Tests/App/Dashboard/MetricChartsBucketInspectorDoubleClick.test.tsx +1001 -0
  208. package/Tests/App/Dashboard/MetricExplorerInvestigationAutoRefresh.test.tsx +514 -0
  209. package/Tests/App/Dashboard/MetricExplorerSavedViewZoomRoundTrip.test.tsx +581 -0
  210. package/Tests/App/Dashboard/MetricExplorerTimeRangeZoom.test.tsx +1162 -0
  211. package/Tests/App/Dashboard/MetricMonitorPreviewZoomLabel.test.ts +72 -0
  212. package/Tests/App/Dashboard/MetricRowSparklineTimeRangeZoom.test.tsx +204 -0
  213. package/Tests/App/Dashboard/MetricViewFirstResultLands.test.tsx +408 -0
  214. package/Tests/App/Dashboard/MetricViewOwnZoomResetPlacement.test.tsx +952 -0
  215. package/Tests/App/Dashboard/MetricViewTimeRange.test.ts +139 -0
  216. package/Tests/App/Dashboard/MetricViewTimeRangeZoom.test.tsx +556 -0
  217. package/Tests/App/Dashboard/MonitorMetricsChartWrappersZoom.test.tsx +552 -0
  218. package/Tests/App/Dashboard/MonitorMetricsTabsTimeRangeZoom.test.tsx +740 -0
  219. package/Tests/App/Dashboard/MonitorOverviewChartsTimeRangeZoom.test.tsx +735 -0
  220. package/Tests/App/Dashboard/MonitorStepPreviewsTimeRangeZoom.test.tsx +835 -0
  221. package/Tests/App/Dashboard/NetworkDeviceTimeRangeZoom.test.tsx +1115 -0
  222. package/Tests/App/Dashboard/OnCallPreventionGuards.test.tsx +186 -17
  223. package/Tests/App/Dashboard/OnCallRulesDeleteGuard.test.tsx +155 -46
  224. package/Tests/App/Dashboard/PasskeySettings.test.tsx +87 -2
  225. package/Tests/App/Dashboard/ProjectAISettingsPages.test.tsx +884 -0
  226. package/Tests/App/Dashboard/ProxmoxNodeRemove.test.tsx +621 -0
  227. package/Tests/App/Dashboard/ProxmoxOverviewSlowLoads.test.tsx +584 -0
  228. package/Tests/App/Dashboard/ProxmoxPagesTimeRangeZoom.test.tsx +823 -0
  229. package/Tests/App/Dashboard/ProxmoxRateChartReload.test.tsx +1253 -0
  230. package/Tests/App/Dashboard/PublicDashboardBoardWideZoom.test.tsx +605 -0
  231. package/Tests/App/Dashboard/RealRowsLineChart.tsx +250 -0
  232. package/Tests/App/Dashboard/RechartsHostsPlotCursor.test.tsx +513 -0
  233. package/Tests/App/Dashboard/RemediationSuggestionCardDescription.test.tsx +209 -0
  234. package/Tests/App/Dashboard/RemediationSuggestionCardResource.test.tsx +317 -0
  235. package/Tests/App/Dashboard/ResourceAiAccessSettingsUtil.test.ts +921 -0
  236. package/Tests/App/Dashboard/ResourceAiAgentInstall.test.ts +860 -0
  237. package/Tests/App/Dashboard/ResourceAiAgentPage.test.tsx +1721 -0
  238. package/Tests/App/Dashboard/ResourceAiAgentStatus.test.ts +1002 -0
  239. package/Tests/App/Dashboard/ResourceAiInsightsPage.test.tsx +931 -0
  240. package/Tests/App/Dashboard/ResourceAiNavigation.test.tsx +575 -0
  241. package/Tests/App/Dashboard/RowActionPlacementOverrides.test.tsx +865 -0
  242. package/Tests/App/Dashboard/RumOverviewTimeRangeZoom.test.tsx +877 -0
  243. package/Tests/App/Dashboard/RunbookStepTypeMaps.test.ts +38 -7
  244. package/Tests/App/Dashboard/RunnerKubernetesAgentForm.test.tsx +20 -4
  245. package/Tests/App/Dashboard/RunnerSupersededByAiAgentNote.test.tsx +296 -0
  246. package/Tests/App/Dashboard/SecurityEventConnectionsTable.test.tsx +146 -36
  247. package/Tests/App/Dashboard/ServiceDatabaseSlowLoads.test.tsx +1166 -0
  248. package/Tests/App/Dashboard/ServiceMapGraph.test.tsx +1243 -104
  249. package/Tests/App/Dashboard/ServiceOverviewTimeRangeZoom.test.tsx +792 -0
  250. package/Tests/App/Dashboard/SloBudgetBurnDownZoom.test.tsx +771 -0
  251. package/Tests/App/Dashboard/SloBudgetBurnDownZoomAffordances.test.tsx +380 -0
  252. package/Tests/App/Dashboard/SloEventMetricsBarChartZoom.test.tsx +369 -0
  253. package/Tests/App/Dashboard/SloHistoryChartsTimeRangeZoom.test.tsx +694 -0
  254. package/Tests/App/Dashboard/SloHistoryChartsZoomAffordances.test.tsx +497 -0
  255. package/Tests/App/Dashboard/SloMetricsTimeRangeZoom.test.tsx +541 -0
  256. package/Tests/App/Dashboard/SlowLoadHarness.ts +123 -0
  257. package/Tests/App/Dashboard/SnapshotExplorerPrimaryZoom.test.tsx +1194 -0
  258. package/Tests/App/Dashboard/SnapshotViewerPinnedWindow.test.tsx +620 -0
  259. package/Tests/App/Dashboard/TelemetrySnapshotPanelTimeRangeZoom.test.tsx +765 -0
  260. package/Tests/App/Dashboard/TelemetrySnapshotZoom.test.tsx +516 -0
  261. package/Tests/App/Dashboard/TelemetrySnapshotZoomAcrossTabs.test.tsx +827 -0
  262. package/Tests/App/Dashboard/TimeRangeZoomPageHarness.tsx +463 -0
  263. package/Tests/App/Dashboard/TraceChartEditorStatusSplit.test.tsx +543 -0
  264. package/Tests/App/Dashboard/TraceStatusPresentation.test.tsx +967 -0
  265. package/Tests/App/Dashboard/TraceTimelineMinimapZoom.test.tsx +315 -0
  266. package/Tests/App/Dashboard/TracesAnalyticsStatusSplit.test.tsx +778 -0
  267. package/Tests/App/Dashboard/TracesAnalyticsViewZoom.test.tsx +349 -0
  268. package/Tests/App/Dashboard/TracesExplorerTimeRangeZoom.test.tsx +551 -0
  269. package/Tests/App/Dashboard/TracesStatusChartAndFacet.test.tsx +687 -0
  270. package/Tests/App/Dashboard/TracesViewerStaleResponses.test.tsx +458 -0
  271. package/Tests/App/Dashboard/UptimeStripsExcludedFromZoom.test.tsx +406 -0
  272. package/Tests/App/Dashboard/VMwareOverviewSlowLoads.test.tsx +535 -0
  273. package/Tests/App/Dashboard/VMwarePagesTimeRangeZoom.test.tsx +606 -0
  274. package/Tests/App/Dashboard/ValueWidgetSparklineStaleHover.test.tsx +1292 -0
  275. package/Tests/App/Dashboard/ValueWidgetSparklineZoom.test.tsx +765 -0
  276. package/Tests/App/StatusPage/StatusPageResourceExplorer.test.tsx +98 -0
  277. package/Tests/Models/DatabaseModels/DatabaseServerModels.test.ts +33 -1
  278. package/Tests/Models/DatabaseModels/KubernetesAiAgentModel.test.ts +295 -0
  279. package/Tests/Models/DatabaseModels/ResourceAiAccessColumns.test.ts +738 -0
  280. package/Tests/Models/DatabaseModels/ResourceAiAgentModel.test.ts +842 -0
  281. package/Tests/Models/DatabaseModels/TeamComplianceSettingModel.test.ts +347 -0
  282. package/Tests/Server/API/AIChatRouteAuthorization.test.ts +58 -0
  283. package/Tests/Server/API/AIGenerationProjectAIToggle.test.ts +1 -1
  284. package/Tests/Server/API/AIInvestigationAPI.test.ts +123 -45
  285. package/Tests/Server/API/AutoRemediationApproveClusterRound.test.ts +1329 -0
  286. package/Tests/Server/API/AutoRemediationApproveKubectlRecheck.test.ts +25 -2
  287. package/Tests/Server/API/AutoRemediationApproveResourceRound.test.ts +835 -0
  288. package/Tests/Server/API/KubernetesClusterAiAccessAPI.test.ts +2 -0
  289. package/Tests/Server/API/KubernetesClusterAiAccessAPIAgent.test.ts +903 -0
  290. package/Tests/Server/API/MicrosoftTeamsChatsRefreshRoute.test.ts +667 -0
  291. package/Tests/Server/API/MicrosoftTeamsManifest.test.ts +294 -6
  292. package/Tests/Server/API/ProxmoxResourceAPIRemoveNode.test.ts +1932 -0
  293. package/Tests/Server/API/ResourceAiAccessAPI.test.ts +2730 -0
  294. package/Tests/Server/Infrastructure/GlobalCache.test.ts +195 -0
  295. package/Tests/Server/Infrastructure/Postgres/AddDatabaseServerTablesMigration.test.ts +60 -0
  296. package/Tests/Server/Infrastructure/Postgres/AddKubernetesAiAgentAndAiDefaultsMigration.test.ts +623 -0
  297. package/Tests/Server/Infrastructure/Postgres/AddKubernetesClusterAiAccessMigration.test.ts +87 -10
  298. package/Tests/Server/Infrastructure/Postgres/AddResourceAiAgentsMigration.test.ts +659 -0
  299. package/Tests/Server/Infrastructure/Postgres/AddTeamComplianceRuleScopeMigration.test.ts +622 -0
  300. package/Tests/Server/Infrastructure/SemaphoreMutex.test.ts +31 -0
  301. package/Tests/Server/Middleware/TelemetryIngestBrowserKey.test.ts +119 -4
  302. package/Tests/Server/Middleware/TelemetryIngestKubernetesAgentRunnerPinnedKey.test.ts +196 -2
  303. package/Tests/Server/Middleware/TelemetryIngestKubernetesAgentRunnerRateLimit.test.ts +123 -2
  304. package/Tests/Server/Services/AIServiceBalanceBlocker.test.ts +282 -0
  305. package/Tests/Server/Services/AlertMonitorClearWritesNull.test.ts +409 -0
  306. package/Tests/Server/Services/AlertMonitorEditFeed.test.ts +474 -0
  307. package/Tests/Server/Services/AlertMonitorEditGuard.test.ts +610 -0
  308. package/Tests/Server/Services/AutoRemediationClusterRuleEngine.test.ts +2 -0
  309. package/Tests/Server/Services/AutoRemediationResourceRuleEngine.test.ts +1279 -0
  310. package/Tests/Server/Services/AutoRemediationRuleCommandRunners.test.ts +2 -1
  311. package/Tests/Server/Services/DatabaseServerService.test.ts +55 -0
  312. package/Tests/Server/Services/GroupTelemetryUsageExcludeNames.test.ts +299 -0
  313. package/Tests/Server/Services/HostServiceFindOrCreateMemo.test.ts +44 -0
  314. package/Tests/Server/Services/IncidentAffectedMonitorsEdit.test.ts +1360 -0
  315. package/Tests/Server/Services/IncidentAlertSloLinkSurvivesEdit.test.ts +3 -1
  316. package/Tests/Server/Services/IncidentSeverityDeletePausesComplianceRules.test.ts +488 -0
  317. package/Tests/Server/Services/KubernetesAiAgentJobService.test.ts +329 -0
  318. package/Tests/Server/Services/KubernetesAiAgentServiceHelpers.test.ts +618 -0
  319. package/Tests/Server/Services/KubernetesAiAgentServiceLifecycle.test.ts +1444 -0
  320. package/Tests/Server/Services/KubernetesAiAgentServiceRegister.test.ts +1666 -0
  321. package/Tests/Server/Services/KubernetesClusterAiAccessDeletedRunnerRemedy.test.ts +140 -111
  322. package/Tests/Server/Services/KubernetesClusterAiAccessGapNextSteps.test.ts +267 -105
  323. package/Tests/Server/Services/KubernetesClusterAiAccessLegacySuperseded.test.ts +440 -0
  324. package/Tests/Server/Services/KubernetesClusterAiAccessService.test.ts +567 -411
  325. package/Tests/Server/Services/KubernetesClusterAiAccessTarget.test.ts +1168 -0
  326. package/Tests/Server/Services/KubernetesClusterAiSettingsPermission.test.ts +410 -12
  327. package/Tests/Server/Services/KubernetesClusterServiceAiAccessFeed.test.ts +55 -0
  328. package/Tests/Server/Services/MonitorProbeServiceIntervalScheduling.test.ts +45 -6
  329. package/Tests/Server/Services/ProjectServiceNewProjectAiDefaults.test.ts +288 -0
  330. package/Tests/Server/Services/ProxmoxResourcePostgres.test.ts +5633 -0
  331. package/Tests/Server/Services/ProxmoxResourceService.test.ts +6458 -5
  332. package/Tests/Server/Services/RecordingRuleReservedMetricName.test.ts +171 -0
  333. package/Tests/Server/Services/ResourceAiAccessChangeAuthorization.test.ts +256 -0
  334. package/Tests/Server/Services/ResourceAiAccessService.test.ts +1373 -0
  335. package/Tests/Server/Services/ResourceAiAgentJobService.test.ts +375 -0
  336. package/Tests/Server/Services/ResourceAiAgentServiceHelpers.test.ts +1114 -0
  337. package/Tests/Server/Services/ResourceAiAgentServiceLifecycle.test.ts +1050 -0
  338. package/Tests/Server/Services/ResourceAiAgentServiceRegister.test.ts +2251 -0
  339. package/Tests/Server/Services/ResourceAiSettingsCreate.test.ts +373 -0
  340. package/Tests/Server/Services/ResourceAiSettingsPermission.test.ts +1042 -0
  341. package/Tests/Server/Services/ResourceServiceDeleteCleansUpAi.test.ts +319 -0
  342. package/Tests/Server/Services/RunnerJobEnqueueKubectl.test.ts +153 -15
  343. package/Tests/Server/Services/RunnerJobEnqueueKubectlAiAgent.test.ts +620 -0
  344. package/Tests/Server/Services/RunnerJobEnqueueResourceCommand.test.ts +986 -0
  345. package/Tests/Server/Services/RunnerJobKubectlWriteScope.test.ts +170 -3
  346. package/Tests/Server/Services/RunnerJobResourceCommandLane.test.ts +211 -0
  347. package/Tests/Server/Services/RunnerJobResourceCommandTimeoutAndRedaction.test.ts +352 -0
  348. package/Tests/Server/Services/RunnerJobTimeoutReason.test.ts +72 -0
  349. package/Tests/Server/Services/RunnerServiceAgentName.test.ts +6 -4
  350. package/Tests/Server/Services/ScheduledMaintenanceAttachmentEdit.test.ts +2113 -0
  351. package/Tests/Server/Services/ScheduledMaintenanceMonitorRelease.test.ts +972 -0
  352. package/Tests/Server/Services/TeamComplianceSettingPostgres.test.ts +2237 -0
  353. package/Tests/Server/Services/TeamComplianceSettingService.test.ts +3988 -138
  354. package/Tests/Server/Services/TelemetryUsageBillingSloExclusion.test.ts +218 -0
  355. package/Tests/Server/TestingUtils/Services/FakeRunnerJobCount.ts +145 -0
  356. package/Tests/Server/Utils/AI/ClusterAccessContext.test.ts +107 -1
  357. package/Tests/Server/Utils/AI/IncidentPostmortemRunner.test.ts +351 -0
  358. package/Tests/Server/Utils/AI/InvestigationClusterAccessWiring.test.ts +2 -0
  359. package/Tests/Server/Utils/AI/InvestigationEligibility.test.ts +310 -0
  360. package/Tests/Server/Utils/AI/InvestigationInfrastructureAccessWiring.test.ts +453 -0
  361. package/Tests/Server/Utils/AI/InvestigationInfrastructureReport.test.ts +721 -0
  362. package/Tests/Server/Utils/AI/KubectlInvestigationToolkit.test.ts +70 -0
  363. package/Tests/Server/Utils/AI/KubectlJobRunnerFinishedJob.test.ts +58 -0
  364. package/Tests/Server/Utils/AI/RemediationCommandTools.test.ts +94 -1
  365. package/Tests/Server/Utils/AI/RemediationCommandToolsKubectl.test.ts +2 -0
  366. package/Tests/Server/Utils/AI/RemediationCommandToolsKubectlOutcome.test.ts +2 -0
  367. package/Tests/Server/Utils/AI/RemediationCommandToolsLiveCluster.test.ts +2 -0
  368. package/Tests/Server/Utils/AI/RemediationCommandToolsResource.test.ts +1530 -0
  369. package/Tests/Server/Utils/AI/RemediationCommandToolsRunnerScope.test.ts +125 -1
  370. package/Tests/Server/Utils/AI/RemediationExecutionRunner.test.ts +252 -3
  371. package/Tests/Server/Utils/AI/RemediationExecutionRunnerClusterMode.test.ts +2 -0
  372. package/Tests/Server/Utils/AI/RemediationExecutionRunnerProjectGates.test.ts +240 -0
  373. package/Tests/Server/Utils/AI/RemediationExecutionRunnerResourceMode.test.ts +1249 -0
  374. package/Tests/Server/Utils/AI/RemediationKubectlCopyParity.test.ts +2 -0
  375. package/Tests/Server/Utils/AI/RemediationPlanRunner.test.ts +117 -0
  376. package/Tests/Server/Utils/AI/RemediationResourceCopyParity.test.ts +463 -0
  377. package/Tests/Server/Utils/AI/ResourceAccess/InfrastructureInvestigationToolkit.test.ts +722 -0
  378. package/Tests/Server/Utils/AI/ResourceAccess/ResourceAccessContext.test.ts +266 -0
  379. package/Tests/Server/Utils/AI/ResourceAccess/ResourceAiAccessSettings.test.ts +1220 -0
  380. package/Tests/Server/Utils/AI/ResourceAccess/ResourceAiDeleteCleanup.test.ts +569 -0
  381. package/Tests/Server/Utils/AI/ResourceAccess/ResourceCommandJobRunner.test.ts +830 -0
  382. package/Tests/Server/Utils/AutoRemediation/CommandPlanExecutor.test.ts +2 -0
  383. package/Tests/Server/Utils/AutoRemediation/CommandPlanExecutorResource.test.ts +717 -0
  384. package/Tests/Server/Utils/AutoRemediation/CommandPlanExecutorUnknownRun.test.ts +2 -0
  385. package/Tests/Server/Utils/AutoRemediation/CommandPlanVerification.test.ts +2 -0
  386. package/Tests/Server/Utils/AutoRemediation/RemediationVerifierResourceFollowUp.test.ts +338 -0
  387. package/Tests/Server/Utils/Monitor/Criteria/SessionReplayBudgetTemplateCriteria.test.ts +422 -0
  388. package/Tests/Server/Utils/SessionReplay/SessionReplayBudgetMetrics.test.ts +1647 -0
  389. package/Tests/Server/Utils/SessionReplay/SessionReplayUsage.test.ts +143 -1
  390. package/Tests/Server/Utils/Telemetry/HostAssetAttributesFromOtlp.test.ts +68 -0
  391. package/Tests/Server/Utils/Telemetry/InventoryMirrorPostgres.test.ts +370 -0
  392. package/Tests/Server/Utils/Telemetry/NetworkDeviceInventoryAttributes.test.ts +522 -0
  393. package/Tests/Server/Utils/Telemetry/ProxmoxCephSnapshotScan.test.ts +155 -0
  394. package/Tests/Server/Utils/Telemetry/ProxmoxNativeNodeLiveness.test.ts +8631 -0
  395. package/Tests/Server/Utils/Telemetry/ProxmoxNativePush.test.ts +2852 -0
  396. package/Tests/Server/Utils/Telemetry/TelemetryEntity.test.ts +367 -1
  397. package/Tests/Server/Utils/Telemetry/TelemetryIngestionKeyGuard.test.ts +75 -3
  398. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsAccountNotLinked.test.ts +1093 -0
  399. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsActivityDeduplicator.test.ts +1049 -0
  400. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCardChoices.test.ts +1676 -0
  401. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCreateCards.test.ts +2884 -0
  402. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCreateCommands.test.ts +2490 -0
  403. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCreateSubmit.test.ts +2293 -0
  404. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCreateSubmitServerTimezone.test.ts +386 -0
  405. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsMessageSize.test.ts +1477 -0
  406. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsReplies.test.ts +1967 -0
  407. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsStripHtmlTags.test.ts +92 -0
  408. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsSubmittedFormRemoval.test.ts +1819 -0
  409. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsTimezone.test.ts +1033 -0
  410. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsTimezoneServerZone.test.ts +610 -0
  411. package/Tests/Server/Utils/Workspace/MicrosoftTeamsBotMessageHandling.test.ts +2610 -0
  412. package/Tests/Server/Utils/Workspace/MicrosoftTeamsChats.test.ts +25 -0
  413. package/Tests/Server/Utils/Workspace/MicrosoftTeamsCreateCommandsEndToEnd.test.ts +4672 -0
  414. package/Tests/Server/Utils/Workspace/MicrosoftTeamsGroupChatNames.test.ts +3119 -0
  415. package/Tests/Server/Utils/Workspace/MicrosoftTeamsGroupChatNamesPostgres.test.ts +1721 -0
  416. package/Tests/Server/Utils/Workspace/WorkspaceCreateProjectReferences.test.ts +190 -65
  417. package/Tests/Types/AutoRemediation/AiRemediationCommandPlan.test.ts +10 -4
  418. package/Tests/Types/AutoRemediation/AiRemediationCommandPlanResourceCommand.test.ts +305 -0
  419. package/Tests/Types/Kubernetes/KubernetesAgentChartAiAccess.test.ts +581 -113
  420. package/Tests/Types/Kubernetes/KubernetesAiAgentAccessContract.test.ts +510 -0
  421. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationCatalog.test.ts +277 -13
  422. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationUtil.test.ts +113 -0
  423. package/Tests/Types/Monitor/RumAlertTemplates.test.ts +773 -2
  424. package/Tests/Types/ResourceAiAgent/AiResourceType.test.ts +419 -0
  425. package/Tests/Types/ResourceAiAgent/ResourceAiAccess.test.ts +517 -0
  426. package/Tests/Types/Runbook/RunbookStepType.test.ts +147 -8
  427. package/Tests/Types/Team/ComplianceRule.test.ts +1029 -0
  428. package/Tests/Types/Team/TeamComplianceStatus.test.ts +176 -0
  429. package/Tests/UI/Components/ActionButton/RowActions.test.tsx +641 -0
  430. package/Tests/UI/Components/ActionButton/RowActionsAccessibility.test.tsx +414 -0
  431. package/Tests/UI/Components/ActionButton/SplitActionButtons.test.ts +565 -0
  432. package/Tests/UI/Components/Charts/BarChartDragToSelect.test.tsx +386 -0
  433. package/Tests/UI/Components/Charts/ChartDoubleClickResetWithoutDblclick.test.tsx +1025 -0
  434. package/Tests/UI/Components/Charts/ChartGroupPageZoomHint.test.tsx +196 -0
  435. package/Tests/UI/Components/Charts/ChartPlaceholdersTakeDoubleClick.test.tsx +1861 -0
  436. package/Tests/UI/Components/Charts/ChartPlotClicksWhileResetArmed.test.tsx +382 -0
  437. package/Tests/UI/Components/Charts/ChartRangeSelection.test.tsx +704 -0
  438. package/Tests/UI/Components/Charts/ChartRangeSelectionDoubleClickReset.test.tsx +1481 -0
  439. package/Tests/UI/Components/Charts/ChartRangeSelectionRealRecharts.test.tsx +918 -0
  440. package/Tests/UI/Components/Charts/ChartRowBucketStart.test.ts +350 -0
  441. package/Tests/UI/Components/Charts/DoubleClickReset.test.tsx +996 -0
  442. package/Tests/UI/Components/Charts/HistogramDoubleClickResetRealRecharts.test.tsx +965 -0
  443. package/Tests/UI/Components/Charts/HistogramDriftThenRefetch.test.tsx +277 -0
  444. package/Tests/UI/Components/Charts/HistogramRangeSelectionDataChange.test.tsx +1757 -0
  445. package/Tests/UI/Components/Charts/HistogramRangeSelectionRealRecharts.test.tsx +560 -0
  446. package/Tests/UI/Components/Charts/TimeRangeZoom/ChartPageTimeRangeZoom.test.tsx +513 -0
  447. package/Tests/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomContext.test.tsx +845 -0
  448. package/Tests/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomHint.test.tsx +119 -0
  449. package/Tests/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomHintTailwind.test.tsx +274 -0
  450. package/Tests/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomUtil.test.ts +433 -0
  451. package/Tests/UI/Components/Charts/TimeRangeZoom/UseTimeRangeZoom.test.tsx +863 -0
  452. package/Tests/UI/Components/Charts/TimeRangeZoom/ZoomDocsPromises.test.tsx +1147 -0
  453. package/Tests/UI/Components/DisabledButtonTooltip.test.tsx +14 -3
  454. package/Tests/UI/Components/EntityDropdownEscape.test.tsx +454 -0
  455. package/Tests/UI/Components/EntityDropdownFocusLeave.test.tsx +420 -0
  456. package/Tests/UI/Components/Forms/FormFieldDropdownDisabled.test.tsx +233 -0
  457. package/Tests/UI/Components/Input.test.tsx +80 -1
  458. package/Tests/UI/Components/LogsAnalyticsViewZoom.test.tsx +947 -0
  459. package/Tests/UI/Components/LogsHistogramDragTooltip.test.tsx +17 -5
  460. package/Tests/UI/Components/LogsViewerAnalyticsZoom.test.tsx +501 -0
  461. package/Tests/UI/Components/LogsViewerHistogramZoom.test.tsx +1 -1
  462. package/Tests/UI/Components/ModelTable/BaseModelTableBulkDelete.test.tsx +6 -4
  463. package/Tests/UI/Components/ModelTable/BaseModelTablePermissionGating.test.tsx +60 -8
  464. package/Tests/UI/Components/ModelTable/BaseModelTableRowActions.test.tsx +476 -0
  465. package/Tests/UI/Components/ModelTable/ModelTableWrapContent.test.tsx +7 -5
  466. package/Tests/UI/Components/MoreMenuPortal.test.tsx +623 -0
  467. package/Tests/UI/Components/StatusPage/ResourceGridActions.test.tsx +538 -0
  468. package/Tests/UI/Components/StatusPage/ResourceGridSelection.test.tsx +14 -4
  469. package/Tests/UI/Components/TableCellWrapping.test.tsx +3 -3
  470. package/Tests/UI/Components/TableRowActions.test.tsx +472 -0
  471. package/Tests/UI/Components/TelemetryHistogramDragTooltip.test.tsx +1 -1
  472. package/Tests/UI/Components/TelemetryViewer/TelemetryViewerHistogramClickZoom.test.tsx +1 -1
  473. package/Tests/UI/Components/TelemetryViewer/TelemetryViewerZoomScope.test.tsx +470 -0
  474. package/Tests/UI/Components/TelemetryViewer/UseViewerTimeRangeZoom.test.tsx +571 -0
  475. package/Tests/UI/Components/TelemetryViewer/ViewerDoubleClickResetUnderDataChange.test.tsx +928 -0
  476. package/Tests/UI/Components/TelemetryViewer/ViewerFollowsEnclosingZoom.test.tsx +1569 -0
  477. package/Tests/UI/Components/Toggle.test.tsx +66 -0
  478. package/Tests/UI/MicrosoftTeams/MicrosoftTeamsChatsCardRefreshNames.test.tsx +1023 -0
  479. package/Tests/UI/Rum/ChunkLoader.test.ts +201 -0
  480. package/Tests/UI/Rum/FidelityNotices.test.ts +54 -0
  481. package/Tests/UI/Rum/RecordingHealthDashboard.test.tsx +769 -1
  482. package/Tests/UI/Rum/ReplayCorrelationPanel.test.tsx +72 -0
  483. package/Tests/UI/Rum/ReplayFrameCapture.test.ts +1009 -9
  484. package/Tests/UI/Rum/ReplayRecordedAssets.test.ts +1069 -0
  485. package/Tests/UI/Rum/ReplayStage.test.tsx +913 -2
  486. package/Tests/UI/Rum/SessionReplayTable.test.tsx +443 -8
  487. package/Tests/UI/Rum/SessionReplayUsersTable.test.tsx +439 -9
  488. package/Tests/UI/Rum/UserFlowTablesRowActions.test.tsx +501 -0
  489. package/Tests/Utils/AiRemediation/KubectlPolicyChartRbacParity.test.ts +47 -19
  490. package/Tests/Utils/AiRemediation/KubectlWriteScopeCallerParity.test.ts +2 -0
  491. package/Tests/Utils/AiRemediation/KubernetesAiAgentPolicyCopyParity.test.ts +396 -0
  492. package/Tests/Utils/AiRemediation/Resource/CephCommandPolicy.test.ts +1653 -0
  493. package/Tests/Utils/AiRemediation/Resource/CephOutputRedaction.test.ts +204 -0
  494. package/Tests/Utils/AiRemediation/Resource/DatabaseCommandPolicy.test.ts +1077 -0
  495. package/Tests/Utils/AiRemediation/Resource/DatabaseDiagnosticCatalog.test.ts +558 -0
  496. package/Tests/Utils/AiRemediation/Resource/DatabaseQueryRedactor.test.ts +834 -0
  497. package/Tests/Utils/AiRemediation/Resource/DockerCliGrammar.test.ts +747 -0
  498. package/Tests/Utils/AiRemediation/Resource/DockerEngineCommandPolicy.test.ts +1259 -0
  499. package/Tests/Utils/AiRemediation/Resource/DockerOutputRedaction.test.ts +386 -0
  500. package/Tests/Utils/AiRemediation/Resource/DockerSwarmCommandPolicy.test.ts +782 -0
  501. package/Tests/Utils/AiRemediation/Resource/GovcCommandPolicy.test.ts +2259 -0
  502. package/Tests/Utils/AiRemediation/Resource/HostCommandPolicy.test.ts +2019 -0
  503. package/Tests/Utils/AiRemediation/Resource/ProxmoxCommandPolicy.test.ts +2497 -0
  504. package/Tests/Utils/AiRemediation/Resource/ResourceCommandPolicy.test.ts +1305 -0
  505. package/Tests/Utils/AiRemediation/Resource/ResourceCommandPolicyCore.test.ts +585 -0
  506. package/Tests/Utils/AiRemediation/Resource/ResourceOutputRedactor.test.ts +334 -0
  507. package/Tests/Utils/AiRemediation/Resource/ResourceOutputRedactorCopyParity.test.ts +98 -0
  508. package/Tests/Utils/AiRemediation/Resource/ResourcePolicyImportClosure.test.ts +391 -0
  509. package/Tests/Utils/AiRemediation/ResourceAiAgentPolicyCopyParity.test.ts +497 -0
  510. package/Tests/Utils/Dashboard/DashboardTimeRangeZoom.test.ts +104 -1
  511. package/Tests/Utils/SessionReplay/SessionReplayBudgetMetricType.test.ts +383 -0
  512. package/Types/AI/AIRunType.ts +9 -4
  513. package/Types/AI/InvestigationNotStartedReason.ts +6 -0
  514. package/Types/AI/ResourceAiAccessApi.ts +140 -0
  515. package/Types/AI/ResourceAiAccessPermissions.ts +126 -0
  516. package/Types/AutoRemediation/AiRemediationCommandPlan.ts +197 -18
  517. package/Types/AutoRemediation/AiRemediationCommandPolicyVerdict.ts +28 -0
  518. package/Types/Dashboard/DashboardComponents/DashboardTraceChartComponent.ts +4 -2
  519. package/Types/Kubernetes/KubernetesClusterAiAccess.ts +263 -32
  520. package/Types/Kubernetes/KubernetesClusterAiAccessPermissions.ts +20 -16
  521. package/Types/Kubernetes/KubernetesClusterAiInsights.ts +81 -0
  522. package/Types/Monitor/ProxmoxAlertTemplates.ts +4 -4
  523. package/Types/Monitor/Recommendation/MonitorRecommendationCatalog.ts +55 -22
  524. package/Types/Monitor/Recommendation/MonitorRecommendationTypes.ts +35 -3
  525. package/Types/Monitor/RumAlertTemplates.ts +351 -4
  526. package/Types/ResourceAiAgent/AiResourceType.ts +310 -0
  527. package/Types/ResourceAiAgent/ResourceAiAccess.ts +574 -0
  528. package/Types/Rum/SessionReplayBudgetMetricType.ts +47 -0
  529. package/Types/Runbook/RunbookStepType.ts +29 -0
  530. package/Types/Team/ComplianceNotificationChannel.ts +22 -0
  531. package/Types/Team/ComplianceRule.ts +434 -0
  532. package/Types/Team/ComplianceRuleType.ts +21 -0
  533. package/Types/Team/TeamComplianceStatus.ts +81 -0
  534. package/Types/Telemetry/TelemetryIngestSurface.ts +40 -0
  535. package/UI/Components/ActionButton/ActionButtonSchema.ts +21 -0
  536. package/UI/Components/ActionButton/RowActions.tsx +238 -0
  537. package/UI/Components/ActionButton/SplitActionButtons.ts +173 -0
  538. package/UI/Components/Charts/Area/AreaChart.tsx +32 -2
  539. package/UI/Components/Charts/Bar/BarChart.tsx +40 -3
  540. package/UI/Components/Charts/ChartGroup/ChartGroup.tsx +37 -11
  541. package/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.tsx +80 -243
  542. package/UI/Components/Charts/ChartLibrary/BarChart/BarChart.tsx +122 -31
  543. package/UI/Components/Charts/ChartLibrary/LineChart/LineChart.tsx +95 -244
  544. package/UI/Components/Charts/ChartLibrary/Utils/DoubleClick.ts +214 -0
  545. package/UI/Components/Charts/ChartLibrary/Utils/UseChartBucketsChange.ts +32 -0
  546. package/UI/Components/Charts/ChartLibrary/Utils/UseChartRangeSelection.ts +599 -0
  547. package/UI/Components/Charts/Line/LineChart.tsx +32 -2
  548. package/UI/Components/Charts/TimeRangeZoom/ResetTimeRangeZoomButton.tsx +70 -0
  549. package/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomContext.tsx +226 -0
  550. package/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomHint.tsx +73 -0
  551. package/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomUtil.ts +163 -0
  552. package/UI/Components/Charts/TimeRangeZoom/UseTimeRangeZoom.ts +215 -0
  553. package/UI/Components/Charts/Utils/DataPoint.ts +20 -8
  554. package/UI/Components/Charts/Utils/XAxis.ts +75 -0
  555. package/UI/Components/Charts/Utils/useHistogramRangeSelection.ts +493 -59
  556. package/UI/Components/EntityDropdown/EntityDropdown.tsx +107 -11
  557. package/UI/Components/Forms/Fields/FormField.tsx +10 -0
  558. package/UI/Components/Input/Input.tsx +90 -57
  559. package/UI/Components/List/ListRow.tsx +8 -74
  560. package/UI/Components/LogsViewer/LogsViewer.tsx +40 -11
  561. package/UI/Components/LogsViewer/components/LogTimeRangePicker.tsx +22 -6
  562. package/UI/Components/LogsViewer/components/LogsAnalyticsView.tsx +209 -6
  563. package/UI/Components/LogsViewer/components/LogsHistogram.tsx +127 -84
  564. package/UI/Components/LogsViewer/components/LogsViewerToolbar.tsx +3 -0
  565. package/UI/Components/ModelTable/BaseModelTable.tsx +12 -1
  566. package/UI/Components/MoreMenu/MoreMenu.tsx +365 -30
  567. package/UI/Components/MoreMenu/MoreMenuItem.tsx +70 -17
  568. package/UI/Components/OrderedStatesList/Item.tsx +7 -72
  569. package/UI/Components/StatusPage/ResourceGrid.tsx +126 -77
  570. package/UI/Components/Table/TableBody.tsx +8 -2
  571. package/UI/Components/Table/TableRow.tsx +20 -140
  572. package/UI/Components/TelemetryViewer/TelemetryViewer.tsx +41 -10
  573. package/UI/Components/TelemetryViewer/components/TelemetryHistogram.tsx +47 -9
  574. package/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.tsx +25 -6
  575. package/UI/Components/TelemetryViewer/types.ts +2 -0
  576. package/UI/Components/TelemetryViewer/useViewerTimeRangeZoom.ts +448 -0
  577. package/UI/Components/Toggle/Toggle.tsx +26 -0
  578. package/UI/Components/Tooltip/Tooltip.tsx +8 -1
  579. package/UI/Styles/Theme.css +14 -2
  580. package/Utils/AI/InvestigationReport.ts +121 -15
  581. package/Utils/AiRemediation/CommandPolicy.ts +1 -1
  582. package/Utils/AiRemediation/KubectlPolicy.ts +1 -1
  583. package/Utils/AiRemediation/Resource/CephCommandPolicy.ts +1936 -0
  584. package/Utils/AiRemediation/Resource/DatabaseCommandPolicy.ts +166 -0
  585. package/Utils/AiRemediation/Resource/DatabaseDiagnosticCatalog.ts +1532 -0
  586. package/Utils/AiRemediation/Resource/DatabaseQueryRedactor.ts +1394 -0
  587. package/Utils/AiRemediation/Resource/DockerCliGrammar.ts +1839 -0
  588. package/Utils/AiRemediation/Resource/DockerEngineCommandPolicy.ts +490 -0
  589. package/Utils/AiRemediation/Resource/DockerSwarmCommandPolicy.ts +687 -0
  590. package/Utils/AiRemediation/Resource/GovcCommandPolicy.ts +2096 -0
  591. package/Utils/AiRemediation/Resource/HostCommandPolicy.ts +2982 -0
  592. package/Utils/AiRemediation/Resource/ProxmoxCommandPolicy.ts +1679 -0
  593. package/Utils/AiRemediation/Resource/ResourceCommandPolicy.ts +851 -0
  594. package/Utils/AiRemediation/Resource/ResourceCommandPolicyCore.ts +593 -0
  595. package/Utils/AiRemediation/Resource/ResourceOutputRedactor.ts +2812 -0
  596. package/Utils/Dashboard/DashboardTimeRangeZoom.ts +46 -2
  597. package/Utils/SessionReplay/SessionReplayBudgetMetricType.ts +184 -0
  598. package/build/dist/Models/DatabaseModels/AutoRemediationSuggestion.js +62 -0
  599. package/build/dist/Models/DatabaseModels/AutoRemediationSuggestion.js.map +1 -1
  600. package/build/dist/Models/DatabaseModels/CephCluster.js +219 -0
  601. package/build/dist/Models/DatabaseModels/CephCluster.js.map +1 -1
  602. package/build/dist/Models/DatabaseModels/DatabaseServer.js +219 -0
  603. package/build/dist/Models/DatabaseModels/DatabaseServer.js.map +1 -1
  604. package/build/dist/Models/DatabaseModels/DockerHost.js +219 -0
  605. package/build/dist/Models/DatabaseModels/DockerHost.js.map +1 -1
  606. package/build/dist/Models/DatabaseModels/DockerSwarmCluster.js +219 -0
  607. package/build/dist/Models/DatabaseModels/DockerSwarmCluster.js.map +1 -1
  608. package/build/dist/Models/DatabaseModels/GlobalConfig.js +1 -1
  609. package/build/dist/Models/DatabaseModels/GlobalConfig.js.map +1 -1
  610. package/build/dist/Models/DatabaseModels/Host.js +219 -0
  611. package/build/dist/Models/DatabaseModels/Host.js.map +1 -1
  612. package/build/dist/Models/DatabaseModels/Index.js +4 -0
  613. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  614. package/build/dist/Models/DatabaseModels/KubernetesAiAgent.js +385 -0
  615. package/build/dist/Models/DatabaseModels/KubernetesAiAgent.js.map +1 -0
  616. package/build/dist/Models/DatabaseModels/KubernetesCluster.js +3 -3
  617. package/build/dist/Models/DatabaseModels/KubernetesCluster.js.map +1 -1
  618. package/build/dist/Models/DatabaseModels/PodmanHost.js +219 -0
  619. package/build/dist/Models/DatabaseModels/PodmanHost.js.map +1 -1
  620. package/build/dist/Models/DatabaseModels/Project.js +56 -5
  621. package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
  622. package/build/dist/Models/DatabaseModels/ProxmoxCluster.js +219 -0
  623. package/build/dist/Models/DatabaseModels/ProxmoxCluster.js.map +1 -1
  624. package/build/dist/Models/DatabaseModels/ProxmoxResource.js +51 -1
  625. package/build/dist/Models/DatabaseModels/ProxmoxResource.js.map +1 -1
  626. package/build/dist/Models/DatabaseModels/ResourceAiAgent.js +439 -0
  627. package/build/dist/Models/DatabaseModels/ResourceAiAgent.js.map +1 -0
  628. package/build/dist/Models/DatabaseModels/RunnerJob.js +219 -0
  629. package/build/dist/Models/DatabaseModels/RunnerJob.js.map +1 -1
  630. package/build/dist/Models/DatabaseModels/TeamComplianceSetting.js +134 -3
  631. package/build/dist/Models/DatabaseModels/TeamComplianceSetting.js.map +1 -1
  632. package/build/dist/Models/DatabaseModels/VMwareVCenter.js +219 -0
  633. package/build/dist/Models/DatabaseModels/VMwareVCenter.js.map +1 -1
  634. package/build/dist/Models/DatabaseModels/WorkspaceProjectAuthToken.js.map +1 -1
  635. package/build/dist/Server/API/AIChatAPI.js +6 -1
  636. package/build/dist/Server/API/AIChatAPI.js.map +1 -1
  637. package/build/dist/Server/API/AIInvestigationAPI.js +17 -10
  638. package/build/dist/Server/API/AIInvestigationAPI.js.map +1 -1
  639. package/build/dist/Server/API/AutoRemediationAPI.js +374 -24
  640. package/build/dist/Server/API/AutoRemediationAPI.js.map +1 -1
  641. package/build/dist/Server/API/KubernetesClusterAiAccessAPI.js +436 -22
  642. package/build/dist/Server/API/KubernetesClusterAiAccessAPI.js.map +1 -1
  643. package/build/dist/Server/API/MicrosoftTeamsAPI.js +69 -13
  644. package/build/dist/Server/API/MicrosoftTeamsAPI.js.map +1 -1
  645. package/build/dist/Server/API/ProxmoxResourceAPI.js +187 -14
  646. package/build/dist/Server/API/ProxmoxResourceAPI.js.map +1 -1
  647. package/build/dist/Server/API/ResourceAiAccessAPI.js +1119 -0
  648. package/build/dist/Server/API/ResourceAiAccessAPI.js.map +1 -0
  649. package/build/dist/Server/Infrastructure/GlobalCache.js +27 -0
  650. package/build/dist/Server/Infrastructure/GlobalCache.js.map +1 -1
  651. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796000000000-AddTeamComplianceRuleScope.js +50 -0
  652. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796000000000-AddTeamComplianceRuleScope.js.map +1 -0
  653. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796100000000-AddKubernetesAiAgentAndAiDefaults.js +84 -0
  654. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796100000000-AddKubernetesAiAgentAndAiDefaults.js.map +1 -0
  655. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796200000000-AddProxmoxResourceNativePushColumns.js +31 -0
  656. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796200000000-AddProxmoxResourceNativePushColumns.js.map +1 -0
  657. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796300000000-AddResourceAiAgents.js +168 -0
  658. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796300000000-AddResourceAiAgents.js.map +1 -0
  659. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +8 -0
  660. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  661. package/build/dist/Server/Infrastructure/Semaphore.js +6 -0
  662. package/build/dist/Server/Infrastructure/Semaphore.js.map +1 -1
  663. package/build/dist/Server/Middleware/TelemetryIngest.js +41 -19
  664. package/build/dist/Server/Middleware/TelemetryIngest.js.map +1 -1
  665. package/build/dist/Server/Services/AIService.js +59 -1
  666. package/build/dist/Server/Services/AIService.js.map +1 -1
  667. package/build/dist/Server/Services/AlertService.js +248 -13
  668. package/build/dist/Server/Services/AlertService.js.map +1 -1
  669. package/build/dist/Server/Services/AlertSeverityService.js +51 -15
  670. package/build/dist/Server/Services/AlertSeverityService.js.map +1 -1
  671. package/build/dist/Server/Services/AnalyticsDatabaseService.js +26 -1
  672. package/build/dist/Server/Services/AnalyticsDatabaseService.js.map +1 -1
  673. package/build/dist/Server/Services/AutoRemediationRuleEngineService.js +601 -3
  674. package/build/dist/Server/Services/AutoRemediationRuleEngineService.js.map +1 -1
  675. package/build/dist/Server/Services/AutoRemediationRuleService.js +1 -1
  676. package/build/dist/Server/Services/AutoRemediationRuleService.js.map +1 -1
  677. package/build/dist/Server/Services/CephClusterService.js +104 -0
  678. package/build/dist/Server/Services/CephClusterService.js.map +1 -1
  679. package/build/dist/Server/Services/DatabaseServerService.js +99 -0
  680. package/build/dist/Server/Services/DatabaseServerService.js.map +1 -1
  681. package/build/dist/Server/Services/DockerHostService.js +104 -0
  682. package/build/dist/Server/Services/DockerHostService.js.map +1 -1
  683. package/build/dist/Server/Services/DockerSwarmClusterService.js +104 -0
  684. package/build/dist/Server/Services/DockerSwarmClusterService.js.map +1 -1
  685. package/build/dist/Server/Services/HostService.js +114 -2
  686. package/build/dist/Server/Services/HostService.js.map +1 -1
  687. package/build/dist/Server/Services/IncidentService.js +199 -72
  688. package/build/dist/Server/Services/IncidentService.js.map +1 -1
  689. package/build/dist/Server/Services/IncidentSeverityService.js +51 -15
  690. package/build/dist/Server/Services/IncidentSeverityService.js.map +1 -1
  691. package/build/dist/Server/Services/KubernetesAiAgentJobService.js +158 -0
  692. package/build/dist/Server/Services/KubernetesAiAgentJobService.js.map +1 -0
  693. package/build/dist/Server/Services/KubernetesAiAgentService.js +1145 -0
  694. package/build/dist/Server/Services/KubernetesAiAgentService.js.map +1 -0
  695. package/build/dist/Server/Services/KubernetesClusterAiAccessService.js +842 -371
  696. package/build/dist/Server/Services/KubernetesClusterAiAccessService.js.map +1 -1
  697. package/build/dist/Server/Services/KubernetesClusterService.js +86 -17
  698. package/build/dist/Server/Services/KubernetesClusterService.js.map +1 -1
  699. package/build/dist/Server/Services/MetricRecordingRuleService.js +46 -0
  700. package/build/dist/Server/Services/MetricRecordingRuleService.js.map +1 -1
  701. package/build/dist/Server/Services/PodmanHostService.js +104 -0
  702. package/build/dist/Server/Services/PodmanHostService.js.map +1 -1
  703. package/build/dist/Server/Services/ProjectService.js +22 -0
  704. package/build/dist/Server/Services/ProjectService.js.map +1 -1
  705. package/build/dist/Server/Services/ProxmoxClusterService.js +104 -0
  706. package/build/dist/Server/Services/ProxmoxClusterService.js.map +1 -1
  707. package/build/dist/Server/Services/ProxmoxResourceService.js +258 -3
  708. package/build/dist/Server/Services/ProxmoxResourceService.js.map +1 -1
  709. package/build/dist/Server/Services/ResourceAiAccessService.js +1028 -0
  710. package/build/dist/Server/Services/ResourceAiAccessService.js.map +1 -0
  711. package/build/dist/Server/Services/ResourceAiAgentJobService.js +173 -0
  712. package/build/dist/Server/Services/ResourceAiAgentJobService.js.map +1 -0
  713. package/build/dist/Server/Services/ResourceAiAgentService.js +1687 -0
  714. package/build/dist/Server/Services/ResourceAiAgentService.js.map +1 -0
  715. package/build/dist/Server/Services/RunnerJobService.js +522 -97
  716. package/build/dist/Server/Services/RunnerJobService.js.map +1 -1
  717. package/build/dist/Server/Services/RunnerService.js +1 -1
  718. package/build/dist/Server/Services/RunnerService.js.map +1 -1
  719. package/build/dist/Server/Services/ScheduledMaintenanceService.js +535 -3
  720. package/build/dist/Server/Services/ScheduledMaintenanceService.js.map +1 -1
  721. package/build/dist/Server/Services/ScheduledMaintenanceStateTimelineService.js +316 -46
  722. package/build/dist/Server/Services/ScheduledMaintenanceStateTimelineService.js.map +1 -1
  723. package/build/dist/Server/Services/TeamComplianceSettingService.js +833 -7
  724. package/build/dist/Server/Services/TeamComplianceSettingService.js.map +1 -1
  725. package/build/dist/Server/Services/TelemetryUsageBillingService.js +26 -0
  726. package/build/dist/Server/Services/TelemetryUsageBillingService.js.map +1 -1
  727. package/build/dist/Server/Services/TraceRecordingRuleService.js +37 -0
  728. package/build/dist/Server/Services/TraceRecordingRuleService.js.map +1 -1
  729. package/build/dist/Server/Services/VMwareVCenterService.js +104 -0
  730. package/build/dist/Server/Services/VMwareVCenterService.js.map +1 -1
  731. package/build/dist/Server/Utils/AI/ClusterAccess/ClusterAccessContext.js +52 -9
  732. package/build/dist/Server/Utils/AI/ClusterAccess/ClusterAccessContext.js.map +1 -1
  733. package/build/dist/Server/Utils/AI/ClusterAccess/KubectlInvestigationToolkit.js +13 -9
  734. package/build/dist/Server/Utils/AI/ClusterAccess/KubectlInvestigationToolkit.js.map +1 -1
  735. package/build/dist/Server/Utils/AI/ClusterAccess/KubectlJobRunner.js +7 -3
  736. package/build/dist/Server/Utils/AI/ClusterAccess/KubectlJobRunner.js.map +1 -1
  737. package/build/dist/Server/Utils/AI/Remediation/RemediationCommandTools.js +1056 -43
  738. package/build/dist/Server/Utils/AI/Remediation/RemediationCommandTools.js.map +1 -1
  739. package/build/dist/Server/Utils/AI/Remediation/RemediationExecutionRunner.js +683 -42
  740. package/build/dist/Server/Utils/AI/Remediation/RemediationExecutionRunner.js.map +1 -1
  741. package/build/dist/Server/Utils/AI/Remediation/RemediationPlanRunner.js +21 -1
  742. package/build/dist/Server/Utils/AI/Remediation/RemediationPlanRunner.js.map +1 -1
  743. package/build/dist/Server/Utils/AI/ResourceAccess/InfrastructureInvestigationToolkit.js +330 -0
  744. package/build/dist/Server/Utils/AI/ResourceAccess/InfrastructureInvestigationToolkit.js.map +1 -0
  745. package/build/dist/Server/Utils/AI/ResourceAccess/ResourceAccessContext.js +160 -0
  746. package/build/dist/Server/Utils/AI/ResourceAccess/ResourceAccessContext.js.map +1 -0
  747. package/build/dist/Server/Utils/AI/ResourceAccess/ResourceAccessToolNames.js +33 -0
  748. package/build/dist/Server/Utils/AI/ResourceAccess/ResourceAccessToolNames.js.map +1 -0
  749. package/build/dist/Server/Utils/AI/ResourceAccess/ResourceAiAccessSettings.js +640 -0
  750. package/build/dist/Server/Utils/AI/ResourceAccess/ResourceAiAccessSettings.js.map +1 -0
  751. package/build/dist/Server/Utils/AI/ResourceAccess/ResourceAiDeleteCleanup.js +226 -0
  752. package/build/dist/Server/Utils/AI/ResourceAccess/ResourceAiDeleteCleanup.js.map +1 -0
  753. package/build/dist/Server/Utils/AI/ResourceAccess/ResourceCommandJobRunner.js +600 -0
  754. package/build/dist/Server/Utils/AI/ResourceAccess/ResourceCommandJobRunner.js.map +1 -0
  755. package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js +89 -9
  756. package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js.map +1 -1
  757. package/build/dist/Server/Utils/AI/SRE/AlertInvestigationRunner.js +55 -4
  758. package/build/dist/Server/Utils/AI/SRE/AlertInvestigationRunner.js.map +1 -1
  759. package/build/dist/Server/Utils/AI/SRE/IncidentInvestigationRunner.js +55 -4
  760. package/build/dist/Server/Utils/AI/SRE/IncidentInvestigationRunner.js.map +1 -1
  761. package/build/dist/Server/Utils/AI/SRE/IncidentPostmortemRunner.js +30 -3
  762. package/build/dist/Server/Utils/AI/SRE/IncidentPostmortemRunner.js.map +1 -1
  763. package/build/dist/Server/Utils/AI/SRE/InvestigationEligibility.js +13 -2
  764. package/build/dist/Server/Utils/AI/SRE/InvestigationEligibility.js.map +1 -1
  765. package/build/dist/Server/Utils/AI/SRE/InvestigationGrader.js +1 -1
  766. package/build/dist/Server/Utils/AutoRemediation/CommandPlanExecutor.js +294 -15
  767. package/build/dist/Server/Utils/AutoRemediation/CommandPlanExecutor.js.map +1 -1
  768. package/build/dist/Server/Utils/AutoRemediation/RemediationVerifier.js +29 -1
  769. package/build/dist/Server/Utils/AutoRemediation/RemediationVerifier.js.map +1 -1
  770. package/build/dist/Server/Utils/Database/ProjectScopedReferenceValidator.js +9 -1
  771. package/build/dist/Server/Utils/Database/ProjectScopedReferenceValidator.js.map +1 -1
  772. package/build/dist/Server/Utils/SessionReplay/SessionReplayBudgetMetrics.js +627 -0
  773. package/build/dist/Server/Utils/SessionReplay/SessionReplayBudgetMetrics.js.map +1 -0
  774. package/build/dist/Server/Utils/SessionReplay/SessionReplayUsage.js +60 -5
  775. package/build/dist/Server/Utils/SessionReplay/SessionReplayUsage.js.map +1 -1
  776. package/build/dist/Server/Utils/Telemetry/InventoryEntityRegistry.js +111 -6
  777. package/build/dist/Server/Utils/Telemetry/InventoryEntityRegistry.js.map +1 -1
  778. package/build/dist/Server/Utils/Telemetry/ProxmoxCephSnapshotScan.js +40 -4
  779. package/build/dist/Server/Utils/Telemetry/ProxmoxCephSnapshotScan.js.map +1 -1
  780. package/build/dist/Server/Utils/Telemetry/ProxmoxNativeNodeLiveness.js +296 -0
  781. package/build/dist/Server/Utils/Telemetry/ProxmoxNativeNodeLiveness.js.map +1 -0
  782. package/build/dist/Server/Utils/Telemetry/ProxmoxNativePush.js +699 -0
  783. package/build/dist/Server/Utils/Telemetry/ProxmoxNativePush.js.map +1 -0
  784. package/build/dist/Server/Utils/Telemetry/TelemetryEntity.js +77 -19
  785. package/build/dist/Server/Utils/Telemetry/TelemetryEntity.js.map +1 -1
  786. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Alert.js +19 -15
  787. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Alert.js.map +1 -1
  788. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/AlertEpisode.js +19 -15
  789. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/AlertEpisode.js.map +1 -1
  790. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Auth.js +17 -1
  791. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Auth.js.map +1 -1
  792. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.js +183 -209
  793. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.js.map +1 -1
  794. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/IncidentEpisode.js +19 -15
  795. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/IncidentEpisode.js.map +1 -1
  796. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/ScheduledMaintenance.js +233 -167
  797. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/ScheduledMaintenance.js.map +1 -1
  798. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +768 -152
  799. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
  800. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsActivityDeduplicator.js +129 -0
  801. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsActivityDeduplicator.js.map +1 -0
  802. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCardChoices.js +266 -0
  803. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCardChoices.js.map +1 -0
  804. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCreateCommands.js +258 -0
  805. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCreateCommands.js.map +1 -0
  806. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsMessageSize.js +104 -0
  807. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsMessageSize.js.map +1 -0
  808. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsReplies.js +183 -0
  809. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsReplies.js.map +1 -0
  810. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsTimezone.js +123 -0
  811. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsTimezone.js.map +1 -0
  812. package/build/dist/Types/AI/AIRunType.js +9 -4
  813. package/build/dist/Types/AI/AIRunType.js.map +1 -1
  814. package/build/dist/Types/AI/ResourceAiAccessApi.js +30 -0
  815. package/build/dist/Types/AI/ResourceAiAccessApi.js.map +1 -0
  816. package/build/dist/Types/AI/ResourceAiAccessPermissions.js +98 -0
  817. package/build/dist/Types/AI/ResourceAiAccessPermissions.js.map +1 -0
  818. package/build/dist/Types/AutoRemediation/AiRemediationCommandPlan.js +126 -45
  819. package/build/dist/Types/AutoRemediation/AiRemediationCommandPlan.js.map +1 -1
  820. package/build/dist/Types/AutoRemediation/AiRemediationCommandPolicyVerdict.js +28 -0
  821. package/build/dist/Types/AutoRemediation/AiRemediationCommandPolicyVerdict.js.map +1 -0
  822. package/build/dist/Types/Kubernetes/KubernetesClusterAiAccess.js +72 -16
  823. package/build/dist/Types/Kubernetes/KubernetesClusterAiAccess.js.map +1 -1
  824. package/build/dist/Types/Kubernetes/KubernetesClusterAiAccessPermissions.js +20 -16
  825. package/build/dist/Types/Kubernetes/KubernetesClusterAiAccessPermissions.js.map +1 -1
  826. package/build/dist/Types/Kubernetes/KubernetesClusterAiInsights.js +17 -0
  827. package/build/dist/Types/Kubernetes/KubernetesClusterAiInsights.js.map +1 -0
  828. package/build/dist/Types/Monitor/ProxmoxAlertTemplates.js +4 -4
  829. package/build/dist/Types/Monitor/ProxmoxAlertTemplates.js.map +1 -1
  830. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationCatalog.js +47 -21
  831. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationCatalog.js.map +1 -1
  832. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationTypes.js +5 -0
  833. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationTypes.js.map +1 -1
  834. package/build/dist/Types/Monitor/RumAlertTemplates.js +199 -3
  835. package/build/dist/Types/Monitor/RumAlertTemplates.js.map +1 -1
  836. package/build/dist/Types/ResourceAiAgent/AiResourceType.js +242 -0
  837. package/build/dist/Types/ResourceAiAgent/AiResourceType.js.map +1 -0
  838. package/build/dist/Types/ResourceAiAgent/ResourceAiAccess.js +275 -0
  839. package/build/dist/Types/ResourceAiAgent/ResourceAiAccess.js.map +1 -0
  840. package/build/dist/Types/Rum/SessionReplayBudgetMetricType.js +45 -0
  841. package/build/dist/Types/Rum/SessionReplayBudgetMetricType.js.map +1 -0
  842. package/build/dist/Types/Runbook/RunbookStepType.js +27 -0
  843. package/build/dist/Types/Runbook/RunbookStepType.js.map +1 -1
  844. package/build/dist/Types/Team/ComplianceNotificationChannel.js +23 -0
  845. package/build/dist/Types/Team/ComplianceNotificationChannel.js.map +1 -0
  846. package/build/dist/Types/Team/ComplianceRule.js +304 -0
  847. package/build/dist/Types/Team/ComplianceRule.js.map +1 -0
  848. package/build/dist/Types/Team/ComplianceRuleType.js +21 -0
  849. package/build/dist/Types/Team/ComplianceRuleType.js.map +1 -1
  850. package/build/dist/Types/Team/TeamComplianceStatus.js +2 -0
  851. package/build/dist/Types/Team/TeamComplianceStatus.js.map +1 -0
  852. package/build/dist/Types/Telemetry/TelemetryIngestSurface.js +36 -0
  853. package/build/dist/Types/Telemetry/TelemetryIngestSurface.js.map +1 -1
  854. package/build/dist/UI/Components/ActionButton/ActionButtonSchema.js +20 -1
  855. package/build/dist/UI/Components/ActionButton/ActionButtonSchema.js.map +1 -1
  856. package/build/dist/UI/Components/ActionButton/RowActions.js +113 -0
  857. package/build/dist/UI/Components/ActionButton/RowActions.js.map +1 -0
  858. package/build/dist/UI/Components/ActionButton/SplitActionButtons.js +71 -0
  859. package/build/dist/UI/Components/ActionButton/SplitActionButtons.js.map +1 -0
  860. package/build/dist/UI/Components/Charts/Area/AreaChart.js +16 -1
  861. package/build/dist/UI/Components/Charts/Area/AreaChart.js.map +1 -1
  862. package/build/dist/UI/Components/Charts/Bar/BarChart.js +16 -1
  863. package/build/dist/UI/Components/Charts/Bar/BarChart.js.map +1 -1
  864. package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js +27 -5
  865. package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js.map +1 -1
  866. package/build/dist/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.js +61 -194
  867. package/build/dist/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.js.map +1 -1
  868. package/build/dist/UI/Components/Charts/ChartLibrary/BarChart/BarChart.js +84 -26
  869. package/build/dist/UI/Components/Charts/ChartLibrary/BarChart/BarChart.js.map +1 -1
  870. package/build/dist/UI/Components/Charts/ChartLibrary/LineChart/LineChart.js +75 -196
  871. package/build/dist/UI/Components/Charts/ChartLibrary/LineChart/LineChart.js.map +1 -1
  872. package/build/dist/UI/Components/Charts/ChartLibrary/Utils/DoubleClick.js +133 -0
  873. package/build/dist/UI/Components/Charts/ChartLibrary/Utils/DoubleClick.js.map +1 -1
  874. package/build/dist/UI/Components/Charts/ChartLibrary/Utils/UseChartBucketsChange.js +26 -0
  875. package/build/dist/UI/Components/Charts/ChartLibrary/Utils/UseChartBucketsChange.js.map +1 -0
  876. package/build/dist/UI/Components/Charts/ChartLibrary/Utils/UseChartRangeSelection.js +371 -0
  877. package/build/dist/UI/Components/Charts/ChartLibrary/Utils/UseChartRangeSelection.js.map +1 -0
  878. package/build/dist/UI/Components/Charts/Line/LineChart.js +16 -1
  879. package/build/dist/UI/Components/Charts/Line/LineChart.js.map +1 -1
  880. package/build/dist/UI/Components/Charts/TimeRangeZoom/ResetTimeRangeZoomButton.js +35 -0
  881. package/build/dist/UI/Components/Charts/TimeRangeZoom/ResetTimeRangeZoomButton.js.map +1 -0
  882. package/build/dist/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomContext.js +107 -0
  883. package/build/dist/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomContext.js.map +1 -0
  884. package/build/dist/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomHint.js +41 -0
  885. package/build/dist/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomHint.js.map +1 -0
  886. package/build/dist/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomUtil.js +117 -0
  887. package/build/dist/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomUtil.js.map +1 -0
  888. package/build/dist/UI/Components/Charts/TimeRangeZoom/UseTimeRangeZoom.js +121 -0
  889. package/build/dist/UI/Components/Charts/TimeRangeZoom/UseTimeRangeZoom.js.map +1 -0
  890. package/build/dist/UI/Components/Charts/Utils/DataPoint.js +15 -7
  891. package/build/dist/UI/Components/Charts/Utils/DataPoint.js.map +1 -1
  892. package/build/dist/UI/Components/Charts/Utils/XAxis.js +70 -0
  893. package/build/dist/UI/Components/Charts/Utils/XAxis.js.map +1 -1
  894. package/build/dist/UI/Components/Charts/Utils/useHistogramRangeSelection.js +305 -38
  895. package/build/dist/UI/Components/Charts/Utils/useHistogramRangeSelection.js.map +1 -1
  896. package/build/dist/UI/Components/EntityDropdown/EntityDropdown.js +89 -14
  897. package/build/dist/UI/Components/EntityDropdown/EntityDropdown.js.map +1 -1
  898. package/build/dist/UI/Components/Forms/Fields/FormField.js +11 -2
  899. package/build/dist/UI/Components/Forms/Fields/FormField.js.map +1 -1
  900. package/build/dist/UI/Components/Input/Input.js +69 -54
  901. package/build/dist/UI/Components/Input/Input.js.map +1 -1
  902. package/build/dist/UI/Components/List/ListRow.js +4 -39
  903. package/build/dist/UI/Components/List/ListRow.js.map +1 -1
  904. package/build/dist/UI/Components/LogsViewer/LogsViewer.js +31 -8
  905. package/build/dist/UI/Components/LogsViewer/LogsViewer.js.map +1 -1
  906. package/build/dist/UI/Components/LogsViewer/components/LogTimeRangePicker.js +10 -1
  907. package/build/dist/UI/Components/LogsViewer/components/LogTimeRangePicker.js.map +1 -1
  908. package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js +139 -21
  909. package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js.map +1 -1
  910. package/build/dist/UI/Components/LogsViewer/components/LogsHistogram.js +30 -8
  911. package/build/dist/UI/Components/LogsViewer/components/LogsHistogram.js.map +1 -1
  912. package/build/dist/UI/Components/LogsViewer/components/LogsViewerToolbar.js +1 -1
  913. package/build/dist/UI/Components/LogsViewer/components/LogsViewerToolbar.js.map +1 -1
  914. package/build/dist/UI/Components/ModelTable/BaseModelTable.js +10 -0
  915. package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
  916. package/build/dist/UI/Components/MoreMenu/MoreMenu.js +253 -14
  917. package/build/dist/UI/Components/MoreMenu/MoreMenu.js.map +1 -1
  918. package/build/dist/UI/Components/MoreMenu/MoreMenuItem.js +43 -13
  919. package/build/dist/UI/Components/MoreMenu/MoreMenuItem.js.map +1 -1
  920. package/build/dist/UI/Components/OrderedStatesList/Item.js +2 -38
  921. package/build/dist/UI/Components/OrderedStatesList/Item.js.map +1 -1
  922. package/build/dist/UI/Components/StatusPage/ResourceGrid.js +53 -15
  923. package/build/dist/UI/Components/StatusPage/ResourceGrid.js.map +1 -1
  924. package/build/dist/UI/Components/Table/TableBody.js +6 -2
  925. package/build/dist/UI/Components/Table/TableBody.js.map +1 -1
  926. package/build/dist/UI/Components/Table/TableRow.js +12 -75
  927. package/build/dist/UI/Components/Table/TableRow.js.map +1 -1
  928. package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js +32 -7
  929. package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js.map +1 -1
  930. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogram.js +27 -9
  931. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogram.js.map +1 -1
  932. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.js +11 -1
  933. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.js.map +1 -1
  934. package/build/dist/UI/Components/TelemetryViewer/useViewerTimeRangeZoom.js +272 -0
  935. package/build/dist/UI/Components/TelemetryViewer/useViewerTimeRangeZoom.js.map +1 -0
  936. package/build/dist/UI/Components/Toggle/Toggle.js +14 -1
  937. package/build/dist/UI/Components/Toggle/Toggle.js.map +1 -1
  938. package/build/dist/UI/Components/Tooltip/Tooltip.js +1 -1
  939. package/build/dist/UI/Components/Tooltip/Tooltip.js.map +1 -1
  940. package/build/dist/Utils/AI/InvestigationReport.js +71 -11
  941. package/build/dist/Utils/AI/InvestigationReport.js.map +1 -1
  942. package/build/dist/Utils/AiRemediation/CommandPolicy.js +1 -1
  943. package/build/dist/Utils/AiRemediation/CommandPolicy.js.map +1 -1
  944. package/build/dist/Utils/AiRemediation/KubectlPolicy.js +1 -1
  945. package/build/dist/Utils/AiRemediation/KubectlPolicy.js.map +1 -1
  946. package/build/dist/Utils/AiRemediation/Resource/CephCommandPolicy.js +1462 -0
  947. package/build/dist/Utils/AiRemediation/Resource/CephCommandPolicy.js.map +1 -0
  948. package/build/dist/Utils/AiRemediation/Resource/DatabaseCommandPolicy.js +122 -0
  949. package/build/dist/Utils/AiRemediation/Resource/DatabaseCommandPolicy.js.map +1 -0
  950. package/build/dist/Utils/AiRemediation/Resource/DatabaseDiagnosticCatalog.js +1139 -0
  951. package/build/dist/Utils/AiRemediation/Resource/DatabaseDiagnosticCatalog.js.map +1 -0
  952. package/build/dist/Utils/AiRemediation/Resource/DatabaseQueryRedactor.js +1060 -0
  953. package/build/dist/Utils/AiRemediation/Resource/DatabaseQueryRedactor.js.map +1 -0
  954. package/build/dist/Utils/AiRemediation/Resource/DockerCliGrammar.js +1301 -0
  955. package/build/dist/Utils/AiRemediation/Resource/DockerCliGrammar.js.map +1 -0
  956. package/build/dist/Utils/AiRemediation/Resource/DockerEngineCommandPolicy.js +370 -0
  957. package/build/dist/Utils/AiRemediation/Resource/DockerEngineCommandPolicy.js.map +1 -0
  958. package/build/dist/Utils/AiRemediation/Resource/DockerSwarmCommandPolicy.js +498 -0
  959. package/build/dist/Utils/AiRemediation/Resource/DockerSwarmCommandPolicy.js.map +1 -0
  960. package/build/dist/Utils/AiRemediation/Resource/GovcCommandPolicy.js +1565 -0
  961. package/build/dist/Utils/AiRemediation/Resource/GovcCommandPolicy.js.map +1 -0
  962. package/build/dist/Utils/AiRemediation/Resource/HostCommandPolicy.js +2206 -0
  963. package/build/dist/Utils/AiRemediation/Resource/HostCommandPolicy.js.map +1 -0
  964. package/build/dist/Utils/AiRemediation/Resource/ProxmoxCommandPolicy.js +1129 -0
  965. package/build/dist/Utils/AiRemediation/Resource/ProxmoxCommandPolicy.js.map +1 -0
  966. package/build/dist/Utils/AiRemediation/Resource/ResourceCommandPolicy.js +565 -0
  967. package/build/dist/Utils/AiRemediation/Resource/ResourceCommandPolicy.js.map +1 -0
  968. package/build/dist/Utils/AiRemediation/Resource/ResourceCommandPolicyCore.js +408 -0
  969. package/build/dist/Utils/AiRemediation/Resource/ResourceCommandPolicyCore.js.map +1 -0
  970. package/build/dist/Utils/AiRemediation/Resource/ResourceOutputRedactor.js +1910 -0
  971. package/build/dist/Utils/AiRemediation/Resource/ResourceOutputRedactor.js.map +1 -0
  972. package/build/dist/Utils/Dashboard/DashboardTimeRangeZoom.js +27 -1
  973. package/build/dist/Utils/Dashboard/DashboardTimeRangeZoom.js.map +1 -1
  974. package/build/dist/Utils/SessionReplay/SessionReplayBudgetMetricType.js +160 -0
  975. package/build/dist/Utils/SessionReplay/SessionReplayBudgetMetricType.js.map +1 -0
  976. package/package.json +1 -1
@@ -0,0 +1,1721 @@
1
+ jest.mock("../../../../Server/EnvironmentConfig", () => {
2
+ return {
3
+ ...(jest.requireActual("../../../../Server/EnvironmentConfig") as Record<
4
+ string,
5
+ unknown
6
+ >),
7
+ MicrosoftTeamsAppClientId: "11111111-2222-3333-4444-555555555555",
8
+ MicrosoftTeamsAppClientSecret: "test-secret",
9
+ MicrosoftTeamsAppTenantId: "test-tenant",
10
+ };
11
+ });
12
+
13
+ jest.mock(
14
+ "../../../../Server/Infrastructure/Postgres/DataSourceOptions",
15
+ () => {
16
+ return {};
17
+ },
18
+ );
19
+
20
+ /*
21
+ * The repo-wide botbuilder manual mock does not expose TeamsInfo or
22
+ * MessageFactory.attachment, which chat capture and the welcome card use.
23
+ */
24
+ jest.mock("botbuilder", () => {
25
+ return {
26
+ CloudAdapter: class CloudAdapter {},
27
+ ConfigurationBotFrameworkAuthentication: class ConfigurationBotFrameworkAuthentication {},
28
+ TeamsActivityHandler: class TeamsActivityHandler {},
29
+ TurnContext: class TurnContext {},
30
+ ActivityHandler: class ActivityHandler {},
31
+ MessageFactory: {
32
+ text: jest.fn(),
33
+ attachment: jest.fn((attachment: unknown) => {
34
+ return { type: "message", attachments: [attachment] };
35
+ }),
36
+ },
37
+ CardFactory: { heroCard: jest.fn() },
38
+ TeamsInfo: {
39
+ getMembers: jest.fn(),
40
+ getPagedMembers: jest.fn(),
41
+ },
42
+ };
43
+ });
44
+
45
+ import Entities from "../../../../Models/DatabaseModels/Index";
46
+ import {
47
+ MicrosoftTeamsChat,
48
+ MicrosoftTeamsMiscData,
49
+ } from "../../../../Models/DatabaseModels/WorkspaceProjectAuthToken";
50
+ import PostgresAppInstance from "../../../../Server/Infrastructure/PostgresDatabase";
51
+ import MicrosoftTeamsUtil, {
52
+ MicrosoftTeamsChatNameRefreshResult,
53
+ MicrosoftTeamsChatNameUpdate,
54
+ } from "../../../../Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams";
55
+ import HTTPErrorResponse from "../../../../Types/API/HTTPErrorResponse";
56
+ import HTTPResponse from "../../../../Types/API/HTTPResponse";
57
+ import { JSONObject } from "../../../../Types/JSON";
58
+ import ObjectID from "../../../../Types/ObjectID";
59
+ import WorkspaceType from "../../../../Types/Workspace/WorkspaceType";
60
+ import API, { APIRequestOptions } from "../../../../Utils/API";
61
+ import {
62
+ TeamsInfo,
63
+ type TeamsChannelAccount,
64
+ type TeamsPagedMembersResult,
65
+ type TurnContext,
66
+ } from "botbuilder";
67
+ import { DataSource, Logger } from "typeorm";
68
+
69
+ /*
70
+ * Issue #4106 - Microsoft Teams group chats listed by their members instead
71
+ * of their Teams name - against a migrated Postgres.
72
+ *
73
+ * MicrosoftTeamsGroupChatNames.test.ts pins the logic with every
74
+ * WorkspaceProjectAuthToken read and write faked. What only a database can
75
+ * show is what is actually stored in the miscData jsonb column:
76
+ *
77
+ * - that capture stores the chat's Teams name (topic), only the first three
78
+ * member names and the member count - on EVERY connected project of the
79
+ * tenant and on no other tenant;
80
+ * - that a re-capture builds on the stored record: the connection date and a
81
+ * Teams name Graph will not (or cannot) read again are kept, and a roster
82
+ * that could not be read leaves the stored one - or the missing member
83
+ * count that makes the next message try again - as it was;
84
+ * - that a Refresh Chats rename writes the name and Teams name alone, onto
85
+ * the record as it is now (a chat removed meanwhile is not resurrected, one
86
+ * re-captured meanwhile keeps its new record), with one write per row and
87
+ * one Graph token for every lookup;
88
+ * - that a long Teams name cut where an emoji sits is stored at all: the cut
89
+ * used to leave half of the emoji, a lone UTF-16 surrogate, and Postgres
90
+ * refuses the whole jsonb write that carries one;
91
+ * - that non-ASCII names round-trip character for character, and that the
92
+ * message backfill's staleness check reads what capture wrote.
93
+ *
94
+ * Opt in with RUN_POSTGRES_MICROSOFT_TEAMS_CHAT_TESTS=true against a Postgres
95
+ * migrated to the current head - the Postgres Schema Drift workflow's
96
+ * database right after its drift check, e.g. from packages/Common:
97
+ *
98
+ * RUN_POSTGRES_MICROSOFT_TEAMS_CHAT_TESTS=true \
99
+ * MICROSOFT_TEAMS_CHAT_TEST_DATABASE_HOST=127.0.0.1 \
100
+ * MICROSOFT_TEAMS_CHAT_TEST_DATABASE_PORT=5400 \
101
+ * DATABASE_PASSWORD=... node node_modules/.bin/jest --runInBand \
102
+ * Tests/Server/Utils/Workspace/MicrosoftTeamsGroupChatNamesPostgres.test.ts --forceExit
103
+ *
104
+ * Without the flag the suite is skipped, so the Common test job - whose
105
+ * Postgres is not migrated - never runs it.
106
+ *
107
+ * The WorkspaceProjectAuthToken table's STRUCTURE is cloned (LIKE ...
108
+ * INCLUDING ALL) into a uniquely named schema (search_path holds that schema
109
+ * first) that is dropped afterwards; every row is synthetic.
110
+ *
111
+ * The production MicrosoftTeamsUtil and WorkspaceProjectAuthTokenService run
112
+ * unchanged - getValidAccessToken included, which hands out the Graph app
113
+ * token stored on the row. Only Microsoft is faked: Graph (API.get, and
114
+ * API.post so a token refresh would be loud) and the Bot Framework
115
+ * (TeamsInfo and the TurnContext). Every statement Postgres rejects is
116
+ * recorded through the DataSource's logger and fails the test, even when a
117
+ * caller catches it - chat capture swallows its own errors, so a refused
118
+ * write would otherwise pass as "nothing stored".
119
+ */
120
+ const describePostgres: typeof describe =
121
+ process.env["RUN_POSTGRES_MICROSOFT_TEAMS_CHAT_TESTS"] === "true"
122
+ ? describe
123
+ : describe.skip;
124
+
125
+ const TABLE: string = "WorkspaceProjectAuthToken";
126
+
127
+ const TENANT_ID: string = "6f1c2d3e-4b5a-4968-8778-a1b2c3d4e5f6";
128
+ const OTHER_TENANT_ID: string = "0a9b8c7d-6e5f-4a3b-9c2d-1e0f9a8b7c6d";
129
+
130
+ const BOT_RECIPIENT_ID: string = "28:bot-recipient-id";
131
+ const SERVICE_URL: string = "https://smba.trafficmanager.net/amer/";
132
+ const MOVED_SERVICE_URL: string = "https://smba.trafficmanager.net/emea/";
133
+
134
+ // Captured before rosters (and names from Graph) were stored.
135
+ const LEGACY_GROUP_CHAT_ID: string =
136
+ "19:1a2b3c4d5e6f47a8b9c0d1e2f3a4b5c6@thread.v2";
137
+ /*
138
+ * Captured by the first release of the #4106 fix, which stored the whole
139
+ * roster's names and no member count.
140
+ */
141
+ const FIRST_RELEASE_GROUP_CHAT_ID: string =
142
+ "19:7c6b5a4938271605f4e3d2c1b0a99887@thread.v2";
143
+ // Captured with its roster; listed by its members.
144
+ const ROSTER_GROUP_CHAT_ID: string =
145
+ "19:9f8e7d6c5b4a43928170f1e2d3c4b5a6@thread.v2";
146
+ // Captured under its Teams name.
147
+ const NAMED_GROUP_CHAT_ID: string =
148
+ "19:2b3c4d5e6f708192a3b4c5d6e7f80912@thread.v2";
149
+ // Also named; Graph keeps answering with the name already stored.
150
+ const QUIET_GROUP_CHAT_ID: string =
151
+ "19:4d5e6f708192a3b4c5d6e7f809123a4b@thread.v2";
152
+ // A group chat in which Microsoft refuses to share the name.
153
+ const DENIED_GROUP_CHAT_ID: string =
154
+ "19:0f1e2d3c4b5a49687f6e5d4c3b2a1908@thread.v2";
155
+ const PERSONAL_CHAT_ID: string = "a:1XyZpersonalChatIdOnlyTheBotFrameworkKnows";
156
+ // A chat the bot is added to during a test.
157
+ const NEW_GROUP_CHAT_ID: string =
158
+ "19:5e4d3c2b1a0948f7e6d5c4b3a2918070@thread.v2";
159
+
160
+ // Six group chats, so Refresh Chats (five lookups at a time) runs two batches.
161
+ const SEEDED_GROUP_CHAT_IDS: Array<string> = [
162
+ LEGACY_GROUP_CHAT_ID,
163
+ FIRST_RELEASE_GROUP_CHAT_ID,
164
+ ROSTER_GROUP_CHAT_ID,
165
+ NAMED_GROUP_CHAT_ID,
166
+ QUIET_GROUP_CHAT_ID,
167
+ DENIED_GROUP_CHAT_ID,
168
+ ];
169
+
170
+ // A UTF-16 surrogate half without its partner - what Postgres jsonb refuses.
171
+ const LONE_SURROGATE: RegExp =
172
+ /[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/;
173
+
174
+ interface SeededRow {
175
+ id: ObjectID;
176
+ projectId: ObjectID;
177
+ tenantId: string;
178
+ authToken: string;
179
+ miscData: MicrosoftTeamsMiscData;
180
+ }
181
+
182
+ type SqlRow = Record<string, unknown>;
183
+
184
+ type GraphAnswer = HTTPResponse<JSONObject> | HTTPErrorResponse;
185
+
186
+ type GraphAnswerFactory = () => Promise<GraphAnswer>;
187
+
188
+ interface Deferred {
189
+ promise: Promise<void>;
190
+ resolve: () => void;
191
+ }
192
+
193
+ /*
194
+ * Records every statement Postgres rejected. PostgresQueryRunner hands each
195
+ * failure to the logger before it throws, whatever the caller then does with
196
+ * the error.
197
+ */
198
+ class FailedQueryRecorder implements Logger {
199
+ public failures: Array<string> = [];
200
+
201
+ public logQuery(): void {
202
+ return;
203
+ }
204
+
205
+ public logQueryError(error: string | Error, query: string): void {
206
+ this.failures.push(
207
+ `${error instanceof Error ? error.message : error} in: ${query}`,
208
+ );
209
+ }
210
+
211
+ public logQuerySlow(): void {
212
+ return;
213
+ }
214
+
215
+ public logSchemaBuild(): void {
216
+ return;
217
+ }
218
+
219
+ public logMigration(): void {
220
+ return;
221
+ }
222
+
223
+ public log(): void {
224
+ return;
225
+ }
226
+ }
227
+
228
+ function legacyGroupChat(): MicrosoftTeamsChat {
229
+ return {
230
+ id: LEGACY_GROUP_CHAT_ID,
231
+ name: "Alice Example, Bob Example",
232
+ chatType: "groupChat",
233
+ serviceUrl: SERVICE_URL,
234
+ addedAt: "2026-03-01T09:00:00.000Z",
235
+ };
236
+ }
237
+
238
+ function firstReleaseGroupChat(): MicrosoftTeamsChat {
239
+ return {
240
+ id: FIRST_RELEASE_GROUP_CHAT_ID,
241
+ name: "Uma Example, Victor Example, Wendy Example + 1 more",
242
+ chatType: "groupChat",
243
+ serviceUrl: SERVICE_URL,
244
+ addedAt: "2026-08-15T09:00:00.000Z",
245
+ memberAadObjectIds: ["aad-uma", "aad-victor", "aad-wendy", "aad-xavier"],
246
+ memberNames: [
247
+ "Uma Example",
248
+ "Victor Example",
249
+ "Wendy Example",
250
+ "Xavier Example",
251
+ ],
252
+ };
253
+ }
254
+
255
+ function rosterGroupChat(
256
+ overrides?: Partial<MicrosoftTeamsChat>,
257
+ ): MicrosoftTeamsChat {
258
+ return {
259
+ id: ROSTER_GROUP_CHAT_ID,
260
+ name: "Carol Example, Dan Example, Erin Example + 2 more",
261
+ chatType: "groupChat",
262
+ serviceUrl: SERVICE_URL,
263
+ addedAt: "2026-09-01T09:00:00.000Z",
264
+ memberAadObjectIds: [
265
+ "aad-carol",
266
+ "aad-dan",
267
+ "aad-erin",
268
+ "aad-ferdinand",
269
+ "aad-gina",
270
+ ],
271
+ memberNames: ["Carol Example", "Dan Example", "Erin Example"],
272
+ memberCount: 5,
273
+ ...(overrides || {}),
274
+ };
275
+ }
276
+
277
+ function namedGroupChat(): MicrosoftTeamsChat {
278
+ return {
279
+ id: NAMED_GROUP_CHAT_ID,
280
+ name: "Release Train",
281
+ topic: "Release Train",
282
+ chatType: "groupChat",
283
+ serviceUrl: SERVICE_URL,
284
+ addedAt: "2026-09-04T09:00:00.000Z",
285
+ memberAadObjectIds: ["aad-kim", "aad-lee"],
286
+ memberNames: ["Kim Example", "Lee Example"],
287
+ memberCount: 2,
288
+ };
289
+ }
290
+
291
+ function quietGroupChat(): MicrosoftTeamsChat {
292
+ return {
293
+ id: QUIET_GROUP_CHAT_ID,
294
+ name: "Status Updates",
295
+ topic: "Status Updates",
296
+ chatType: "groupChat",
297
+ serviceUrl: SERVICE_URL,
298
+ addedAt: "2026-09-05T09:00:00.000Z",
299
+ memberAadObjectIds: ["aad-nina", "aad-omar"],
300
+ memberNames: ["Nina Example", "Omar Example"],
301
+ memberCount: 2,
302
+ };
303
+ }
304
+
305
+ function deniedGroupChat(): MicrosoftTeamsChat {
306
+ return {
307
+ id: DENIED_GROUP_CHAT_ID,
308
+ name: "Frank Example, Grace Example",
309
+ chatType: "groupChat",
310
+ serviceUrl: SERVICE_URL,
311
+ addedAt: "2026-09-02T09:00:00.000Z",
312
+ memberAadObjectIds: ["aad-frank", "aad-grace"],
313
+ memberNames: ["Frank Example", "Grace Example"],
314
+ memberCount: 2,
315
+ };
316
+ }
317
+
318
+ function personalChat(): MicrosoftTeamsChat {
319
+ return {
320
+ id: PERSONAL_CHAT_ID,
321
+ name: "Erin Example",
322
+ chatType: "personal",
323
+ serviceUrl: SERVICE_URL,
324
+ addedAt: "2026-09-03T09:00:00.000Z",
325
+ memberAadObjectIds: ["aad-erin"],
326
+ };
327
+ }
328
+
329
+ function seedChats(): Record<string, MicrosoftTeamsChat> {
330
+ return {
331
+ [LEGACY_GROUP_CHAT_ID]: legacyGroupChat(),
332
+ [FIRST_RELEASE_GROUP_CHAT_ID]: firstReleaseGroupChat(),
333
+ [ROSTER_GROUP_CHAT_ID]: rosterGroupChat(),
334
+ [NAMED_GROUP_CHAT_ID]: namedGroupChat(),
335
+ [QUIET_GROUP_CHAT_ID]: quietGroupChat(),
336
+ [DENIED_GROUP_CHAT_ID]: deniedGroupChat(),
337
+ [PERSONAL_CHAT_ID]: personalChat(),
338
+ };
339
+ }
340
+
341
+ // Everything in miscData besides the chats, which no chat write may touch.
342
+ function seedMiscData(data: {
343
+ tenantId: string;
344
+ teamName: string;
345
+ availableChats: Record<string, MicrosoftTeamsChat>;
346
+ }): MicrosoftTeamsMiscData {
347
+ return {
348
+ tenantId: data.tenantId,
349
+ teamId: "team-1",
350
+ teamName: data.teamName,
351
+ botId: "bot-1",
352
+ adminConsentGranted: true,
353
+ adminConsentGrantedAt: "2026-02-01T00:00:00.000Z",
354
+ availableTeams: {
355
+ "a1b2c3d4-0000-4000-8000-000000000001": {
356
+ id: "a1b2c3d4-0000-4000-8000-000000000001",
357
+ name: "Engineering",
358
+ },
359
+ },
360
+ installedTeams: {
361
+ "a1b2c3d4-0000-4000-8000-000000000001": {
362
+ id: "a1b2c3d4-0000-4000-8000-000000000001",
363
+ graphTeamId: "a1b2c3d4-0000-4000-8000-000000000001",
364
+ teamsThreadId: "19:engineering@thread.tacv2",
365
+ name: "Engineering",
366
+ serviceUrl: SERVICE_URL,
367
+ },
368
+ },
369
+ availableChats: data.availableChats,
370
+ };
371
+ }
372
+
373
+ function withoutChats(miscData: MicrosoftTeamsMiscData): JSONObject {
374
+ const rest: JSONObject = { ...(miscData as unknown as JSONObject) };
375
+ delete rest["availableChats"];
376
+ return rest;
377
+ }
378
+
379
+ function graphOk(data: JSONObject): HTTPResponse<JSONObject> {
380
+ return new HTTPResponse<JSONObject>(200, data, {});
381
+ }
382
+
383
+ function graphError(statusCode: number, body: JSONObject): HTTPErrorResponse {
384
+ return new HTTPErrorResponse(statusCode, body, {});
385
+ }
386
+
387
+ // Graph answers with the chat's name in Teams (null: the chat has none).
388
+ function namedInTeams(topic: string | null): GraphAnswerFactory {
389
+ return async (): Promise<GraphAnswer> => {
390
+ return graphOk({ topic: topic, chatType: "group" });
391
+ };
392
+ }
393
+
394
+ // The chat has not granted OneUptime ChatSettings.Read.Chat.
395
+ function refused(): GraphAnswerFactory {
396
+ return async (): Promise<GraphAnswer> => {
397
+ return graphError(403, {
398
+ error: {
399
+ code: "Forbidden",
400
+ message: "Missing role permissions on the request.",
401
+ },
402
+ });
403
+ };
404
+ }
405
+
406
+ // Graph is having a bad moment.
407
+ function failing(): GraphAnswerFactory {
408
+ return async (): Promise<GraphAnswer> => {
409
+ return graphError(500, {
410
+ error: { code: "InternalServerError", message: "Service down" },
411
+ });
412
+ };
413
+ }
414
+
415
+ function everySeededGroupChat(
416
+ answer: GraphAnswerFactory,
417
+ ): Record<string, GraphAnswerFactory> {
418
+ const answers: Record<string, GraphAnswerFactory> = {};
419
+ for (const chatId of SEEDED_GROUP_CHAT_IDS) {
420
+ answers[chatId] = answer;
421
+ }
422
+ return answers;
423
+ }
424
+
425
+ /*
426
+ * What Teams says about each seeded group chat in the refresh tests: two
427
+ * chats have a name OneUptime does not show yet, one has no name (and is
428
+ * already listed by its members), one's name cannot be read right now, one
429
+ * is already up to date, and one refuses.
430
+ */
431
+ function refreshAnswers(): Record<string, GraphAnswerFactory> {
432
+ return {
433
+ [LEGACY_GROUP_CHAT_ID]: namedInTeams("Payments War Room"),
434
+ [FIRST_RELEASE_GROUP_CHAT_ID]: namedInTeams(null),
435
+ [ROSTER_GROUP_CHAT_ID]: namedInTeams(" Platform On-Call "),
436
+ [NAMED_GROUP_CHAT_ID]: failing(),
437
+ [QUIET_GROUP_CHAT_ID]: namedInTeams("Status Updates"),
438
+ [DENIED_GROUP_CHAT_ID]: refused(),
439
+ };
440
+ }
441
+
442
+ // The Graph request OneUptime makes for a chat: no $select, which would 400.
443
+ function graphChatUrl(chatId: string): string {
444
+ return `https://graph.microsoft.com/v1.0/chats/${encodeURIComponent(chatId)}`;
445
+ }
446
+
447
+ function deferred(): Deferred {
448
+ let resolve: () => void = () => {
449
+ return;
450
+ };
451
+ const promise: Promise<void> = new Promise<void>((done: () => void) => {
452
+ resolve = done;
453
+ });
454
+ return { promise: promise, resolve: resolve };
455
+ }
456
+
457
+ interface FakeTurnContext {
458
+ turnContext: TurnContext;
459
+ sendActivity: jest.Mock;
460
+ }
461
+
462
+ function buildTurnContext(): FakeTurnContext {
463
+ const sendActivity: jest.Mock = jest.fn(async () => {
464
+ return { id: "welcome-card" };
465
+ });
466
+
467
+ return {
468
+ turnContext: {
469
+ activity: {
470
+ recipient: { id: BOT_RECIPIENT_ID },
471
+ serviceUrl: SERVICE_URL,
472
+ conversation: { id: "ctx-conversation-id" },
473
+ },
474
+ turnState: new Map<string, unknown>(),
475
+ sendActivity: sendActivity,
476
+ } as unknown as TurnContext,
477
+ sendActivity: sendActivity,
478
+ };
479
+ }
480
+
481
+ function member(
482
+ id: string,
483
+ name: string,
484
+ aadObjectId?: string,
485
+ ): Partial<TeamsChannelAccount> {
486
+ return aadObjectId
487
+ ? { id: id, name: name, aadObjectId: aadObjectId }
488
+ : { id: id, name: name };
489
+ }
490
+
491
+ function mockRoster(members: Array<Partial<TeamsChannelAccount>>): void {
492
+ (TeamsInfo.getPagedMembers as jest.Mock).mockResolvedValue({
493
+ members: [
494
+ member(BOT_RECIPIENT_ID, "OneUptime"),
495
+ ...members,
496
+ ] as Array<TeamsChannelAccount>,
497
+ continuationToken: undefined,
498
+ } as unknown as TeamsPagedMembersResult);
499
+ }
500
+
501
+ function mockRosterUnavailable(): void {
502
+ (TeamsInfo.getPagedMembers as jest.Mock).mockRejectedValue(
503
+ new Error("The bot is not part of the conversation roster."),
504
+ );
505
+ }
506
+
507
+ function groupChatActivity(data: {
508
+ chatId: string;
509
+ tenantId: string;
510
+ name?: string | undefined;
511
+ botAdded?: boolean | undefined;
512
+ action?: string | undefined;
513
+ serviceUrl?: string | undefined;
514
+ }): JSONObject {
515
+ const conversation: JSONObject = {
516
+ conversationType: "groupChat",
517
+ id: data.chatId,
518
+ tenantId: data.tenantId,
519
+ };
520
+
521
+ if (data.name !== undefined) {
522
+ conversation["name"] = data.name;
523
+ }
524
+
525
+ return {
526
+ ...(data.botAdded ? { membersAdded: [{ id: BOT_RECIPIENT_ID }] } : {}),
527
+ ...(data.action ? { action: data.action } : {}),
528
+ conversation: conversation,
529
+ channelData: { tenant: { id: data.tenantId } },
530
+ serviceUrl: data.serviceUrl || SERVICE_URL,
531
+ };
532
+ }
533
+
534
+ describePostgres(
535
+ "Microsoft Teams group chat names (#4106) against a migrated Postgres",
536
+ () => {
537
+ const schema: string = `teams_chat_names_${ObjectID.generate()
538
+ .toString()
539
+ .replace(/-/g, "")}`;
540
+
541
+ const failedQueries: FailedQueryRecorder = new FailedQueryRecorder();
542
+
543
+ let database: DataSource;
544
+
545
+ // Two projects connected to the same tenant, and one to another tenant.
546
+ let projectA: SeededRow;
547
+ let projectB: SeededRow;
548
+ let otherTenantProject: SeededRow;
549
+
550
+ // Graph's answer per chat id; a chat left out is an unexpected call.
551
+ let graphAnswers: Record<string, GraphAnswerFactory> = {};
552
+ let graphRequests: Array<APIRequestOptions> = [];
553
+ let tokenSpy: jest.SpyInstance;
554
+
555
+ beforeAll(async () => {
556
+ database = new DataSource({
557
+ type: "postgres",
558
+ host:
559
+ process.env["MICROSOFT_TEAMS_CHAT_TEST_DATABASE_HOST"] || "localhost",
560
+ port: Number(
561
+ process.env["MICROSOFT_TEAMS_CHAT_TEST_DATABASE_PORT"] || "5400",
562
+ ),
563
+ username: process.env["DATABASE_USERNAME"] || "postgres",
564
+ password: process.env["DATABASE_PASSWORD"] || "password",
565
+ database:
566
+ process.env["MICROSOFT_TEAMS_CHAT_TEST_DATABASE_NAME"] ||
567
+ process.env["DATABASE_NAME"] ||
568
+ "oneuptimedb",
569
+ entities: Entities,
570
+ schema,
571
+ synchronize: false,
572
+ logging: ["error"],
573
+ logger: failedQueries,
574
+ extra: { options: `-c search_path=${schema},public` },
575
+ });
576
+ await database.initialize();
577
+
578
+ await database.query(`CREATE SCHEMA "${schema}"`);
579
+ await database.query(
580
+ `CREATE TABLE "${schema}"."${TABLE}" (LIKE public."${TABLE}" INCLUDING ALL)`,
581
+ );
582
+
583
+ const currentSchema: Array<{ current_schema: string }> =
584
+ await database.query("SELECT current_schema()");
585
+ expect(currentSchema[0]?.current_schema).toBe(schema);
586
+ });
587
+
588
+ beforeEach(async () => {
589
+ failedQueries.failures = [];
590
+ graphRequests = [];
591
+ graphAnswers = {};
592
+
593
+ jest.spyOn(PostgresAppInstance, "isConnected").mockReturnValue(true);
594
+ jest
595
+ .spyOn(PostgresAppInstance, "getDataSource")
596
+ .mockReturnValue(database);
597
+
598
+ (TeamsInfo.getPagedMembers as jest.Mock).mockReset();
599
+ (TeamsInfo.getMembers as jest.Mock).mockReset();
600
+
601
+ jest
602
+ .spyOn(API, "get")
603
+ .mockImplementation(
604
+ async (options: APIRequestOptions): Promise<GraphAnswer> => {
605
+ graphRequests.push(options);
606
+ const url: string = options.url.toString();
607
+ for (const [chatId, answer] of Object.entries(graphAnswers)) {
608
+ if (url === graphChatUrl(chatId)) {
609
+ return answer();
610
+ }
611
+ }
612
+ throw new Error(`Unexpected Graph call in test: ${url}`);
613
+ },
614
+ );
615
+
616
+ // The stored app tokens are fresh; minting a new one would be a bug here.
617
+ jest.spyOn(API, "post").mockImplementation(async () => {
618
+ throw new Error("Unexpected token request in test");
619
+ });
620
+
621
+ // Counted, not faked: the real one reads the token off the row.
622
+ tokenSpy = jest.spyOn(MicrosoftTeamsUtil, "getValidAccessToken");
623
+
624
+ await database.query(`DELETE FROM "${schema}"."${TABLE}"`);
625
+
626
+ projectA = await seedRow({
627
+ tenantId: TENANT_ID,
628
+ teamName: "Engineering (project A)",
629
+ availableChats: seedChats(),
630
+ });
631
+ projectB = await seedRow({
632
+ tenantId: TENANT_ID,
633
+ teamName: "Engineering (project B)",
634
+ availableChats: seedChats(),
635
+ });
636
+ // Holds chats under the SAME ids, so a tenant filter left off shows.
637
+ otherTenantProject = await seedRow({
638
+ tenantId: OTHER_TENANT_ID,
639
+ teamName: "Someone else's tenant",
640
+ availableChats: {
641
+ [ROSTER_GROUP_CHAT_ID]: rosterGroupChat({
642
+ name: "Another tenant's chat",
643
+ }),
644
+ [LEGACY_GROUP_CHAT_ID]: legacyGroupChat(),
645
+ [NAMED_GROUP_CHAT_ID]: namedGroupChat(),
646
+ },
647
+ });
648
+ });
649
+
650
+ afterEach(() => {
651
+ jest.restoreAllMocks();
652
+
653
+ // A failed statement fails the test, even if a caller swallowed it.
654
+ expect(failedQueries.failures).toEqual([]);
655
+ });
656
+
657
+ afterAll(async () => {
658
+ jest.restoreAllMocks();
659
+ if (database?.isInitialized) {
660
+ await database.query(`DROP SCHEMA IF EXISTS "${schema}" CASCADE`);
661
+ await database.destroy();
662
+ }
663
+ });
664
+
665
+ async function seedRow(data: {
666
+ tenantId: string;
667
+ teamName: string;
668
+ availableChats: Record<string, MicrosoftTeamsChat>;
669
+ }): Promise<SeededRow> {
670
+ const row: SeededRow = {
671
+ id: ObjectID.generate(),
672
+ projectId: ObjectID.generate(),
673
+ tenantId: data.tenantId,
674
+ authToken: `graph.${ObjectID.generate().toString()}.token`,
675
+ miscData: seedMiscData(data),
676
+ };
677
+
678
+ await database.query(
679
+ `INSERT INTO "${schema}"."${TABLE}"
680
+ ("_id", "projectId", "authToken", "authTokenExpiresAt", "workspaceType",
681
+ "workspaceProjectId", "miscData", "version")
682
+ VALUES ($1, $2, $3, now() + interval '1 day', $4, $5, $6, 1)`,
683
+ [
684
+ row.id.toString(),
685
+ row.projectId.toString(),
686
+ row.authToken,
687
+ WorkspaceType.MicrosoftTeams,
688
+ row.tenantId,
689
+ JSON.stringify(row.miscData),
690
+ ],
691
+ );
692
+
693
+ return row;
694
+ }
695
+
696
+ // The row as Postgres holds it, not as any service returns it.
697
+ async function storedRow(row: SeededRow): Promise<SqlRow> {
698
+ const rows: Array<SqlRow> = await database.query(
699
+ `SELECT "miscData", "version", "updatedAt" FROM "${schema}"."${TABLE}" WHERE "_id" = $1`,
700
+ [row.id.toString()],
701
+ );
702
+ expect(rows).toHaveLength(1);
703
+ return rows[0]!;
704
+ }
705
+
706
+ async function storedRows(): Promise<Array<SqlRow>> {
707
+ return [await storedRow(projectA), await storedRow(projectB)];
708
+ }
709
+
710
+ async function storedMiscData(
711
+ row: SeededRow,
712
+ ): Promise<MicrosoftTeamsMiscData> {
713
+ return (await storedRow(row))["miscData"] as MicrosoftTeamsMiscData;
714
+ }
715
+
716
+ async function storedChats(
717
+ row: SeededRow,
718
+ ): Promise<Record<string, MicrosoftTeamsChat>> {
719
+ return (await storedMiscData(row)).availableChats || {};
720
+ }
721
+
722
+ async function storedChat(
723
+ row: SeededRow,
724
+ chatId: string,
725
+ ): Promise<MicrosoftTeamsChat | undefined> {
726
+ return (await storedChats(row))[chatId];
727
+ }
728
+
729
+ // Each tenant row's optimistic-lock version: one write bumps it by one.
730
+ async function tenantRowVersions(): Promise<Array<number>> {
731
+ return (await storedRows()).map((row: SqlRow) => {
732
+ return Number(row["version"]);
733
+ });
734
+ }
735
+
736
+ // Whether the stored chat record has the key at all, as jsonb sees it.
737
+ async function storedChatHasKey(
738
+ row: SeededRow,
739
+ chatId: string,
740
+ key: string,
741
+ ): Promise<boolean> {
742
+ const rows: Array<{ present: boolean }> = await database.query(
743
+ `SELECT jsonb_exists("miscData"->'availableChats'->($1::text), $2::text) AS "present"
744
+ FROM "${schema}"."${TABLE}" WHERE "_id" = $3`,
745
+ [chatId, key, row.id.toString()],
746
+ );
747
+ return rows[0]!.present;
748
+ }
749
+
750
+ function isChatCapturedForTenant(data: {
751
+ tenantId: string;
752
+ chatId: string;
753
+ serviceUrl?: string | undefined;
754
+ }): Promise<boolean> {
755
+ return (MicrosoftTeamsUtil as any).isChatCapturedForTenant(data);
756
+ }
757
+
758
+ function captureFromMessage(activity: JSONObject): Promise<void> {
759
+ // What handleBotMessageActivity runs for every chat message.
760
+ return (MicrosoftTeamsUtil as any).captureChatFromBotActivity({
761
+ activity: activity,
762
+ turnContext: buildTurnContext().turnContext,
763
+ onlyIfMissingOrStale: true,
764
+ });
765
+ }
766
+
767
+ function botAddedToChat(data: {
768
+ chatId: string;
769
+ name?: string | undefined;
770
+ }): Promise<void> {
771
+ return MicrosoftTeamsUtil.handleConversationUpdateActivity({
772
+ activity: groupChatActivity({
773
+ chatId: data.chatId,
774
+ tenantId: TENANT_ID,
775
+ name: data.name,
776
+ botAdded: true,
777
+ }),
778
+ turnContext: buildTurnContext().turnContext,
779
+ });
780
+ }
781
+
782
+ function graphRequestUrls(): Array<string> {
783
+ return graphRequests
784
+ .map((request: APIRequestOptions) => {
785
+ return request.url.toString();
786
+ })
787
+ .sort();
788
+ }
789
+
790
+ async function expectOtherTenantUntouched(): Promise<void> {
791
+ const row: SqlRow = await storedRow(otherTenantProject);
792
+ expect(row["miscData"]).toEqual(otherTenantProject.miscData);
793
+ expect(Number(row["version"])).toBe(1);
794
+ }
795
+
796
+ async function expectRestOfMiscDataKept(row: SeededRow): Promise<void> {
797
+ expect(withoutChats(await storedMiscData(row))).toEqual(
798
+ withoutChats(row.miscData),
799
+ );
800
+ }
801
+
802
+ describe("capture (bot added / message backfill)", () => {
803
+ test("bot added to a named group chat: every project of the tenant stores the Teams name, the first three member names and the member count", async () => {
804
+ mockRoster([
805
+ member("29:heidi", "Heidi Example", "aad-heidi"),
806
+ member("29:ivan", "Ivan Example", "aad-ivan"),
807
+ member("29:judy", "Judy Example", "aad-judy"),
808
+ // A member Teams sent without a display name is not counted.
809
+ member("29:blank", " ", "aad-blank"),
810
+ member("29:karl", "Karl Example", "aad-karl"),
811
+ ]);
812
+ graphAnswers = {
813
+ [NEW_GROUP_CHAT_ID]: namedInTeams(" Incident Bridge "),
814
+ };
815
+
816
+ const { turnContext, sendActivity }: FakeTurnContext =
817
+ buildTurnContext();
818
+
819
+ await MicrosoftTeamsUtil.handleConversationUpdateActivity({
820
+ activity: groupChatActivity({
821
+ chatId: NEW_GROUP_CHAT_ID,
822
+ tenantId: TENANT_ID,
823
+ botAdded: true,
824
+ }),
825
+ turnContext: turnContext,
826
+ });
827
+
828
+ // One Graph read, with the tenant's app token from one of its rows.
829
+ expect(graphRequestUrls()).toEqual([graphChatUrl(NEW_GROUP_CHAT_ID)]);
830
+ expect([
831
+ `Bearer ${projectA.authToken}`,
832
+ `Bearer ${projectB.authToken}`,
833
+ ]).toContain(graphRequests[0]?.headers?.["Authorization"]);
834
+ expect(tokenSpy).toHaveBeenCalledTimes(1);
835
+
836
+ const storedA: MicrosoftTeamsChat | undefined = await storedChat(
837
+ projectA,
838
+ NEW_GROUP_CHAT_ID,
839
+ );
840
+
841
+ expect(storedA).toEqual({
842
+ id: NEW_GROUP_CHAT_ID,
843
+ name: "Incident Bridge",
844
+ topic: "Incident Bridge",
845
+ chatType: "groupChat",
846
+ serviceUrl: SERVICE_URL,
847
+ addedAt: expect.any(String),
848
+ memberAadObjectIds: [
849
+ "aad-heidi",
850
+ "aad-ivan",
851
+ "aad-judy",
852
+ "aad-blank",
853
+ "aad-karl",
854
+ ],
855
+ // Only what a chat named after its members shows; not the roster.
856
+ memberNames: ["Heidi Example", "Ivan Example", "Judy Example"],
857
+ memberCount: 4,
858
+ });
859
+ expect(Number.isNaN(Date.parse(storedA!.addedAt!))).toBe(false);
860
+
861
+ for (const row of [projectA, projectB]) {
862
+ expect(await storedChats(row)).toEqual({
863
+ ...seedChats(),
864
+ [NEW_GROUP_CHAT_ID]: storedA,
865
+ });
866
+ await expectRestOfMiscDataKept(row);
867
+ }
868
+
869
+ // As Postgres sees them: a three-name array and a numeric count.
870
+ const shapes: Array<SqlRow> = await database.query(
871
+ `SELECT jsonb_array_length("miscData"->'availableChats'->($1::text)->'memberNames') AS "names",
872
+ jsonb_typeof("miscData"->'availableChats'->($1::text)->'memberCount') AS "countType",
873
+ ("miscData"->'availableChats'->($1::text)->>'memberCount')::int AS "count"
874
+ FROM "${schema}"."${TABLE}" WHERE "workspaceProjectId" = $2`,
875
+ [NEW_GROUP_CHAT_ID, TENANT_ID],
876
+ );
877
+ expect(shapes).toEqual([
878
+ { names: 3, countType: "number", count: 4 },
879
+ { names: 3, countType: "number", count: 4 },
880
+ ]);
881
+
882
+ await expectOtherTenantUntouched();
883
+
884
+ // The welcome card still goes out.
885
+ expect(sendActivity).toHaveBeenCalledTimes(1);
886
+
887
+ // Captured with its member count, so the backfill leaves it alone now.
888
+ await expect(
889
+ isChatCapturedForTenant({
890
+ tenantId: TENANT_ID,
891
+ chatId: NEW_GROUP_CHAT_ID,
892
+ serviceUrl: SERVICE_URL,
893
+ }),
894
+ ).resolves.toBe(true);
895
+ });
896
+
897
+ test("a name on the activity is used as is, without asking Graph", async () => {
898
+ mockRoster([member("29:heidi", "Heidi Example", "aad-heidi")]);
899
+
900
+ await botAddedToChat({
901
+ chatId: NEW_GROUP_CHAT_ID,
902
+ name: " Release Train ",
903
+ });
904
+
905
+ expect(graphRequests).toEqual([]);
906
+ expect(tokenSpy).not.toHaveBeenCalled();
907
+ for (const row of [projectA, projectB]) {
908
+ expect(await storedChat(row, NEW_GROUP_CHAT_ID)).toMatchObject({
909
+ name: "Release Train",
910
+ topic: "Release Train",
911
+ memberNames: ["Heidi Example"],
912
+ memberCount: 1,
913
+ });
914
+ }
915
+ });
916
+
917
+ test("bot added where Graph refuses the name: the chat is stored under its members, with no Teams name", async () => {
918
+ mockRoster([
919
+ member("29:heidi", "Heidi Example", "aad-heidi"),
920
+ member("29:ivan", "Ivan Example", "aad-ivan"),
921
+ member("29:judy", "Judy Example", "aad-judy"),
922
+ member("29:karl", "Karl Example", "aad-karl"),
923
+ member("29:lena", "Lena Example", "aad-lena"),
924
+ ]);
925
+ graphAnswers = { [NEW_GROUP_CHAT_ID]: refused() };
926
+
927
+ await botAddedToChat({ chatId: NEW_GROUP_CHAT_ID });
928
+
929
+ for (const row of [projectA, projectB]) {
930
+ const chat: MicrosoftTeamsChat | undefined = await storedChat(
931
+ row,
932
+ NEW_GROUP_CHAT_ID,
933
+ );
934
+ expect(chat?.name).toBe(
935
+ "Heidi Example, Ivan Example, Judy Example + 2 more",
936
+ );
937
+ expect(chat?.memberNames).toEqual([
938
+ "Heidi Example",
939
+ "Ivan Example",
940
+ "Judy Example",
941
+ ]);
942
+ expect(chat?.memberCount).toBe(5);
943
+ expect(await storedChatHasKey(row, NEW_GROUP_CHAT_ID, "topic")).toBe(
944
+ false,
945
+ );
946
+ }
947
+ await expectOtherTenantUntouched();
948
+ });
949
+
950
+ test("a re-capture keeps the connection date and the stored Teams name when Graph refuses (403) or fails (500)", async () => {
951
+ // The bot is removed and added back; Graph now refuses the name.
952
+ mockRoster([
953
+ member("29:kim", "Kim Example", "aad-kim"),
954
+ member("29:lee", "Lee Example", "aad-lee"),
955
+ member("29:mia", "Mia Example", "aad-mia"),
956
+ ]);
957
+ graphAnswers = { [NAMED_GROUP_CHAT_ID]: refused() };
958
+
959
+ await botAddedToChat({ chatId: NAMED_GROUP_CHAT_ID });
960
+
961
+ const afterRefusal: MicrosoftTeamsChat = {
962
+ ...namedGroupChat(),
963
+ // The roster is new...
964
+ memberAadObjectIds: ["aad-kim", "aad-lee", "aad-mia"],
965
+ memberNames: ["Kim Example", "Lee Example", "Mia Example"],
966
+ memberCount: 3,
967
+ // ...the Teams name and the date it was first connected are not.
968
+ name: "Release Train",
969
+ topic: "Release Train",
970
+ addedAt: "2026-09-04T09:00:00.000Z",
971
+ };
972
+
973
+ for (const row of [projectA, projectB]) {
974
+ expect(await storedChat(row, NAMED_GROUP_CHAT_ID)).toEqual(
975
+ afterRefusal,
976
+ );
977
+ }
978
+
979
+ /*
980
+ * An app upgrade in the chat from another region, while neither the
981
+ * roster nor Graph can be read: only the service URL is news.
982
+ */
983
+ mockRosterUnavailable();
984
+ graphAnswers = { [NAMED_GROUP_CHAT_ID]: failing() };
985
+
986
+ await MicrosoftTeamsUtil.handleInstallationUpdateActivity({
987
+ activity: groupChatActivity({
988
+ chatId: NAMED_GROUP_CHAT_ID,
989
+ tenantId: TENANT_ID,
990
+ action: "add-upgrade",
991
+ serviceUrl: MOVED_SERVICE_URL,
992
+ }),
993
+ turnContext: buildTurnContext().turnContext,
994
+ });
995
+
996
+ expect(graphRequests).toHaveLength(2);
997
+ for (const row of [projectA, projectB]) {
998
+ expect(await storedChat(row, NAMED_GROUP_CHAT_ID)).toEqual({
999
+ ...afterRefusal,
1000
+ serviceUrl: MOVED_SERVICE_URL,
1001
+ });
1002
+ await expectRestOfMiscDataKept(row);
1003
+ }
1004
+ await expectOtherTenantUntouched();
1005
+ });
1006
+
1007
+ test("a legacy group chat whose roster cannot be read and whose name Graph refuses keeps its name, and stays due for re-capture", async () => {
1008
+ mockRosterUnavailable();
1009
+ graphAnswers = { [LEGACY_GROUP_CHAT_ID]: refused() };
1010
+
1011
+ const activity: JSONObject = groupChatActivity({
1012
+ chatId: LEGACY_GROUP_CHAT_ID,
1013
+ tenantId: TENANT_ID,
1014
+ });
1015
+
1016
+ await captureFromMessage(activity);
1017
+
1018
+ expect(TeamsInfo.getPagedMembers).toHaveBeenCalledTimes(1);
1019
+ expect(graphRequests).toHaveLength(1);
1020
+
1021
+ for (const row of [projectA, projectB]) {
1022
+ // Not "Group chat": nothing better was learned, so nothing is lost.
1023
+ expect(await storedChat(row, LEGACY_GROUP_CHAT_ID)).toEqual({
1024
+ ...legacyGroupChat(),
1025
+ memberAadObjectIds: [],
1026
+ });
1027
+ for (const key of ["memberCount", "memberNames", "topic"]) {
1028
+ expect(await storedChatHasKey(row, LEGACY_GROUP_CHAT_ID, key)).toBe(
1029
+ false,
1030
+ );
1031
+ }
1032
+ }
1033
+ await expectOtherTenantUntouched();
1034
+
1035
+ // Still no member count, so the next message tries the roster again.
1036
+ await expect(
1037
+ isChatCapturedForTenant({
1038
+ tenantId: TENANT_ID,
1039
+ chatId: LEGACY_GROUP_CHAT_ID,
1040
+ serviceUrl: SERVICE_URL,
1041
+ }),
1042
+ ).resolves.toBe(false);
1043
+
1044
+ await captureFromMessage(activity);
1045
+
1046
+ expect(TeamsInfo.getPagedMembers).toHaveBeenCalledTimes(2);
1047
+ });
1048
+
1049
+ test("the message backfill re-captures a legacy group chat once, under its Teams name", async () => {
1050
+ mockRoster([
1051
+ member("29:alice", "Alice Example", "aad-alice"),
1052
+ member("29:bob", "Bob Example", "aad-bob"),
1053
+ ]);
1054
+ graphAnswers = { [LEGACY_GROUP_CHAT_ID]: namedInTeams("Payments") };
1055
+
1056
+ const activity: JSONObject = groupChatActivity({
1057
+ chatId: LEGACY_GROUP_CHAT_ID,
1058
+ tenantId: TENANT_ID,
1059
+ });
1060
+
1061
+ await captureFromMessage(activity);
1062
+
1063
+ for (const row of [projectA, projectB]) {
1064
+ expect(await storedChat(row, LEGACY_GROUP_CHAT_ID)).toEqual({
1065
+ id: LEGACY_GROUP_CHAT_ID,
1066
+ name: "Payments",
1067
+ topic: "Payments",
1068
+ chatType: "groupChat",
1069
+ serviceUrl: SERVICE_URL,
1070
+ // Hearing from the chat again is not when it was connected.
1071
+ addedAt: "2026-03-01T09:00:00.000Z",
1072
+ memberAadObjectIds: ["aad-alice", "aad-bob"],
1073
+ memberNames: ["Alice Example", "Bob Example"],
1074
+ memberCount: 2,
1075
+ });
1076
+ await expectRestOfMiscDataKept(row);
1077
+ }
1078
+ await expectOtherTenantUntouched();
1079
+ expect(TeamsInfo.getPagedMembers).toHaveBeenCalledTimes(1);
1080
+ expect(graphRequests).toHaveLength(1);
1081
+
1082
+ // The next message finds it captured: no roster call, no Graph call.
1083
+ const before: Array<SqlRow> = await storedRows();
1084
+
1085
+ await captureFromMessage(activity);
1086
+
1087
+ expect(TeamsInfo.getPagedMembers).toHaveBeenCalledTimes(1);
1088
+ expect(graphRequests).toHaveLength(1);
1089
+ expect(await storedRows()).toEqual(before);
1090
+ });
1091
+
1092
+ test("the message backfill re-captures a chat the first release stored, keeping only three member names", async () => {
1093
+ mockRoster([
1094
+ member("29:uma", "Uma Example", "aad-uma"),
1095
+ member("29:victor", "Victor Example", "aad-victor"),
1096
+ member("29:wendy", "Wendy Example", "aad-wendy"),
1097
+ member("29:xavier", "Xavier Example", "aad-xavier"),
1098
+ ]);
1099
+ graphAnswers = { [FIRST_RELEASE_GROUP_CHAT_ID]: namedInTeams(null) };
1100
+
1101
+ await captureFromMessage(
1102
+ groupChatActivity({
1103
+ chatId: FIRST_RELEASE_GROUP_CHAT_ID,
1104
+ tenantId: TENANT_ID,
1105
+ }),
1106
+ );
1107
+
1108
+ for (const row of [projectA, projectB]) {
1109
+ expect(await storedChat(row, FIRST_RELEASE_GROUP_CHAT_ID)).toEqual({
1110
+ ...firstReleaseGroupChat(),
1111
+ // The full roster is no longer kept where every Viewer can read it.
1112
+ memberNames: ["Uma Example", "Victor Example", "Wendy Example"],
1113
+ memberCount: 4,
1114
+ });
1115
+ }
1116
+
1117
+ await expect(
1118
+ isChatCapturedForTenant({
1119
+ tenantId: TENANT_ID,
1120
+ chatId: FIRST_RELEASE_GROUP_CHAT_ID,
1121
+ serviceUrl: SERVICE_URL,
1122
+ }),
1123
+ ).resolves.toBe(true);
1124
+ });
1125
+
1126
+ test("the message backfill leaves a group chat with a member count alone", async () => {
1127
+ const before: Array<SqlRow> = await storedRows();
1128
+
1129
+ await captureFromMessage(
1130
+ groupChatActivity({
1131
+ chatId: ROSTER_GROUP_CHAT_ID,
1132
+ tenantId: TENANT_ID,
1133
+ }),
1134
+ );
1135
+
1136
+ expect(TeamsInfo.getPagedMembers).not.toHaveBeenCalled();
1137
+ expect(graphRequests).toEqual([]);
1138
+ expect(await storedRows()).toEqual(before);
1139
+ });
1140
+ });
1141
+
1142
+ describe("names Postgres must store as they are", () => {
1143
+ test("non-ASCII Teams and member names round-trip through jsonb character for character", async () => {
1144
+ const topic: string = "Équipe d'astreinte · 障害対応 🚨";
1145
+ // 28 UTF-16 units, 27 characters: the emoji is one character of two units.
1146
+ const topicCharacters: number = Array.from(topic).length;
1147
+ expect(topicCharacters).toBe(topic.length - 1);
1148
+ const memberNames: Array<string> = [
1149
+ "Zoë Ünïcode-Łukasz",
1150
+ "李雷",
1151
+ "Ольга Пример",
1152
+ "محمد مثال",
1153
+ ];
1154
+
1155
+ mockRoster(
1156
+ memberNames.map((name: string, index: number) => {
1157
+ return member(`29:member-${index}`, name, `aad-member-${index}`);
1158
+ }),
1159
+ );
1160
+ graphAnswers = { [NEW_GROUP_CHAT_ID]: namedInTeams(topic) };
1161
+
1162
+ await botAddedToChat({ chatId: NEW_GROUP_CHAT_ID });
1163
+
1164
+ for (const row of [projectA, projectB]) {
1165
+ expect(await storedChat(row, NEW_GROUP_CHAT_ID)).toMatchObject({
1166
+ name: topic,
1167
+ topic: topic,
1168
+ memberNames: memberNames.slice(0, 3),
1169
+ memberCount: 4,
1170
+ });
1171
+ }
1172
+
1173
+ /*
1174
+ * Compared inside Postgres, and counted in characters: text stored as
1175
+ * escapes or re-encoded bytes would differ in length.
1176
+ */
1177
+ const stored: Array<SqlRow> = await database.query(
1178
+ `SELECT "miscData"->'availableChats'->($1::text)->>'topic' = $2 AS "sameTopic",
1179
+ char_length("miscData"->'availableChats'->($1::text)->>'topic') AS "topicLength",
1180
+ "miscData"->'availableChats'->($1::text)->'memberNames' = $3::jsonb AS "sameMembers"
1181
+ FROM "${schema}"."${TABLE}" WHERE "workspaceProjectId" = $4`,
1182
+ [
1183
+ NEW_GROUP_CHAT_ID,
1184
+ topic,
1185
+ JSON.stringify(memberNames.slice(0, 3)),
1186
+ TENANT_ID,
1187
+ ],
1188
+ );
1189
+ expect(stored).toEqual([
1190
+ { sameTopic: true, topicLength: topicCharacters, sameMembers: true },
1191
+ { sameTopic: true, topicLength: topicCharacters, sameMembers: true },
1192
+ ]);
1193
+ });
1194
+
1195
+ test("a long Teams name cut where an emoji sits is stored, on capture and on refresh, without half an emoji", async () => {
1196
+ // Characters 78 and 79 (0-based UTF-16 units) are the two halves of 🚨.
1197
+ const longTopic: string =
1198
+ "A".repeat(78) + "🚨" + " Payments incident bridge, EMEA and APAC";
1199
+ // 78 characters and the ellipsis: the emoji is dropped whole.
1200
+ const expectedName: string = "A".repeat(78) + "…";
1201
+
1202
+ /*
1203
+ * The probe that makes this test mean something: a name cut by UTF-16
1204
+ * units alone ends in a lone high surrogate, and Postgres refuses the
1205
+ * write that carries it - the chat is not stored at all.
1206
+ */
1207
+ const naiveCut: string = `${longTopic.substring(0, 79)}…`;
1208
+ expect(LONE_SURROGATE.test(naiveCut)).toBe(true);
1209
+
1210
+ await expect(
1211
+ MicrosoftTeamsUtil.saveChatToProjectAuthTokens({
1212
+ tenantId: TENANT_ID,
1213
+ chat: {
1214
+ id: NEW_GROUP_CHAT_ID,
1215
+ name: naiveCut,
1216
+ chatType: "groupChat",
1217
+ serviceUrl: SERVICE_URL,
1218
+ addedAt: "2026-09-28T10:00:00.000Z",
1219
+ memberAadObjectIds: [],
1220
+ },
1221
+ }),
1222
+ ).rejects.toMatchObject({
1223
+ detail: "Unicode low surrogate must follow a high surrogate.",
1224
+ });
1225
+ expect(failedQueries.failures).toHaveLength(1);
1226
+ expect(failedQueries.failures[0]).toContain(
1227
+ "invalid input syntax for type json",
1228
+ );
1229
+ failedQueries.failures = [];
1230
+ expect(await storedChat(projectA, NEW_GROUP_CHAT_ID)).toBeUndefined();
1231
+
1232
+ // Capture: the bot is added to the chat with the long name.
1233
+ mockRoster([member("29:heidi", "Heidi Example", "aad-heidi")]);
1234
+ graphAnswers = { [NEW_GROUP_CHAT_ID]: namedInTeams(longTopic) };
1235
+
1236
+ await botAddedToChat({ chatId: NEW_GROUP_CHAT_ID });
1237
+
1238
+ for (const row of [projectA, projectB]) {
1239
+ const chat: MicrosoftTeamsChat | undefined = await storedChat(
1240
+ row,
1241
+ NEW_GROUP_CHAT_ID,
1242
+ );
1243
+ expect(chat?.name).toBe(expectedName);
1244
+ // The Teams name itself is kept whole, emoji included.
1245
+ expect(chat?.topic).toBe(longTopic);
1246
+ expect(LONE_SURROGATE.test(chat?.name || "")).toBe(false);
1247
+ expect(LONE_SURROGATE.test(chat?.topic || "")).toBe(false);
1248
+ }
1249
+
1250
+ // Refresh: the chat is renamed in Teams to another long name.
1251
+ const renamedTopic: string = "Ω".repeat(78) + "🚨" + " renamed bridge";
1252
+ graphAnswers = {
1253
+ ...everySeededGroupChat(refused()),
1254
+ [NEW_GROUP_CHAT_ID]: namedInTeams(renamedTopic),
1255
+ };
1256
+
1257
+ const result: MicrosoftTeamsChatNameRefreshResult =
1258
+ await MicrosoftTeamsUtil.refreshChatNamesForProject({
1259
+ projectId: projectA.projectId,
1260
+ });
1261
+
1262
+ expect(result.chats[NEW_GROUP_CHAT_ID]?.name).toBe(
1263
+ "Ω".repeat(78) + "…",
1264
+ );
1265
+
1266
+ const stored: Array<SqlRow> = await database.query(
1267
+ `SELECT "miscData"->'availableChats'->($1::text)->>'name' AS "name",
1268
+ char_length("miscData"->'availableChats'->($1::text)->>'name') AS "nameLength",
1269
+ "miscData"->'availableChats'->($1::text)->>'topic' AS "topic"
1270
+ FROM "${schema}"."${TABLE}" WHERE "workspaceProjectId" = $2`,
1271
+ [NEW_GROUP_CHAT_ID, TENANT_ID],
1272
+ );
1273
+ expect(stored).toEqual([
1274
+ {
1275
+ name: "Ω".repeat(78) + "…",
1276
+ nameLength: 79,
1277
+ topic: renamedTopic,
1278
+ },
1279
+ {
1280
+ name: "Ω".repeat(78) + "…",
1281
+ nameLength: 79,
1282
+ topic: renamedTopic,
1283
+ },
1284
+ ]);
1285
+ });
1286
+ });
1287
+
1288
+ describe("isChatCapturedForTenant (the message backfill's check)", () => {
1289
+ test("reads the stored records: group chats without a member count are stale, the rest are not", async () => {
1290
+ for (const chatId of [
1291
+ LEGACY_GROUP_CHAT_ID,
1292
+ // Stored its names, but no count: re-captured once as well.
1293
+ FIRST_RELEASE_GROUP_CHAT_ID,
1294
+ ]) {
1295
+ await expect(
1296
+ isChatCapturedForTenant({
1297
+ tenantId: TENANT_ID,
1298
+ chatId: chatId,
1299
+ serviceUrl: SERVICE_URL,
1300
+ }),
1301
+ ).resolves.toBe(false);
1302
+ }
1303
+
1304
+ for (const chatId of [
1305
+ ROSTER_GROUP_CHAT_ID,
1306
+ NAMED_GROUP_CHAT_ID,
1307
+ QUIET_GROUP_CHAT_ID,
1308
+ DENIED_GROUP_CHAT_ID,
1309
+ PERSONAL_CHAT_ID,
1310
+ ]) {
1311
+ await expect(
1312
+ isChatCapturedForTenant({
1313
+ tenantId: TENANT_ID,
1314
+ chatId: chatId,
1315
+ serviceUrl: SERVICE_URL,
1316
+ }),
1317
+ ).resolves.toBe(true);
1318
+ }
1319
+
1320
+ // A moved serviceUrl is still stale.
1321
+ await expect(
1322
+ isChatCapturedForTenant({
1323
+ tenantId: TENANT_ID,
1324
+ chatId: ROSTER_GROUP_CHAT_ID,
1325
+ serviceUrl: MOVED_SERVICE_URL,
1326
+ }),
1327
+ ).resolves.toBe(false);
1328
+
1329
+ // A tenant with no connected project has nothing to capture into.
1330
+ await expect(
1331
+ isChatCapturedForTenant({
1332
+ tenantId: "no-such-tenant",
1333
+ chatId: ROSTER_GROUP_CHAT_ID,
1334
+ serviceUrl: SERVICE_URL,
1335
+ }),
1336
+ ).resolves.toBe(true);
1337
+ });
1338
+
1339
+ test("a member count of 0 is a roster that was read, not a missing one", async () => {
1340
+ // A chat whose members Teams sent without display names.
1341
+ await MicrosoftTeamsUtil.saveChatToProjectAuthTokens({
1342
+ tenantId: TENANT_ID,
1343
+ chat: rosterGroupChat({ name: "Group chat", memberNames: [] }),
1344
+ });
1345
+ await database.query(
1346
+ `UPDATE "${schema}"."${TABLE}"
1347
+ SET "miscData" = jsonb_set("miscData", ARRAY['availableChats', $1::text, 'memberCount'], '0'::jsonb)
1348
+ WHERE "workspaceProjectId" = $2`,
1349
+ [ROSTER_GROUP_CHAT_ID, TENANT_ID],
1350
+ );
1351
+
1352
+ for (const row of [projectA, projectB]) {
1353
+ expect(
1354
+ (await storedChat(row, ROSTER_GROUP_CHAT_ID))?.memberCount,
1355
+ ).toBe(0);
1356
+ }
1357
+
1358
+ await expect(
1359
+ isChatCapturedForTenant({
1360
+ tenantId: TENANT_ID,
1361
+ chatId: ROSTER_GROUP_CHAT_ID,
1362
+ serviceUrl: SERVICE_URL,
1363
+ }),
1364
+ ).resolves.toBe(true);
1365
+ });
1366
+
1367
+ test("a chat missing from one project of the tenant is stale", async () => {
1368
+ await database.query(
1369
+ `UPDATE "${schema}"."${TABLE}"
1370
+ SET "miscData" = jsonb_set("miscData", '{availableChats}', ("miscData"->'availableChats') - $1::text)
1371
+ WHERE "_id" = $2`,
1372
+ [ROSTER_GROUP_CHAT_ID, projectB.id.toString()],
1373
+ );
1374
+
1375
+ await expect(
1376
+ isChatCapturedForTenant({
1377
+ tenantId: TENANT_ID,
1378
+ chatId: ROSTER_GROUP_CHAT_ID,
1379
+ serviceUrl: SERVICE_URL,
1380
+ }),
1381
+ ).resolves.toBe(false);
1382
+ });
1383
+ });
1384
+
1385
+ describe("refreshChatNamesForProject (Refresh Chats)", () => {
1386
+ test("renames on both projects of the tenant with the Teams name, reports refusals and failures by id, and reads the token once", async () => {
1387
+ graphAnswers = refreshAnswers();
1388
+
1389
+ const result: MicrosoftTeamsChatNameRefreshResult =
1390
+ await MicrosoftTeamsUtil.refreshChatNamesForProject({
1391
+ projectId: projectA.projectId,
1392
+ });
1393
+
1394
+ expect(result.permissionDeniedChatIds).toEqual([DENIED_GROUP_CHAT_ID]);
1395
+ expect(result.failedChatIds).toEqual([NAMED_GROUP_CHAT_ID]);
1396
+
1397
+ /*
1398
+ * One Graph read per group chat, none for the personal chat - two
1399
+ * batches, one token: the one stored on the project's own row.
1400
+ */
1401
+ expect(graphRequestUrls()).toEqual(
1402
+ SEEDED_GROUP_CHAT_IDS.map(graphChatUrl).sort(),
1403
+ );
1404
+ for (const request of graphRequests) {
1405
+ expect(request.headers?.["Authorization"]).toBe(
1406
+ `Bearer ${projectA.authToken}`,
1407
+ );
1408
+ }
1409
+ expect(tokenSpy).toHaveBeenCalledTimes(1);
1410
+
1411
+ const expected: Record<string, MicrosoftTeamsChat> = {
1412
+ ...seedChats(),
1413
+ [LEGACY_GROUP_CHAT_ID]: {
1414
+ ...legacyGroupChat(),
1415
+ name: "Payments War Room",
1416
+ topic: "Payments War Room",
1417
+ },
1418
+ [ROSTER_GROUP_CHAT_ID]: rosterGroupChat({
1419
+ name: "Platform On-Call",
1420
+ topic: "Platform On-Call",
1421
+ }),
1422
+ };
1423
+
1424
+ // What the page is sent is what is stored.
1425
+ expect(result.chats).toEqual(expected);
1426
+
1427
+ for (const row of [projectA, projectB]) {
1428
+ expect(await storedChats(row)).toEqual(expected);
1429
+ // A name-only write: the legacy record gains no roster.
1430
+ for (const key of ["memberCount", "memberNames"]) {
1431
+ expect(await storedChatHasKey(row, LEGACY_GROUP_CHAT_ID, key)).toBe(
1432
+ false,
1433
+ );
1434
+ }
1435
+ await expectRestOfMiscDataKept(row);
1436
+
1437
+ await expect(
1438
+ MicrosoftTeamsUtil.getChatsForProject({
1439
+ projectId: row.projectId,
1440
+ }),
1441
+ ).resolves.toEqual(expected);
1442
+ }
1443
+
1444
+ // Both renames in one write per row.
1445
+ expect(await tenantRowVersions()).toEqual([2, 2]);
1446
+
1447
+ await expectOtherTenantUntouched();
1448
+ });
1449
+
1450
+ test('a name removed in Teams falls back to the stored member names, or to "Group chat" when there are none', async () => {
1451
+ // A named chat whose members all came without display names.
1452
+ const namelessMembers: MicrosoftTeamsChat = {
1453
+ ...quietGroupChat(),
1454
+ memberNames: [],
1455
+ memberCount: 0,
1456
+ };
1457
+ await MicrosoftTeamsUtil.saveChatToProjectAuthTokens({
1458
+ tenantId: TENANT_ID,
1459
+ chat: namelessMembers,
1460
+ });
1461
+
1462
+ graphAnswers = {
1463
+ ...everySeededGroupChat(namedInTeams(null)),
1464
+ [FIRST_RELEASE_GROUP_CHAT_ID]: refused(),
1465
+ [DENIED_GROUP_CHAT_ID]: refused(),
1466
+ };
1467
+
1468
+ const result: MicrosoftTeamsChatNameRefreshResult =
1469
+ await MicrosoftTeamsUtil.refreshChatNamesForProject({
1470
+ projectId: projectB.projectId,
1471
+ });
1472
+
1473
+ expect([...result.permissionDeniedChatIds].sort()).toEqual(
1474
+ [FIRST_RELEASE_GROUP_CHAT_ID, DENIED_GROUP_CHAT_ID].sort(),
1475
+ );
1476
+ expect(result.failedChatIds).toEqual([]);
1477
+
1478
+ const namedWithoutTopic: MicrosoftTeamsChat = {
1479
+ ...namedGroupChat(),
1480
+ name: "Kim Example, Lee Example",
1481
+ };
1482
+ delete namedWithoutTopic.topic;
1483
+
1484
+ const quietWithoutTopic: MicrosoftTeamsChat = {
1485
+ ...namelessMembers,
1486
+ name: "Group chat",
1487
+ };
1488
+ delete quietWithoutTopic.topic;
1489
+
1490
+ for (const row of [projectA, projectB]) {
1491
+ expect(await storedChats(row)).toEqual({
1492
+ ...seedChats(),
1493
+ [NAMED_GROUP_CHAT_ID]: namedWithoutTopic,
1494
+ [QUIET_GROUP_CHAT_ID]: quietWithoutTopic,
1495
+ /*
1496
+ * Unchanged, and so not rewritten: the roster chat already shows
1497
+ * its first three members and the count of the rest, and the
1498
+ * legacy chat has no members to fall back to.
1499
+ */
1500
+ [ROSTER_GROUP_CHAT_ID]: rosterGroupChat(),
1501
+ [LEGACY_GROUP_CHAT_ID]: legacyGroupChat(),
1502
+ });
1503
+ for (const chatId of [NAMED_GROUP_CHAT_ID, QUIET_GROUP_CHAT_ID]) {
1504
+ expect(await storedChatHasKey(row, chatId, "topic")).toBe(false);
1505
+ }
1506
+ }
1507
+
1508
+ await expectOtherTenantUntouched();
1509
+ });
1510
+
1511
+ test("a chat removed while names were read is neither resurrected nor returned, and one re-captured meanwhile keeps its new record under its Teams name", async () => {
1512
+ const recaptured: MicrosoftTeamsChat = rosterGroupChat({
1513
+ name: "Carol Example, Dan Example, Erin Example + 3 more",
1514
+ serviceUrl: MOVED_SERVICE_URL,
1515
+ memberAadObjectIds: [
1516
+ "aad-carol",
1517
+ "aad-dan",
1518
+ "aad-erin",
1519
+ "aad-ferdinand",
1520
+ "aad-gina",
1521
+ "aad-oscar",
1522
+ ],
1523
+ memberCount: 6,
1524
+ });
1525
+
1526
+ /*
1527
+ * Both happen while the lookups are out, one after the other in one
1528
+ * answer: they are read-modify-writes of the same rows, and this test
1529
+ * is about the rename that follows them, not about each other.
1530
+ */
1531
+ graphAnswers = {
1532
+ ...refreshAnswers(),
1533
+ [LEGACY_GROUP_CHAT_ID]: async (): Promise<GraphAnswer> => {
1534
+ // The bot is removed from the chat mid-refresh...
1535
+ await MicrosoftTeamsUtil.removeChatFromProjectAuthTokens({
1536
+ tenantId: TENANT_ID,
1537
+ chatId: LEGACY_GROUP_CHAT_ID,
1538
+ });
1539
+ // ...and someone joins another, which is captured again.
1540
+ await MicrosoftTeamsUtil.saveChatToProjectAuthTokens({
1541
+ tenantId: TENANT_ID,
1542
+ chat: recaptured,
1543
+ });
1544
+ return graphOk({ topic: "Payments War Room" });
1545
+ },
1546
+ };
1547
+
1548
+ const result: MicrosoftTeamsChatNameRefreshResult =
1549
+ await MicrosoftTeamsUtil.refreshChatNamesForProject({
1550
+ projectId: projectA.projectId,
1551
+ });
1552
+
1553
+ const expected: Record<string, MicrosoftTeamsChat> = seedChats();
1554
+ delete expected[LEGACY_GROUP_CHAT_ID];
1555
+ expected[ROSTER_GROUP_CHAT_ID] = {
1556
+ ...recaptured,
1557
+ name: "Platform On-Call",
1558
+ topic: "Platform On-Call",
1559
+ };
1560
+
1561
+ expect(result.chats).toEqual(expected);
1562
+ expect(result.chats[LEGACY_GROUP_CHAT_ID]).toBeUndefined();
1563
+
1564
+ for (const row of [projectA, projectB]) {
1565
+ expect(await storedChats(row)).toEqual(expected);
1566
+ await expectRestOfMiscDataKept(row);
1567
+ }
1568
+
1569
+ // It holds chats under both ids.
1570
+ await expectOtherTenantUntouched();
1571
+ });
1572
+
1573
+ test("two refreshes of one project at once share one pass over the chats and one write per row", async () => {
1574
+ const lookupStarted: Deferred = deferred();
1575
+ const releaseLookup: Deferred = deferred();
1576
+
1577
+ graphAnswers = {
1578
+ ...refreshAnswers(),
1579
+ [LEGACY_GROUP_CHAT_ID]: async (): Promise<GraphAnswer> => {
1580
+ lookupStarted.resolve();
1581
+ await releaseLookup.promise;
1582
+ return graphOk({ topic: "Payments War Room" });
1583
+ },
1584
+ };
1585
+
1586
+ const first: Promise<MicrosoftTeamsChatNameRefreshResult> =
1587
+ MicrosoftTeamsUtil.refreshChatNamesForProject({
1588
+ projectId: projectA.projectId,
1589
+ });
1590
+
1591
+ // A second click while the first is still reading names.
1592
+ await lookupStarted.promise;
1593
+ const second: Promise<MicrosoftTeamsChatNameRefreshResult> =
1594
+ MicrosoftTeamsUtil.refreshChatNamesForProject({
1595
+ projectId: projectA.projectId,
1596
+ });
1597
+
1598
+ releaseLookup.resolve();
1599
+
1600
+ const results: Array<MicrosoftTeamsChatNameRefreshResult> =
1601
+ await Promise.all([first, second]);
1602
+
1603
+ expect(results[1]).toEqual(results[0]);
1604
+ expect(results[0]?.chats[LEGACY_GROUP_CHAT_ID]?.name).toBe(
1605
+ "Payments War Room",
1606
+ );
1607
+ expect(graphRequests).toHaveLength(SEEDED_GROUP_CHAT_IDS.length);
1608
+ expect(tokenSpy).toHaveBeenCalledTimes(1);
1609
+ expect(await tenantRowVersions()).toEqual([2, 2]);
1610
+
1611
+ // Once it has settled, the next click runs a refresh of its own.
1612
+ await MicrosoftTeamsUtil.refreshChatNamesForProject({
1613
+ projectId: projectA.projectId,
1614
+ });
1615
+
1616
+ expect(graphRequests).toHaveLength(2 * SEEDED_GROUP_CHAT_IDS.length);
1617
+ expect(tokenSpy).toHaveBeenCalledTimes(2);
1618
+ // Nothing new to write.
1619
+ expect(await tenantRowVersions()).toEqual([2, 2]);
1620
+ });
1621
+
1622
+ test("a project with no group chats reads no token, makes no Graph call and writes nothing", async () => {
1623
+ await database.query(
1624
+ `UPDATE "${schema}"."${TABLE}"
1625
+ SET "miscData" = jsonb_set("miscData", '{availableChats}', jsonb_build_object($1::text, "miscData"->'availableChats'->($1::text)))
1626
+ WHERE "workspaceProjectId" = $2`,
1627
+ [PERSONAL_CHAT_ID, TENANT_ID],
1628
+ );
1629
+ const before: SqlRow = await storedRow(projectA);
1630
+
1631
+ const result: MicrosoftTeamsChatNameRefreshResult =
1632
+ await MicrosoftTeamsUtil.refreshChatNamesForProject({
1633
+ projectId: projectA.projectId,
1634
+ });
1635
+
1636
+ expect(result).toEqual({
1637
+ chats: { [PERSONAL_CHAT_ID]: personalChat() },
1638
+ permissionDeniedChatIds: [],
1639
+ failedChatIds: [],
1640
+ });
1641
+ expect(graphRequests).toEqual([]);
1642
+ expect(tokenSpy).not.toHaveBeenCalled();
1643
+ expect(await storedRow(projectA)).toEqual(before);
1644
+ });
1645
+ });
1646
+
1647
+ describe("renameChatsInProjectAuthTokens", () => {
1648
+ test("writes the name and Teams name onto chats that exist, drops a removed Teams name, and skips rows it would not change", async () => {
1649
+ const updates: Record<string, MicrosoftTeamsChatNameUpdate> = {
1650
+ [ROSTER_GROUP_CHAT_ID]: {
1651
+ name: "Platform On-Call",
1652
+ topic: "Platform On-Call",
1653
+ },
1654
+ // The chat's name was removed in Teams.
1655
+ [NAMED_GROUP_CHAT_ID]: { name: "Kim Example, Lee Example" },
1656
+ [NEW_GROUP_CHAT_ID]: { name: "Never stored", topic: "Never stored" },
1657
+ };
1658
+
1659
+ await MicrosoftTeamsUtil.renameChatsInProjectAuthTokens({
1660
+ tenantId: TENANT_ID,
1661
+ chatNames: updates,
1662
+ });
1663
+
1664
+ const namedWithoutTopic: MicrosoftTeamsChat = {
1665
+ ...namedGroupChat(),
1666
+ name: "Kim Example, Lee Example",
1667
+ };
1668
+ delete namedWithoutTopic.topic;
1669
+
1670
+ for (const row of [projectA, projectB]) {
1671
+ const chats: Record<string, MicrosoftTeamsChat> =
1672
+ await storedChats(row);
1673
+ expect(chats).toEqual({
1674
+ ...seedChats(),
1675
+ [ROSTER_GROUP_CHAT_ID]: rosterGroupChat({
1676
+ name: "Platform On-Call",
1677
+ topic: "Platform On-Call",
1678
+ }),
1679
+ [NAMED_GROUP_CHAT_ID]: namedWithoutTopic,
1680
+ });
1681
+ expect(chats[NEW_GROUP_CHAT_ID]).toBeUndefined();
1682
+ expect(
1683
+ await storedChatHasKey(row, NAMED_GROUP_CHAT_ID, "topic"),
1684
+ ).toBe(false);
1685
+ await expectRestOfMiscDataKept(row);
1686
+ }
1687
+ expect(await tenantRowVersions()).toEqual([2, 2]);
1688
+ await expectOtherTenantUntouched();
1689
+
1690
+ // Nothing left to change: no row is written again.
1691
+ const before: Array<SqlRow> = await storedRows();
1692
+
1693
+ await MicrosoftTeamsUtil.renameChatsInProjectAuthTokens({
1694
+ tenantId: TENANT_ID,
1695
+ chatNames: updates,
1696
+ });
1697
+
1698
+ expect(await storedRows()).toEqual(before);
1699
+ });
1700
+ });
1701
+
1702
+ describe("removeChatFromProjectAuthTokens", () => {
1703
+ test("removes the chat from every row of the tenant and keeps the rest", async () => {
1704
+ await MicrosoftTeamsUtil.removeChatFromProjectAuthTokens({
1705
+ tenantId: TENANT_ID,
1706
+ chatId: ROSTER_GROUP_CHAT_ID,
1707
+ });
1708
+
1709
+ const expected: Record<string, MicrosoftTeamsChat> = seedChats();
1710
+ delete expected[ROSTER_GROUP_CHAT_ID];
1711
+
1712
+ for (const row of [projectA, projectB]) {
1713
+ expect(await storedChats(row)).toEqual(expected);
1714
+ await expectRestOfMiscDataKept(row);
1715
+ }
1716
+
1717
+ await expectOtherTenantUntouched();
1718
+ });
1719
+ });
1720
+ },
1721
+ );