@oneuptime/common 12.0.18 → 12.0.20

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 (262) hide show
  1. package/Models/DatabaseModels/EnterpriseLicense.ts +23 -0
  2. package/Models/DatabaseModels/Index.ts +4 -2
  3. package/Models/DatabaseModels/Monitor.ts +39 -9
  4. package/Models/DatabaseModels/UserMicrosoftTeams.ts +344 -0
  5. package/Models/DatabaseModels/UserNotificationRule.ts +144 -8
  6. package/Models/DatabaseModels/UserNotificationSetting.ts +34 -0
  7. package/Models/DatabaseModels/UserOnCallLogTimeline.ts +95 -0
  8. package/Models/DatabaseModels/UserSlack.ts +341 -0
  9. package/Models/DatabaseModels/WorkspaceProjectAuthToken.ts +9 -0
  10. package/Server/API/EnterpriseLicenseAPI.ts +94 -19
  11. package/Server/API/UserMicrosoftTeamsAPI.ts +127 -0
  12. package/Server/API/UserSlackAPI.ts +125 -0
  13. package/Server/EnvironmentConfig.ts +29 -107
  14. package/Server/Infrastructure/Postgres/SchemaMigrations/1788700000000-RemoveMarketingConversionUploadState.ts +33 -0
  15. package/Server/Infrastructure/Postgres/SchemaMigrations/1788800000000-DropMarketingConversionAddEnterpriseLicenseEmail.ts +51 -0
  16. package/Server/Infrastructure/Postgres/SchemaMigrations/1788900000000-RedactStoredMonitorIngestSecrets.ts +117 -0
  17. package/Server/Infrastructure/Postgres/SchemaMigrations/1789000000000-AddUserSlackAndMicrosoftTeams.ts +197 -0
  18. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +8 -0
  19. package/Server/Infrastructure/Queue.ts +7 -0
  20. package/Server/Services/EnterpriseLicenseService.ts +60 -0
  21. package/Server/Services/Index.ts +6 -2
  22. package/Server/Services/LlmLogService.ts +25 -9
  23. package/Server/Services/OnCallReadinessService.ts +86 -2
  24. package/Server/Services/ProjectService.ts +171 -0
  25. package/Server/Services/UserMicrosoftTeamsService.ts +208 -0
  26. package/Server/Services/UserNotificationMethodAdminService.ts +167 -5
  27. package/Server/Services/UserNotificationRuleAdminService.ts +40 -6
  28. package/Server/Services/UserNotificationRuleService.ts +589 -11
  29. package/Server/Services/UserNotificationSettingService.ts +138 -0
  30. package/Server/Services/UserService.ts +28 -0
  31. package/Server/Services/UserSlackService.ts +218 -0
  32. package/Server/Services/WorkspaceProjectAuthTokenService.ts +67 -1
  33. package/Server/Services/WorkspaceUserAuthTokenService.ts +73 -0
  34. package/Server/Services/WorkspaceUserNotificationService.ts +190 -0
  35. package/Server/Utils/Marketing/MarketingEventUtil.ts +145 -0
  36. package/Server/Utils/Marketing/MarketingEventWebhook.ts +114 -0
  37. package/Server/Utils/Monitor/MonitorLogUtil.ts +14 -1
  38. package/Server/Utils/Monitor/MonitorPayloadRedaction.ts +321 -0
  39. package/Server/Utils/Response.ts +2 -2
  40. package/Server/Utils/StartServer.ts +2 -2
  41. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +191 -4
  42. package/Server/Views/Partials/AnalyticsConsent.ejs +533 -0
  43. package/Tests/App/Dashboard/AdminNotificationRulesPage.test.tsx +22 -14
  44. package/Tests/App/Dashboard/AdminUserNotificationMethodsPage.test.tsx +22 -11
  45. package/Tests/App/Dashboard/AlertEpisodeViewFields.test.tsx +465 -0
  46. package/Tests/App/Dashboard/DashboardChartWidgetZoom.test.tsx +302 -0
  47. package/Tests/App/Dashboard/DiscoveryScanWizardValidation.test.tsx +408 -0
  48. package/Tests/App/Dashboard/IncidentEpisodeViewFields.test.tsx +474 -0
  49. package/Tests/App/Dashboard/MetricSeriesInvestigateMenu.test.tsx +368 -0
  50. package/Tests/App/Dashboard/OnCallPreventionGuards.test.tsx +37 -1
  51. package/Tests/App/Dashboard/OnCallRulesTable.test.tsx +18 -10
  52. package/Tests/Models/DatabaseModels/DomainRoleTierCoverage.test.ts +27 -1
  53. package/Tests/Models/DatabaseModels/MonitorSecretKeyColumnAccessControl.test.ts +214 -0
  54. package/Tests/Server/API/EnterpriseLicenseReportUserCount.test.ts +763 -0
  55. package/Tests/Server/API/OnCallReadinessAPI.test.ts +145 -26
  56. package/Tests/Server/API/UserNotificationMethodAdminAPI.test.ts +2 -2
  57. package/Tests/Server/Services/AIServiceDailyBudget.test.ts +11 -1
  58. package/Tests/Server/Services/AdminRuleEditGuards.test.ts +22 -8
  59. package/Tests/Server/Services/DeliverNotificationForRuleExtraction.test.ts +18 -2
  60. package/Tests/Server/Services/EpisodeRuleSeverityRepair.test.ts +5 -1
  61. package/Tests/Server/Services/LlmLogServiceTokenAggregateParams.test.ts +640 -0
  62. package/Tests/Server/Services/NotificationChannelEventCoverage.test.ts +246 -22
  63. package/Tests/Server/Services/NotificationDeletionImpact.test.ts +136 -8
  64. package/Tests/Server/Services/OnCallNotificationFallback.test.ts +191 -8
  65. package/Tests/Server/Services/OnCallReadinessService.test.ts +343 -18
  66. package/Tests/Server/Services/ProjectServiceChangePlan.test.ts +138 -0
  67. package/Tests/Server/Services/ProjectServiceCreateSubscriptionStarted.test.ts +516 -0
  68. package/Tests/Server/Services/SeverityRuleBackfill.test.ts +5 -1
  69. package/Tests/Server/Services/UserMicrosoftTeamsService.test.ts +343 -0
  70. package/Tests/Server/Services/UserNotificationMethodAdminService.test.ts +209 -8
  71. package/Tests/Server/Services/UserNotificationRuleAdminGuards.test.ts +22 -6
  72. package/Tests/Server/Services/UserNotificationRuleDefaultCreation.test.ts +12 -6
  73. package/Tests/Server/Services/UserNotificationRuleExecuteItem.test.ts +37 -3
  74. package/Tests/Server/Services/UserNotificationRuleWorkspaceDelivery.test.ts +669 -0
  75. package/Tests/Server/Services/UserNotificationSettingWorkspaceChannels.test.ts +410 -0
  76. package/Tests/Server/Services/UserSlackService.test.ts +407 -0
  77. package/Tests/Server/Services/WorkspaceProjectAuthTokenDisconnectCascade.test.ts +224 -0
  78. package/Tests/Server/Services/WorkspaceUserAuthTokenNotificationMethodCascade.test.ts +189 -0
  79. package/Tests/Server/Services/WorkspaceUserNotificationService.test.ts +496 -0
  80. package/Tests/Server/Types/Database/Permissions/AdminNotificationRuleAccess.test.ts +30 -17
  81. package/Tests/Server/Types/Database/Permissions/CreateOwnershipScoping.test.ts +8 -3
  82. package/Tests/Server/Types/Database/Permissions/OwnerOnlyColumns.test.ts +72 -3
  83. package/Tests/Server/Types/Database/Permissions/UserNotificationRuleScoping.test.ts +22 -18
  84. package/Tests/Server/Types/Database/Permissions/WorkspaceMethodStampedColumnCreate.test.ts +178 -0
  85. package/Tests/Server/Utils/Marketing/MarketingEventUtil.test.ts +259 -0
  86. package/Tests/Server/Utils/Marketing/MarketingEventWebhook.test.ts +257 -0
  87. package/Tests/Server/Utils/Monitor/MonitorLogUtilRedaction.test.ts +300 -0
  88. package/Tests/Server/Utils/Monitor/MonitorPayloadRedaction.test.ts +381 -0
  89. package/Tests/Server/Utils/ResponseRenderAnalytics.test.ts +53 -0
  90. package/Tests/Server/Utils/Runbook/RunbookExecutePermission.test.ts +228 -0
  91. package/Tests/Server/Utils/SessionReplay/SessionReplayErasureTombstone.test.ts +189 -0
  92. package/Tests/Server/Utils/SessionReplay/SessionReplayUsage.test.ts +205 -0
  93. package/Tests/Server/Utils/Telemetry/AppMetrics.test.ts +275 -0
  94. package/Tests/Server/Utils/Telemetry/TelemetryContext.test.ts +279 -0
  95. package/Tests/Server/Utils/Workspace/MicrosoftTeamsDirectMessage.test.ts +454 -0
  96. package/Tests/Server/Views/AnalyticsConsentPartial.test.ts +241 -0
  97. package/Tests/UI/Components/Charts/ChartTooltipSorted.test.tsx +129 -0
  98. package/Tests/UI/Components/Charts/TooltipEntries.test.ts +155 -0
  99. package/Tests/UI/Components/Detail/DetailFieldErrors.test.tsx +347 -0
  100. package/Tests/UI/Components/Forms/BasicFormStepValidation.test.tsx +392 -0
  101. package/Tests/UI/Components/Forms/ValidationFormSteps.test.ts +549 -0
  102. package/Tests/UI/Utils/NotificationMethodUtil.test.ts +137 -9
  103. package/Tests/UI/Utils/PermissionGate.test.ts +105 -0
  104. package/Tests/Utils/EnterpriseLicenseSync.test.ts +562 -0
  105. package/Types/Analytics/RevenueEvent.ts +1 -0
  106. package/Types/Marketing/MarketingEvent.ts +140 -0
  107. package/UI/Components/Charts/Area/AreaChart.tsx +8 -1
  108. package/UI/Components/Charts/Bar/BarChart.tsx +8 -1
  109. package/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.tsx +19 -4
  110. package/UI/Components/Charts/ChartLibrary/BarChart/BarChart.tsx +20 -2
  111. package/UI/Components/Charts/ChartLibrary/LineChart/LineChart.tsx +19 -4
  112. package/UI/Components/Charts/ChartLibrary/Utils/TooltipEntries.ts +105 -0
  113. package/UI/Components/Charts/Line/LineChart.tsx +8 -1
  114. package/UI/Components/Detail/Detail.tsx +18 -1
  115. package/UI/Utils/NotificationMethodUtil.ts +41 -1
  116. package/UI/Utils/PermissionGate.ts +56 -0
  117. package/Utils/EnterpriseLicense/EnterpriseLicenseSync.ts +244 -0
  118. package/build/dist/Models/DatabaseModels/EnterpriseLicense.js +24 -0
  119. package/build/dist/Models/DatabaseModels/EnterpriseLicense.js.map +1 -1
  120. package/build/dist/Models/DatabaseModels/Index.js +4 -2
  121. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  122. package/build/dist/Models/DatabaseModels/Monitor.js +39 -9
  123. package/build/dist/Models/DatabaseModels/Monitor.js.map +1 -1
  124. package/build/dist/Models/DatabaseModels/UserMicrosoftTeams.js +363 -0
  125. package/build/dist/Models/DatabaseModels/UserMicrosoftTeams.js.map +1 -0
  126. package/build/dist/Models/DatabaseModels/UserNotificationRule.js +145 -8
  127. package/build/dist/Models/DatabaseModels/UserNotificationRule.js.map +1 -1
  128. package/build/dist/Models/DatabaseModels/UserNotificationSetting.js +38 -0
  129. package/build/dist/Models/DatabaseModels/UserNotificationSetting.js.map +1 -1
  130. package/build/dist/Models/DatabaseModels/UserOnCallLogTimeline.js +96 -0
  131. package/build/dist/Models/DatabaseModels/UserOnCallLogTimeline.js.map +1 -1
  132. package/build/dist/Models/DatabaseModels/UserSlack.js +361 -0
  133. package/build/dist/Models/DatabaseModels/UserSlack.js.map +1 -0
  134. package/build/dist/Models/DatabaseModels/WorkspaceProjectAuthToken.js.map +1 -1
  135. package/build/dist/Server/API/EnterpriseLicenseAPI.js +66 -12
  136. package/build/dist/Server/API/EnterpriseLicenseAPI.js.map +1 -1
  137. package/build/dist/Server/API/UserMicrosoftTeamsAPI.js +82 -0
  138. package/build/dist/Server/API/UserMicrosoftTeamsAPI.js.map +1 -0
  139. package/build/dist/Server/API/UserSlackAPI.js +81 -0
  140. package/build/dist/Server/API/UserSlackAPI.js.map +1 -0
  141. package/build/dist/Server/EnvironmentConfig.js +25 -70
  142. package/build/dist/Server/EnvironmentConfig.js.map +1 -1
  143. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788700000000-RemoveMarketingConversionUploadState.js +26 -0
  144. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788700000000-RemoveMarketingConversionUploadState.js.map +1 -0
  145. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788800000000-DropMarketingConversionAddEnterpriseLicenseEmail.js +37 -0
  146. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788800000000-DropMarketingConversionAddEnterpriseLicenseEmail.js.map +1 -0
  147. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788900000000-RedactStoredMonitorIngestSecrets.js +106 -0
  148. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788900000000-RedactStoredMonitorIngestSecrets.js.map +1 -0
  149. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789000000000-AddUserSlackAndMicrosoftTeams.js +78 -0
  150. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789000000000-AddUserSlackAndMicrosoftTeams.js.map +1 -0
  151. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +8 -0
  152. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  153. package/build/dist/Server/Infrastructure/Queue.js +7 -0
  154. package/build/dist/Server/Infrastructure/Queue.js.map +1 -1
  155. package/build/dist/Server/Services/EnterpriseLicenseService.js +65 -0
  156. package/build/dist/Server/Services/EnterpriseLicenseService.js.map +1 -1
  157. package/build/dist/Server/Services/Index.js +6 -2
  158. package/build/dist/Server/Services/Index.js.map +1 -1
  159. package/build/dist/Server/Services/LlmLogService.js +25 -9
  160. package/build/dist/Server/Services/LlmLogService.js.map +1 -1
  161. package/build/dist/Server/Services/OnCallReadinessService.js +74 -2
  162. package/build/dist/Server/Services/OnCallReadinessService.js.map +1 -1
  163. package/build/dist/Server/Services/ProjectService.js +144 -0
  164. package/build/dist/Server/Services/ProjectService.js.map +1 -1
  165. package/build/dist/Server/Services/UserMicrosoftTeamsService.js +183 -0
  166. package/build/dist/Server/Services/UserMicrosoftTeamsService.js.map +1 -0
  167. package/build/dist/Server/Services/UserNotificationMethodAdminService.js +138 -2
  168. package/build/dist/Server/Services/UserNotificationMethodAdminService.js.map +1 -1
  169. package/build/dist/Server/Services/UserNotificationRuleAdminService.js +32 -4
  170. package/build/dist/Server/Services/UserNotificationRuleAdminService.js.map +1 -1
  171. package/build/dist/Server/Services/UserNotificationRuleService.js +499 -67
  172. package/build/dist/Server/Services/UserNotificationRuleService.js.map +1 -1
  173. package/build/dist/Server/Services/UserNotificationSettingService.js +118 -0
  174. package/build/dist/Server/Services/UserNotificationSettingService.js.map +1 -1
  175. package/build/dist/Server/Services/UserService.js +26 -1
  176. package/build/dist/Server/Services/UserService.js.map +1 -1
  177. package/build/dist/Server/Services/UserSlackService.js +195 -0
  178. package/build/dist/Server/Services/UserSlackService.js.map +1 -0
  179. package/build/dist/Server/Services/WorkspaceProjectAuthTokenService.js +64 -1
  180. package/build/dist/Server/Services/WorkspaceProjectAuthTokenService.js.map +1 -1
  181. package/build/dist/Server/Services/WorkspaceUserAuthTokenService.js +70 -0
  182. package/build/dist/Server/Services/WorkspaceUserAuthTokenService.js.map +1 -1
  183. package/build/dist/Server/Services/WorkspaceUserNotificationService.js +129 -0
  184. package/build/dist/Server/Services/WorkspaceUserNotificationService.js.map +1 -0
  185. package/build/dist/Server/Utils/Marketing/MarketingEventUtil.js +94 -0
  186. package/build/dist/Server/Utils/Marketing/MarketingEventUtil.js.map +1 -0
  187. package/build/dist/Server/Utils/Marketing/MarketingEventWebhook.js +93 -0
  188. package/build/dist/Server/Utils/Marketing/MarketingEventWebhook.js.map +1 -0
  189. package/build/dist/Server/Utils/Monitor/MonitorLogUtil.js +12 -1
  190. package/build/dist/Server/Utils/Monitor/MonitorLogUtil.js.map +1 -1
  191. package/build/dist/Server/Utils/Monitor/MonitorPayloadRedaction.js +233 -0
  192. package/build/dist/Server/Utils/Monitor/MonitorPayloadRedaction.js.map +1 -0
  193. package/build/dist/Server/Utils/Response.js +2 -2
  194. package/build/dist/Server/Utils/Response.js.map +1 -1
  195. package/build/dist/Server/Utils/StartServer.js +2 -2
  196. package/build/dist/Server/Utils/StartServer.js.map +1 -1
  197. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +141 -4
  198. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
  199. package/build/dist/Types/Analytics/RevenueEvent.js +1 -0
  200. package/build/dist/Types/Analytics/RevenueEvent.js.map +1 -1
  201. package/build/dist/Types/Marketing/MarketingEvent.js +82 -0
  202. package/build/dist/Types/Marketing/MarketingEvent.js.map +1 -0
  203. package/build/dist/UI/Components/Charts/Area/AreaChart.js +9 -1
  204. package/build/dist/UI/Components/Charts/Area/AreaChart.js.map +1 -1
  205. package/build/dist/UI/Components/Charts/Bar/BarChart.js +9 -1
  206. package/build/dist/UI/Components/Charts/Bar/BarChart.js.map +1 -1
  207. package/build/dist/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.js +22 -13
  208. package/build/dist/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.js.map +1 -1
  209. package/build/dist/UI/Components/Charts/ChartLibrary/BarChart/BarChart.js +27 -15
  210. package/build/dist/UI/Components/Charts/ChartLibrary/BarChart/BarChart.js.map +1 -1
  211. package/build/dist/UI/Components/Charts/ChartLibrary/LineChart/LineChart.js +27 -18
  212. package/build/dist/UI/Components/Charts/ChartLibrary/LineChart/LineChart.js.map +1 -1
  213. package/build/dist/UI/Components/Charts/ChartLibrary/Utils/TooltipEntries.js +71 -0
  214. package/build/dist/UI/Components/Charts/ChartLibrary/Utils/TooltipEntries.js.map +1 -0
  215. package/build/dist/UI/Components/Charts/Line/LineChart.js +9 -1
  216. package/build/dist/UI/Components/Charts/Line/LineChart.js.map +1 -1
  217. package/build/dist/UI/Components/Detail/Detail.js +17 -1
  218. package/build/dist/UI/Components/Detail/Detail.js.map +1 -1
  219. package/build/dist/UI/Utils/NotificationMethodUtil.js +33 -1
  220. package/build/dist/UI/Utils/NotificationMethodUtil.js.map +1 -1
  221. package/build/dist/UI/Utils/PermissionGate.js +32 -0
  222. package/build/dist/UI/Utils/PermissionGate.js.map +1 -1
  223. package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseSync.js +174 -0
  224. package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseSync.js.map +1 -0
  225. package/package.json +1 -1
  226. package/Models/DatabaseModels/MarketingConversion.ts +0 -410
  227. package/Server/Services/MarketingConversionService.ts +0 -10
  228. package/Server/Utils/Marketing/ConversionUploadProvider.ts +0 -215
  229. package/Server/Utils/Marketing/ConversionUploadProviders.ts +0 -22
  230. package/Server/Utils/Marketing/Providers/GoogleAds.ts +0 -333
  231. package/Server/Utils/Marketing/Providers/LinkedIn.ts +0 -185
  232. package/Server/Utils/Marketing/Providers/Meta.ts +0 -182
  233. package/Server/Utils/Marketing/Providers/MicrosoftAds.ts +0 -223
  234. package/Server/Utils/Marketing/Providers/Reddit.ts +0 -208
  235. package/Tests/Server/Utils/Marketing/AdUploadableConversionTypes.test.ts +0 -315
  236. package/Tests/Server/Utils/Marketing/ConversionUploadProvider.test.ts +0 -300
  237. package/Tests/Server/Utils/Marketing/GoogleAds.test.ts +0 -592
  238. package/Tests/Server/Utils/Marketing/LinkedIn.test.ts +0 -282
  239. package/Tests/Server/Utils/Marketing/Meta.test.ts +0 -304
  240. package/Tests/Server/Utils/Marketing/MicrosoftAds.test.ts +0 -263
  241. package/Tests/Server/Utils/Marketing/Reddit.test.ts +0 -259
  242. package/Types/Marketing/MarketingConversion.ts +0 -53
  243. package/build/dist/Models/DatabaseModels/MarketingConversion.js +0 -445
  244. package/build/dist/Models/DatabaseModels/MarketingConversion.js.map +0 -1
  245. package/build/dist/Server/Services/MarketingConversionService.js +0 -9
  246. package/build/dist/Server/Services/MarketingConversionService.js.map +0 -1
  247. package/build/dist/Server/Utils/Marketing/ConversionUploadProvider.js +0 -93
  248. package/build/dist/Server/Utils/Marketing/ConversionUploadProvider.js.map +0 -1
  249. package/build/dist/Server/Utils/Marketing/ConversionUploadProviders.js +0 -20
  250. package/build/dist/Server/Utils/Marketing/ConversionUploadProviders.js.map +0 -1
  251. package/build/dist/Server/Utils/Marketing/Providers/GoogleAds.js +0 -221
  252. package/build/dist/Server/Utils/Marketing/Providers/GoogleAds.js.map +0 -1
  253. package/build/dist/Server/Utils/Marketing/Providers/LinkedIn.js +0 -137
  254. package/build/dist/Server/Utils/Marketing/Providers/LinkedIn.js.map +0 -1
  255. package/build/dist/Server/Utils/Marketing/Providers/Meta.js +0 -133
  256. package/build/dist/Server/Utils/Marketing/Providers/Meta.js.map +0 -1
  257. package/build/dist/Server/Utils/Marketing/Providers/MicrosoftAds.js +0 -143
  258. package/build/dist/Server/Utils/Marketing/Providers/MicrosoftAds.js.map +0 -1
  259. package/build/dist/Server/Utils/Marketing/Providers/Reddit.js +0 -145
  260. package/build/dist/Server/Utils/Marketing/Providers/Reddit.js.map +0 -1
  261. package/build/dist/Types/Marketing/MarketingConversion.js +0 -52
  262. package/build/dist/Types/Marketing/MarketingConversion.js.map +0 -1
@@ -26,9 +26,11 @@ import TeamMemberService from "../../../Server/Services/TeamMemberService";
26
26
  import TeamService from "../../../Server/Services/TeamService";
27
27
  import UserCallService from "../../../Server/Services/UserCallService";
28
28
  import UserEmailService from "../../../Server/Services/UserEmailService";
29
+ import UserMicrosoftTeamsService from "../../../Server/Services/UserMicrosoftTeamsService";
29
30
  import UserNotificationRuleService from "../../../Server/Services/UserNotificationRuleService";
30
31
  import UserPushService from "../../../Server/Services/UserPushService";
31
32
  import UserService from "../../../Server/Services/UserService";
33
+ import UserSlackService from "../../../Server/Services/UserSlackService";
32
34
  import UserSmsService from "../../../Server/Services/UserSmsService";
33
35
  import UserTelegramService from "../../../Server/Services/UserTelegramService";
34
36
  import UserWebhookService from "../../../Server/Services/UserWebhookService";
@@ -41,7 +43,9 @@ import {
41
43
  import Response from "../../../Server/Utils/Response";
42
44
  import UserCall from "../../../Models/DatabaseModels/UserCall";
43
45
  import UserEmail from "../../../Models/DatabaseModels/UserEmail";
46
+ import UserMicrosoftTeams from "../../../Models/DatabaseModels/UserMicrosoftTeams";
44
47
  import UserPush from "../../../Models/DatabaseModels/UserPush";
48
+ import UserSlack from "../../../Models/DatabaseModels/UserSlack";
45
49
  import UserSMS from "../../../Models/DatabaseModels/UserSMS";
46
50
  import UserTelegram from "../../../Models/DatabaseModels/UserTelegram";
47
51
  import UserWebhook from "../../../Models/DatabaseModels/UserWebhook";
@@ -316,6 +320,8 @@ let userSmsFindBy: jest.SpyInstance;
316
320
  let userCallFindBy: jest.SpyInstance;
317
321
  let userWhatsAppFindBy: jest.SpyInstance;
318
322
  let userTelegramFindBy: jest.SpyInstance;
323
+ let userSlackFindBy: jest.SpyInstance;
324
+ let userMicrosoftTeamsFindBy: jest.SpyInstance;
319
325
  let userWebhookFindBy: jest.SpyInstance;
320
326
  let notificationRuleFindBy: jest.SpyInstance;
321
327
  let incidentSeverityFindBy: jest.SpyInstance;
@@ -340,6 +346,8 @@ function everyFindBySpy(): Array<jest.SpyInstance> {
340
346
  userCallFindBy,
341
347
  userWhatsAppFindBy,
342
348
  userTelegramFindBy,
349
+ userSlackFindBy,
350
+ userMicrosoftTeamsFindBy,
343
351
  userWebhookFindBy,
344
352
  notificationRuleFindBy,
345
353
  incidentSeverityFindBy,
@@ -445,6 +453,12 @@ beforeEach(() => {
445
453
  userTelegramFindBy = jest
446
454
  .spyOn(UserTelegramService, "findBy")
447
455
  .mockResolvedValue([] as never);
456
+ userSlackFindBy = jest
457
+ .spyOn(UserSlackService, "findBy")
458
+ .mockResolvedValue([] as never);
459
+ userMicrosoftTeamsFindBy = jest
460
+ .spyOn(UserMicrosoftTeamsService, "findBy")
461
+ .mockResolvedValue([] as never);
448
462
  userWebhookFindBy = jest
449
463
  .spyOn(UserWebhookService, "findBy")
450
464
  .mockResolvedValue([] as never);
@@ -759,13 +773,14 @@ describe("GET /on-call-readiness/policy/:policyId", () => {
759
773
 
760
774
  /*
761
775
  * Four fields and no fifth. `methods` is the part of this payload that
762
- * describes rows an administrator is NOT permitted to read - the seven
776
+ * describes rows an administrator is NOT permitted to read - the nine
763
777
  * method models are owner-scoped precisely because their columns are the raw
764
- * phone number, the webhook bearer url, the push token, the telegram chat id
765
- * and the verification code - so the wire shape is asserted exhaustively
766
- * rather than field by field. A field added to ReadinessMethod is a field
767
- * that ships to every administrator of the project, and it should have to
768
- * pass through here on the way.
778
+ * phone number, the webhook bearer url, the push token, the telegram chat
779
+ * id, the slack member id, the teams object id and the verification code -
780
+ * so the wire shape is asserted exhaustively rather than field by field. A
781
+ * field added to ReadinessMethod is a field that ships to every
782
+ * administrator of the project, and it should have to pass through here on
783
+ * the way.
769
784
  */
770
785
  expect(user["methods"]).toEqual([
771
786
  {
@@ -1437,6 +1452,15 @@ describe("GET /on-call-readiness/user/:userId - identifier masking", () => {
1437
1452
  const RAW_WHATSAPP: string = "+14155559999";
1438
1453
  const RAW_HANDLE: string = "@jamesbond";
1439
1454
  const RAW_DEVICE: string = "Jane's iPhone 15 Pro";
1455
+ const RAW_SLACK_USERNAME: string = "jane.oncall.slack";
1456
+ const RAW_TEAMS_USERNAME: string = "Jane Doe [Overnight]";
1457
+ /*
1458
+ * The addressable targets behind the two workspace channels - the Slack
1459
+ * member id the bot DMs and the Entra object id the Teams bot resolves. On
1460
+ * the rows, like the webhook's bearer url, and never selected.
1461
+ */
1462
+ const RAW_SLACK_USER_ID: string = "U0MEMBERIDLEAK";
1463
+ const RAW_TEAMS_USER_ID: string = "aad-ENTRALEAK-7788";
1440
1464
  const RAW_WEBHOOK_NAME: string = "Payments Slack Hook";
1441
1465
  const RAW_WEBHOOK_URL: string =
1442
1466
  "https://hooks.slack.com/services/T000/B000/XXXXsecretXXXX";
@@ -1445,7 +1469,7 @@ describe("GET /on-call-readiness/user/:userId - identifier masking", () => {
1445
1469
  * One fixed id per channel, and fixed rather than generated so that a failure
1446
1470
  * prints a value a reader can match against the fixture that produced it.
1447
1471
  *
1448
- * These are the ids UserNotificationRule's seven foreign keys reference -
1472
+ * These are the ids UserNotificationRule's nine foreign keys reference -
1449
1473
  * userEmailId points at a UserEmail row, userSmsId at a UserSMS row - and they
1450
1474
  * are the reason `methodId` exists at all. An administrator building a rule
1451
1475
  * for somebody else may not READ any of these rows; what they get instead is
@@ -1475,10 +1499,16 @@ describe("GET /on-call-readiness/user/:userId - identifier masking", () => {
1475
1499
  const WEBHOOK_METHOD_ID: ObjectID = new ObjectID(
1476
1500
  "1a000000-0000-4000-8000-000000000007",
1477
1501
  );
1502
+ const SLACK_METHOD_ID: ObjectID = new ObjectID(
1503
+ "1a000000-0000-4000-8000-000000000008",
1504
+ );
1505
+ const MICROSOFT_TEAMS_METHOD_ID: ObjectID = new ObjectID(
1506
+ "1a000000-0000-4000-8000-000000000009",
1507
+ );
1478
1508
 
1479
1509
  /*
1480
1510
  * The expected id per channel, in one place, so the sweep below is a sweep
1481
- * over all seven rather than seven assertions that can each be forgotten
1511
+ * over all nine rather than nine assertions that can each be forgotten
1482
1512
  * individually. Keyed by the wire's `methodType` string.
1483
1513
  */
1484
1514
  const METHOD_ID_BY_TYPE: Dictionary<string> = {
@@ -1488,6 +1518,8 @@ describe("GET /on-call-readiness/user/:userId - identifier masking", () => {
1488
1518
  [ReadinessMethodType.Call]: CALL_METHOD_ID.toString(),
1489
1519
  [ReadinessMethodType.WhatsApp]: WHATSAPP_METHOD_ID.toString(),
1490
1520
  [ReadinessMethodType.Telegram]: TELEGRAM_METHOD_ID.toString(),
1521
+ [ReadinessMethodType.Slack]: SLACK_METHOD_ID.toString(),
1522
+ [ReadinessMethodType.MicrosoftTeams]: MICROSOFT_TEAMS_METHOD_ID.toString(),
1491
1523
  [ReadinessMethodType.Webhook]: WEBHOOK_METHOD_ID.toString(),
1492
1524
  };
1493
1525
 
@@ -1548,6 +1580,24 @@ describe("GET /on-call-readiness/user/:userId - identifier masking", () => {
1548
1580
  telegram.isVerified = true;
1549
1581
  userTelegramFindBy.mockResolvedValue([telegram] as never);
1550
1582
 
1583
+ const slack: UserSlack = new UserSlack();
1584
+ slack.id = SLACK_METHOD_ID;
1585
+ slack.userId = subjectUserId;
1586
+ slack.slackUserName = RAW_SLACK_USERNAME;
1587
+ // On the row, and never selected: the member id is the addressable target.
1588
+ slack.slackUserId = RAW_SLACK_USER_ID;
1589
+ slack.isVerified = true;
1590
+ userSlackFindBy.mockResolvedValue([slack] as never);
1591
+
1592
+ const microsoftTeams: UserMicrosoftTeams = new UserMicrosoftTeams();
1593
+ microsoftTeams.id = MICROSOFT_TEAMS_METHOD_ID;
1594
+ microsoftTeams.userId = subjectUserId;
1595
+ microsoftTeams.microsoftTeamsUserName = RAW_TEAMS_USERNAME;
1596
+ // On the row, and never selected: the Entra id is the addressable target.
1597
+ microsoftTeams.microsoftTeamsUserId = RAW_TEAMS_USER_ID;
1598
+ microsoftTeams.isVerified = true;
1599
+ userMicrosoftTeamsFindBy.mockResolvedValue([microsoftTeams] as never);
1600
+
1551
1601
  const push: UserPush = new UserPush();
1552
1602
  push.id = PUSH_METHOD_ID;
1553
1603
  push.userId = subjectUserId;
@@ -1597,6 +1647,24 @@ describe("GET /on-call-readiness/user/:userId - identifier masking", () => {
1597
1647
  maskedIdentifier: `j${IDENTIFIER_MASK}@personal.example.com`,
1598
1648
  isVerified: true,
1599
1649
  },
1650
+ /*
1651
+ * The two workspace channels sit in the zero-cost tier beside Push and
1652
+ * Email, ahead of every paid channel. Their masked identifier is the
1653
+ * human-facing NAME column under the handle rule; the addressable ids
1654
+ * are never selected at all - see the select tests below.
1655
+ */
1656
+ {
1657
+ methodId: SLACK_METHOD_ID.toString(),
1658
+ methodType: ReadinessMethodType.Slack,
1659
+ maskedIdentifier: `ja${IDENTIFIER_MASK}`,
1660
+ isVerified: true,
1661
+ },
1662
+ {
1663
+ methodId: MICROSOFT_TEAMS_METHOD_ID.toString(),
1664
+ methodType: ReadinessMethodType.MicrosoftTeams,
1665
+ maskedIdentifier: `Ja${IDENTIFIER_MASK}`,
1666
+ isVerified: true,
1667
+ },
1600
1668
  {
1601
1669
  methodId: SMS_METHOD_ID.toString(),
1602
1670
  methodType: ReadinessMethodType.SMS,
@@ -1634,10 +1702,10 @@ describe("GET /on-call-readiness/user/:userId - identifier masking", () => {
1634
1702
  ]);
1635
1703
  });
1636
1704
 
1637
- test("all seven channels reach the wire carrying the id of their OWN row", async () => {
1705
+ test("all nine channels reach the wire carrying the id of their OWN row", async () => {
1638
1706
  /*
1639
1707
  * The dropdown's entire premise, swept across every channel rather than
1640
- * spot-checked on one, because the seven are seven separate code paths that
1708
+ * spot-checked on one, because the nine are nine separate code paths that
1641
1709
  * each build their own row and each have their own opportunity to hand over
1642
1710
  * somebody else's id - or none at all.
1643
1711
  *
@@ -1650,7 +1718,7 @@ describe("GET /on-call-readiness/user/:userId - identifier masking", () => {
1650
1718
  const payload: Record<string, unknown> = await readUser();
1651
1719
  const methods: Array<Record<string, unknown>> = methodsOf(payload);
1652
1720
 
1653
- expect(methods).toHaveLength(7);
1721
+ expect(methods).toHaveLength(9);
1654
1722
 
1655
1723
  for (const method of methods) {
1656
1724
  const methodType: string = method["methodType"] as string;
@@ -1682,7 +1750,7 @@ describe("GET /on-call-readiness/user/:userId - identifier masking", () => {
1682
1750
  const payload: Record<string, unknown> = await readUser();
1683
1751
  const methods: Array<Record<string, unknown>> = methodsOf(payload);
1684
1752
 
1685
- expect(methods).toHaveLength(7);
1753
+ expect(methods).toHaveLength(9);
1686
1754
 
1687
1755
  for (const method of methods) {
1688
1756
  expect(method["methodId"]).not.toBe(subjectUserId.toString());
@@ -1691,23 +1759,23 @@ describe("GET /on-call-readiness/user/:userId - identifier masking", () => {
1691
1759
  expect(method["methodId"]).not.toBe(projectId.toString());
1692
1760
  }
1693
1761
 
1694
- // And all seven are distinct - one id copied across channels is the same bug.
1762
+ // And all nine are distinct - one id copied across channels is the same bug.
1695
1763
  const ids: Array<unknown> = methods.map(
1696
1764
  (method: Record<string, unknown>): unknown => {
1697
1765
  return method["methodId"];
1698
1766
  },
1699
1767
  );
1700
- expect(new Set(ids).size).toBe(7);
1768
+ expect(new Set(ids).size).toBe(9);
1701
1769
  });
1702
1770
 
1703
1771
  test("a method carries its id, its type, its mask and its verification - and nothing else", async () => {
1704
1772
  /*
1705
1773
  * The containment assertion. Every field on this object ships to every
1706
1774
  * administrator of the project, about a row that administrator is not
1707
- * allowed to read, so the key set is pinned exhaustively on all seven rather
1775
+ * allowed to read, so the key set is pinned exhaustively on all nine rather
1708
1776
  * than left to whatever the serialiser happens to copy. A `phone`, a
1709
- * `webhookUrl` or a `telegramChatId` appearing here is not a formatting
1710
- * change; it is the exposure this design was built to avoid.
1777
+ * `webhookUrl`, a `telegramChatId` or a `slackUserId` appearing here is not
1778
+ * a formatting change; it is the exposure this design was built to avoid.
1711
1779
  */
1712
1780
  const payload: Record<string, unknown> = await readUser();
1713
1781
 
@@ -1729,10 +1797,10 @@ describe("GET /on-call-readiness/user/:userId - identifier masking", () => {
1729
1797
  * Guards the guard, and it is not a formality here: the service drops any
1730
1798
  * method row it cannot find an id on, so a fixture regression that emptied
1731
1799
  * `methods` would satisfy every "not present" assertion below for the one
1732
- * reason that proves nothing at all. The seven masks have to be in the body
1800
+ * reason that proves nothing at all. The nine masks have to be in the body
1733
1801
  * before their absence of raw values means anything.
1734
1802
  */
1735
- expect(methodsOf(payload)).toHaveLength(7);
1803
+ expect(methodsOf(payload)).toHaveLength(9);
1736
1804
  expect(body).toContain(IDENTIFIER_MASK);
1737
1805
 
1738
1806
  /*
@@ -1747,6 +1815,8 @@ describe("GET /on-call-readiness/user/:userId - identifier masking", () => {
1747
1815
  RAW_WHATSAPP,
1748
1816
  RAW_HANDLE,
1749
1817
  RAW_DEVICE,
1818
+ RAW_SLACK_USERNAME,
1819
+ RAW_TEAMS_USERNAME,
1750
1820
  RAW_WEBHOOK_NAME,
1751
1821
  RAW_WEBHOOK_URL,
1752
1822
  // The notification address's local part on its own, and the hook's host.
@@ -1754,6 +1824,14 @@ describe("GET /on-call-readiness/user/:userId - identifier masking", () => {
1754
1824
  "hooks.slack.com",
1755
1825
  // The telegram chat id - the addressable target, never the label.
1756
1826
  "998877",
1827
+ // The workspace targets - the Slack member id and the Entra object id.
1828
+ RAW_SLACK_USER_ID,
1829
+ RAW_TEAMS_USER_ID,
1830
+ "MEMBERIDLEAK",
1831
+ "ENTRALEAK",
1832
+ // The workspace names' revealing middles, past what a handle mask keeps.
1833
+ "oncall.slack",
1834
+ "[Overnight]",
1757
1835
  /*
1758
1836
  * The unmasked tails of the four phone-shaped identifiers. maskIdentifier
1759
1837
  * keeps the last four digits on purpose, so the numbers themselves are
@@ -1795,7 +1873,7 @@ describe("GET /on-call-readiness/user/:userId - identifier masking", () => {
1795
1873
  const payload: Record<string, unknown> = await readUser();
1796
1874
  const methods: Array<Record<string, unknown>> = methodsOf(payload);
1797
1875
 
1798
- expect(methods).toHaveLength(7);
1876
+ expect(methods).toHaveLength(9);
1799
1877
 
1800
1878
  for (const method of methods) {
1801
1879
  expect(method["methodId"]).not.toBe(ruleId.toString());
@@ -1870,6 +1948,24 @@ describe("GET /on-call-readiness/user/:userId - identifier masking", () => {
1870
1948
  expect(call.select?.["telegramUserHandle"]).toBe(true);
1871
1949
  expect(call.select?.["telegramChatId"]).toBeUndefined();
1872
1950
  });
1951
+
1952
+ test("the slack read takes the username, never the addressable member id", async () => {
1953
+ await readUser();
1954
+
1955
+ const call: CapturedFindBy = firstCall(userSlackFindBy);
1956
+
1957
+ expect(call.select?.["slackUserName"]).toBe(true);
1958
+ expect(call.select?.["slackUserId"]).toBeUndefined();
1959
+ });
1960
+
1961
+ test("the microsoft teams read takes the display name, never the addressable entra id", async () => {
1962
+ await readUser();
1963
+
1964
+ const call: CapturedFindBy = firstCall(userMicrosoftTeamsFindBy);
1965
+
1966
+ expect(call.select?.["microsoftTeamsUserName"]).toBe(true);
1967
+ expect(call.select?.["microsoftTeamsUserId"]).toBeUndefined();
1968
+ });
1873
1969
  });
1874
1970
 
1875
1971
  /*
@@ -1898,6 +1994,8 @@ interface StubRule {
1898
1994
  userEmailId?: ObjectID | undefined;
1899
1995
  userPushId?: ObjectID | undefined;
1900
1996
  userTelegramId?: ObjectID | undefined;
1997
+ userSlackId?: ObjectID | undefined;
1998
+ userMicrosoftTeamsId?: ObjectID | undefined;
1901
1999
  userWhatsAppId?: ObjectID | undefined;
1902
2000
  userWebhookId?: ObjectID | undefined;
1903
2001
  }
@@ -2037,17 +2135,22 @@ describe("GET /team/compliance-status/:teamId - the rebuilt service", () => {
2037
2135
  ]);
2038
2136
  });
2039
2137
 
2040
- test("DEFECT closed: a rule on Telegram, WhatsApp or a webhook now counts", async () => {
2138
+ test("DEFECT closed: a rule on Telegram, WhatsApp, Slack, Teams or a webhook now counts", async () => {
2041
2139
  /*
2042
2140
  * The old check read userCallId/userSmsId/userEmailId/userPushId off the
2043
2141
  * rule row and treated a row carrying none of them as no rule at all, so a
2044
2142
  * responder reachable only on Telegram, WhatsApp or a webhook was reported
2045
2143
  * non-compliant while the runtime was quite happily paging them. A false RED
2046
- * teaches admins to ignore the table, which is worse than no table.
2144
+ * teaches admins to ignore the table, which is worse than no table. Slack
2145
+ * and Microsoft Teams arrived after the fix and are staged alongside so the
2146
+ * defect cannot return for the channels that never lived through it.
2047
2147
  *
2048
- * All three channels are staged onto one user at once: whichever column the
2148
+ * All five channels are staged onto one user at once: whichever column the
2049
2149
  * rule carries, the row is a rule.
2050
2150
  */
2151
+ const minor: StubSeverity = { id: ObjectID.generate(), name: "Minor" };
2152
+ const warn: StubSeverity = { id: ObjectID.generate(), name: "Warn" };
2153
+
2051
2154
  stage({
2052
2155
  settings: [
2053
2156
  { ruleType: ComplianceRuleType.HasIncidentOnCallRules, enabled: true },
@@ -2069,6 +2172,13 @@ describe("GET /team/compliance-status/:teamId - the rebuilt service", () => {
2069
2172
  incidentSeverityId: major.id,
2070
2173
  userWhatsAppId: ObjectID.generate(),
2071
2174
  },
2175
+ {
2176
+ _id: "rule-slack",
2177
+ userId: ada.id,
2178
+ ruleType: NotificationRuleType.ON_CALL_EXECUTED_INCIDENT,
2179
+ incidentSeverityId: minor.id,
2180
+ userSlackId: ObjectID.generate(),
2181
+ },
2072
2182
  {
2073
2183
  _id: "rule-webhook",
2074
2184
  userId: ada.id,
@@ -2076,9 +2186,16 @@ describe("GET /team/compliance-status/:teamId - the rebuilt service", () => {
2076
2186
  alertSeverityId: page.id,
2077
2187
  userWebhookId: ObjectID.generate(),
2078
2188
  },
2189
+ {
2190
+ _id: "rule-microsoft-teams",
2191
+ userId: ada.id,
2192
+ ruleType: NotificationRuleType.ON_CALL_EXECUTED_ALERT,
2193
+ alertSeverityId: warn.id,
2194
+ userMicrosoftTeamsId: ObjectID.generate(),
2195
+ },
2079
2196
  ],
2080
- incidentSeverities: [critical, major],
2081
- alertSeverities: [page],
2197
+ incidentSeverities: [critical, major, minor],
2198
+ alertSeverities: [page, warn],
2082
2199
  });
2083
2200
 
2084
2201
  const statuses: Array<Record<string, unknown>> = statusesOf(
@@ -2093,7 +2210,7 @@ describe("GET /team/compliance-status/:teamId - the rebuilt service", () => {
2093
2210
  /*
2094
2211
  * The structural half of the fix. The three formerly-invisible channels were
2095
2212
  * invisible because they were never SELECTed; asserting that NONE of the
2096
- * seven is selected means no future edit can reintroduce a partial column
2213
+ * nine is selected means no future edit can reintroduce a partial column
2097
2214
  * list and quietly start under-counting again.
2098
2215
  */
2099
2216
  stage({
@@ -2116,6 +2233,8 @@ describe("GET /team/compliance-status/:teamId - the rebuilt service", () => {
2116
2233
  "userEmailId",
2117
2234
  "userPushId",
2118
2235
  "userTelegramId",
2236
+ "userSlackId",
2237
+ "userMicrosoftTeamsId",
2119
2238
  "userWhatsAppId",
2120
2239
  "userWebhookId",
2121
2240
  ]) {
@@ -30,7 +30,7 @@ import { beforeEach, describe, expect, test } from "@jest/globals";
30
30
  * methods".
31
31
  *
32
32
  * This router is the ONLY gate under these routes, and that is not a stylistic
33
- * observation. The seven notification method models are scoped to the person
33
+ * observation. The nine notification method models are scoped to the person
34
34
  * who owns the device — the essay at the top of UserEmail.ts is the record of
35
35
  * what happened the one time that scope was widened — so the service behind
36
36
  * these handlers does every read and write with `isRoot: true`, which means the
@@ -362,7 +362,7 @@ describe("the routes it registers", () => {
362
362
  * Extending BaseAPI to inherit `this.router` would register a whole model's
363
363
  * CRUD routes as a side effect — dead code that is shadowed by the real
364
364
  * registration until the day the mount order changes. There is no model to
365
- * inherit here in any case: the seven method models stay owner-scoped and
365
+ * inherit here in any case: the nine method models stay owner-scoped and
366
366
  * this router deliberately never exposes them.
367
367
  */
368
368
  expect(UserNotificationMethodAdminAPI).toBe(mockRouter);
@@ -459,7 +459,17 @@ describe("LlmLogService autonomous-token lane isolation", () => {
459
459
  expect(sql).toContain('FROM "AIRun" AS "run"');
460
460
  expect(sql).toContain('"run"."triggeredByIncidentId" IS NOT NULL');
461
461
  expect(params[3]).toEqual(legacyIncidentFeatures);
462
- expect(params[4]).toEqual(legacyAlertFeatures);
462
+
463
+ /*
464
+ * The incident lane reads only its own legacy list, so it must bind
465
+ * exactly four parameters. Binding the unread alert list as $5 is what
466
+ * PostgreSQL rejected with "bind message supplies 5 parameters, but
467
+ * prepared statement "" requires 4", which silently disabled every capped
468
+ * incident investigation. LlmLogServiceTokenAggregateParams.test.ts holds
469
+ * the exhaustive placeholder/binding contract.
470
+ */
471
+ expect(params).toHaveLength(4);
472
+ expect(sql).not.toContain("$5");
463
473
  });
464
474
 
465
475
  test("subjectless totals exclude identifiable legacy incident and alert rows", async () => {
@@ -11,8 +11,10 @@ import UserNotificationRuleService, {
11
11
  } from "../../../Server/Services/UserNotificationRuleService";
12
12
  import UserOnCallLogService from "../../../Server/Services/UserOnCallLogService";
13
13
  import UserOnCallLogTimelineService from "../../../Server/Services/UserOnCallLogTimelineService";
14
+ import UserMicrosoftTeamsService from "../../../Server/Services/UserMicrosoftTeamsService";
14
15
  import UserPushService from "../../../Server/Services/UserPushService";
15
16
  import UserService from "../../../Server/Services/UserService";
17
+ import UserSlackService from "../../../Server/Services/UserSlackService";
16
18
  import UserSmsService from "../../../Server/Services/UserSmsService";
17
19
  import UserTelegramService from "../../../Server/Services/UserTelegramService";
18
20
  import UserWebhookService from "../../../Server/Services/UserWebhookService";
@@ -74,7 +76,7 @@ import { afterEach, beforeEach, describe, expect, test } from "@jest/globals";
74
76
  * a user id is global.
75
77
  * R3 a rule's method FK must point at a method row owned by the rule's own
76
78
  * user - on create, on update, through the FK column and through the
77
- * relation slot, on every one of the seven channels, and on update with
79
+ * relation slot, on every one of the nine channels, and on update with
78
80
  * the owner re-read from the DATABASE because the body is written by the
79
81
  * party under suspicion.
80
82
  * R6 the audit line and the owner's warning email are keyed on the actor the
@@ -135,14 +137,14 @@ const ADMIN_EMAIL: string = "ada.admin@example.test";
135
137
  const VICTIM_EMAIL: string = "vic.responder@example.test";
136
138
 
137
139
  /*
138
- * The seven channels, each named by the FK column that carries it, the relation
140
+ * The nine channels, each named by the FK column that carries it, the relation
139
141
  * slot that is the same write spelled differently, the service the guard has to
140
142
  * consult for that channel's owner, and the word that must appear in the
141
143
  * refusal so an operator can tell WHICH method was wrong.
142
144
  *
143
145
  * Everything about method ownership is driven off this table on purpose. The
144
- * one realistic way for the guard to be wrong is to cover six channels and
145
- * forget the seventh, and an attacker choosing between seven doors will always
146
+ * one realistic way for the guard to be wrong is to cover eight channels and
147
+ * forget the ninth, and an attacker choosing between nine doors will always
146
148
  * choose the unlocked one - so a per-channel loop is the only shape of test
147
149
  * that means anything here.
148
150
  */
@@ -188,6 +190,18 @@ const CHANNELS: Array<ChannelFixture> = [
188
190
  label: "Telegram",
189
191
  service: UserTelegramService as unknown as MethodServiceLike,
190
192
  },
193
+ {
194
+ idColumn: "userSlackId",
195
+ relationColumn: "userSlack",
196
+ label: "Slack",
197
+ service: UserSlackService as unknown as MethodServiceLike,
198
+ },
199
+ {
200
+ idColumn: "userMicrosoftTeamsId",
201
+ relationColumn: "userMicrosoftTeams",
202
+ label: "Microsoft Teams",
203
+ service: UserMicrosoftTeamsService as unknown as MethodServiceLike,
204
+ },
191
205
  {
192
206
  idColumn: "userPushId",
193
207
  relationColumn: "userPush",
@@ -860,7 +874,7 @@ describe("Administrative notification rule edit guards", () => {
860
874
  describe("R3 on create: a rule's method must belong to the rule's user", () => {
861
875
  test("the guard covers exactly the notification method columns the model declares", () => {
862
876
  /*
863
- * Derived from the model rather than restated, so that an eighth channel
877
+ * Derived from the model rather than restated, so that a tenth channel
864
878
  * added to UserNotificationRule and forgotten in the guard fails HERE,
865
879
  * loudly, instead of quietly becoming the one unlocked door.
866
880
  */
@@ -873,7 +887,7 @@ describe("Administrative notification rule edit guards", () => {
873
887
  });
874
888
 
875
889
  // If the derivation itself ever breaks, fail rather than pass vacuously.
876
- expect(columnsOnModel).toHaveLength(7);
890
+ expect(columnsOnModel).toHaveLength(9);
877
891
 
878
892
  expect(
879
893
  [
@@ -1783,7 +1797,7 @@ describe("Administrative notification rule edit guards", () => {
1783
1797
  }),
1784
1798
  ),
1785
1799
  ).rejects.toThrow(
1786
- "Call, SMS, WhatsApp, Telegram, Webhook, Email, or Push notification is required",
1800
+ "Call, SMS, WhatsApp, Telegram, Slack, Microsoft Teams, Webhook, Email, or Push notification is required",
1787
1801
  );
1788
1802
  });
1789
1803
 
@@ -1995,7 +2009,7 @@ describe("Administrative notification rule edit guards", () => {
1995
2009
  updatePayload({ patch: { userEmailId: null } }),
1996
2010
  ),
1997
2011
  ).rejects.toThrow(
1998
- "Call, SMS, WhatsApp, Telegram, Webhook, Email, or Push notification is required",
2012
+ "Call, SMS, WhatsApp, Telegram, Slack, Microsoft Teams, Webhook, Email, or Push notification is required",
1999
2013
  );
2000
2014
  });
2001
2015
 
@@ -46,7 +46,7 @@ import { describe, expect, test, beforeEach, afterEach } from "@jest/globals";
46
46
  * with a UserNotificationRule it assembled in memory and never saved: there is
47
47
  * no rule row to claim and no id to look up, so the first half is meaningless
48
48
  * to it while the second half is exactly what it needs. That makes the SEAM
49
- * between the two halves — not the seven channel blocks, which
49
+ * between the two halves — not the nine channel blocks, which
50
50
  * NotificationChannelEventCoverage.test.ts owns — the thing worth pinning, and
51
51
  * this file pins five properties of it:
52
52
  *
@@ -731,7 +731,7 @@ describe("UserNotificationRuleService - the deliverNotificationForRule extractio
731
731
  * the loaded relation's userId against the rule's, and it can only report
732
732
  * a mismatch it can SEE - an unselected column arrives as `undefined` and
733
733
  * is deliberately read as "no evidence", so dropping any one of these
734
- * seven silently disables the backstop for that channel.
734
+ * nine silently disables the backstop for that channel.
735
735
  *
736
736
  * Kept as an exact-shape assertion on purpose. This test caught the
737
737
  * column being added, which is the whole argument for not relaxing it
@@ -762,6 +762,18 @@ describe("UserNotificationRuleService - the deliverNotificationForRule extractio
762
762
  isVerified: true,
763
763
  userId: true,
764
764
  },
765
+ userSlack: {
766
+ slackUserId: true,
767
+ slackUserName: true,
768
+ isVerified: true,
769
+ userId: true,
770
+ },
771
+ userMicrosoftTeams: {
772
+ microsoftTeamsUserId: true,
773
+ microsoftTeamsUserName: true,
774
+ isVerified: true,
775
+ userId: true,
776
+ },
765
777
  userWebhook: {
766
778
  webhookUrl: true,
767
779
  name: true,
@@ -788,6 +800,8 @@ describe("UserNotificationRuleService - the deliverNotificationForRule extractio
788
800
  ["userSms"],
789
801
  ["userWhatsApp"],
790
802
  ["userTelegram"],
803
+ ["userSlack"],
804
+ ["userMicrosoftTeams"],
791
805
  ["userEmail"],
792
806
  ["userPush"],
793
807
  ])(
@@ -806,6 +820,8 @@ describe("UserNotificationRuleService - the deliverNotificationForRule extractio
806
820
  ["userSms", "phone"],
807
821
  ["userWhatsApp", "phone"],
808
822
  ["userTelegram", "telegramChatId"],
823
+ ["userSlack", "slackUserId"],
824
+ ["userMicrosoftTeams", "microsoftTeamsUserId"],
809
825
  ["userEmail", "email"],
810
826
  ["userPush", "deviceToken"],
811
827
  ])(
@@ -161,13 +161,15 @@ const TELEGRAM_METHOD_ID: ObjectID = new ObjectID(
161
161
  "ccccccc2-cccc-4ccc-8ccc-cccccccccccc",
162
162
  );
163
163
 
164
- /* The seven FK columns a rule can name a notification method through. */
164
+ /* The nine FK columns a rule can name a notification method through. */
165
165
  type MethodColumn =
166
166
  | "userEmailId"
167
167
  | "userSmsId"
168
168
  | "userCallId"
169
169
  | "userWhatsAppId"
170
170
  | "userTelegramId"
171
+ | "userSlackId"
172
+ | "userMicrosoftTeamsId"
171
173
  | "userWebhookId"
172
174
  | "userPushId";
173
175
 
@@ -177,6 +179,8 @@ const ALL_METHOD_COLUMNS: Array<MethodColumn> = [
177
179
  "userCallId",
178
180
  "userWhatsAppId",
179
181
  "userTelegramId",
182
+ "userSlackId",
183
+ "userMicrosoftTeamsId",
180
184
  "userWebhookId",
181
185
  "userPushId",
182
186
  ];