@oneuptime/common 12.0.17 → 12.0.19

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 (220) 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/UserMicrosoftTeamsAPI.ts +127 -0
  11. package/Server/API/UserSlackAPI.ts +125 -0
  12. package/Server/EnvironmentConfig.ts +12 -107
  13. package/Server/Infrastructure/Postgres/SchemaMigrations/1788700000000-RemoveMarketingConversionUploadState.ts +33 -0
  14. package/Server/Infrastructure/Postgres/SchemaMigrations/1788800000000-DropMarketingConversionAddEnterpriseLicenseEmail.ts +51 -0
  15. package/Server/Infrastructure/Postgres/SchemaMigrations/1788900000000-RedactStoredMonitorIngestSecrets.ts +117 -0
  16. package/Server/Infrastructure/Postgres/SchemaMigrations/1789000000000-AddUserSlackAndMicrosoftTeams.ts +197 -0
  17. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +8 -0
  18. package/Server/Infrastructure/Queue.ts +7 -0
  19. package/Server/Services/BillingService.ts +8 -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 +67 -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/Workspace/MicrosoftTeams/MicrosoftTeams.ts +191 -4
  40. package/Tests/App/Dashboard/AdminNotificationRulesPage.test.tsx +22 -14
  41. package/Tests/App/Dashboard/AdminUserNotificationMethodsPage.test.tsx +22 -11
  42. package/Tests/App/Dashboard/OnCallPreventionGuards.test.tsx +37 -1
  43. package/Tests/App/Dashboard/OnCallRulesTable.test.tsx +18 -10
  44. package/Tests/App/Dashboard/PayAsYouGoNotices.test.tsx +17 -0
  45. package/Tests/Models/DatabaseModels/DomainRoleTierCoverage.test.ts +27 -1
  46. package/Tests/Models/DatabaseModels/MonitorSecretKeyColumnAccessControl.test.ts +214 -0
  47. package/Tests/Server/API/OnCallReadinessAPI.test.ts +145 -26
  48. package/Tests/Server/API/UserNotificationMethodAdminAPI.test.ts +2 -2
  49. package/Tests/Server/Services/AIServiceDailyBudget.test.ts +11 -1
  50. package/Tests/Server/Services/AdminRuleEditGuards.test.ts +22 -8
  51. package/Tests/Server/Services/BillingService.test.ts +20 -0
  52. package/Tests/Server/Services/DeliverNotificationForRuleExtraction.test.ts +18 -2
  53. package/Tests/Server/Services/EpisodeRuleSeverityRepair.test.ts +5 -1
  54. package/Tests/Server/Services/LlmLogServiceTokenAggregateParams.test.ts +640 -0
  55. package/Tests/Server/Services/NotificationChannelEventCoverage.test.ts +246 -22
  56. package/Tests/Server/Services/NotificationDeletionImpact.test.ts +136 -8
  57. package/Tests/Server/Services/OnCallNotificationFallback.test.ts +191 -8
  58. package/Tests/Server/Services/OnCallReadinessService.test.ts +343 -18
  59. package/Tests/Server/Services/SeverityRuleBackfill.test.ts +5 -1
  60. package/Tests/Server/Services/UserMicrosoftTeamsService.test.ts +343 -0
  61. package/Tests/Server/Services/UserNotificationMethodAdminService.test.ts +209 -8
  62. package/Tests/Server/Services/UserNotificationRuleAdminGuards.test.ts +22 -6
  63. package/Tests/Server/Services/UserNotificationRuleDefaultCreation.test.ts +12 -6
  64. package/Tests/Server/Services/UserNotificationRuleExecuteItem.test.ts +37 -3
  65. package/Tests/Server/Services/UserNotificationRuleWorkspaceDelivery.test.ts +669 -0
  66. package/Tests/Server/Services/UserNotificationSettingWorkspaceChannels.test.ts +410 -0
  67. package/Tests/Server/Services/UserSlackService.test.ts +407 -0
  68. package/Tests/Server/Services/WorkspaceProjectAuthTokenDisconnectCascade.test.ts +224 -0
  69. package/Tests/Server/Services/WorkspaceUserAuthTokenNotificationMethodCascade.test.ts +189 -0
  70. package/Tests/Server/Services/WorkspaceUserNotificationService.test.ts +496 -0
  71. package/Tests/Server/Types/Database/Permissions/AdminNotificationRuleAccess.test.ts +30 -17
  72. package/Tests/Server/Types/Database/Permissions/CreateOwnershipScoping.test.ts +8 -3
  73. package/Tests/Server/Types/Database/Permissions/OwnerOnlyColumns.test.ts +72 -3
  74. package/Tests/Server/Types/Database/Permissions/UserNotificationRuleScoping.test.ts +22 -18
  75. package/Tests/Server/Types/Database/Permissions/WorkspaceMethodStampedColumnCreate.test.ts +178 -0
  76. package/Tests/Server/Utils/Marketing/MarketingEventUtil.test.ts +259 -0
  77. package/Tests/Server/Utils/Marketing/MarketingEventWebhook.test.ts +257 -0
  78. package/Tests/Server/Utils/Monitor/MonitorLogUtilRedaction.test.ts +300 -0
  79. package/Tests/Server/Utils/Monitor/MonitorPayloadRedaction.test.ts +381 -0
  80. package/Tests/Server/Utils/Runbook/RunbookExecutePermission.test.ts +228 -0
  81. package/Tests/Server/Utils/SessionReplay/SessionReplayErasureTombstone.test.ts +189 -0
  82. package/Tests/Server/Utils/SessionReplay/SessionReplayUsage.test.ts +205 -0
  83. package/Tests/Server/Utils/Telemetry/AppMetrics.test.ts +275 -0
  84. package/Tests/Server/Utils/Telemetry/TelemetryContext.test.ts +279 -0
  85. package/Tests/Server/Utils/Workspace/MicrosoftTeamsDirectMessage.test.ts +454 -0
  86. package/Tests/Types/Kubernetes/KubernetesObjectParsers.test.ts +794 -0
  87. package/Tests/Types/Kubernetes/KubernetesRightSizing.test.ts +53 -0
  88. package/Tests/Types/Measurement/MeasurementAggregationType.test.ts +100 -0
  89. package/Tests/Types/Monitor/MonitorStepConfigHelpers.test.ts +114 -0
  90. package/Tests/Types/Workspace/WorkspaceType.test.ts +53 -0
  91. package/Tests/UI/Utils/NotificationMethodUtil.test.ts +137 -9
  92. package/Tests/UI/Utils/PermissionGate.test.ts +105 -0
  93. package/Tests/Utils/Dashboard/Components/DashboardComponentDefaults.test.ts +257 -0
  94. package/Types/Billing/PayAsYouGoPricing.ts +1 -1
  95. package/Types/Marketing/MarketingEvent.ts +97 -0
  96. package/UI/Utils/NotificationMethodUtil.ts +41 -1
  97. package/UI/Utils/PermissionGate.ts +56 -0
  98. package/build/dist/Models/DatabaseModels/EnterpriseLicense.js +24 -0
  99. package/build/dist/Models/DatabaseModels/EnterpriseLicense.js.map +1 -1
  100. package/build/dist/Models/DatabaseModels/Index.js +4 -2
  101. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  102. package/build/dist/Models/DatabaseModels/Monitor.js +39 -9
  103. package/build/dist/Models/DatabaseModels/Monitor.js.map +1 -1
  104. package/build/dist/Models/DatabaseModels/UserMicrosoftTeams.js +363 -0
  105. package/build/dist/Models/DatabaseModels/UserMicrosoftTeams.js.map +1 -0
  106. package/build/dist/Models/DatabaseModels/UserNotificationRule.js +145 -8
  107. package/build/dist/Models/DatabaseModels/UserNotificationRule.js.map +1 -1
  108. package/build/dist/Models/DatabaseModels/UserNotificationSetting.js +38 -0
  109. package/build/dist/Models/DatabaseModels/UserNotificationSetting.js.map +1 -1
  110. package/build/dist/Models/DatabaseModels/UserOnCallLogTimeline.js +96 -0
  111. package/build/dist/Models/DatabaseModels/UserOnCallLogTimeline.js.map +1 -1
  112. package/build/dist/Models/DatabaseModels/UserSlack.js +361 -0
  113. package/build/dist/Models/DatabaseModels/UserSlack.js.map +1 -0
  114. package/build/dist/Models/DatabaseModels/WorkspaceProjectAuthToken.js.map +1 -1
  115. package/build/dist/Server/API/UserMicrosoftTeamsAPI.js +82 -0
  116. package/build/dist/Server/API/UserMicrosoftTeamsAPI.js.map +1 -0
  117. package/build/dist/Server/API/UserSlackAPI.js +81 -0
  118. package/build/dist/Server/API/UserSlackAPI.js.map +1 -0
  119. package/build/dist/Server/EnvironmentConfig.js +10 -70
  120. package/build/dist/Server/EnvironmentConfig.js.map +1 -1
  121. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788700000000-RemoveMarketingConversionUploadState.js +26 -0
  122. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788700000000-RemoveMarketingConversionUploadState.js.map +1 -0
  123. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788800000000-DropMarketingConversionAddEnterpriseLicenseEmail.js +37 -0
  124. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788800000000-DropMarketingConversionAddEnterpriseLicenseEmail.js.map +1 -0
  125. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788900000000-RedactStoredMonitorIngestSecrets.js +106 -0
  126. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788900000000-RedactStoredMonitorIngestSecrets.js.map +1 -0
  127. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789000000000-AddUserSlackAndMicrosoftTeams.js +78 -0
  128. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789000000000-AddUserSlackAndMicrosoftTeams.js.map +1 -0
  129. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +8 -0
  130. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  131. package/build/dist/Server/Infrastructure/Queue.js +7 -0
  132. package/build/dist/Server/Infrastructure/Queue.js.map +1 -1
  133. package/build/dist/Server/Services/BillingService.js +6 -0
  134. package/build/dist/Server/Services/BillingService.js.map +1 -1
  135. package/build/dist/Server/Services/EnterpriseLicenseService.js +65 -0
  136. package/build/dist/Server/Services/EnterpriseLicenseService.js.map +1 -1
  137. package/build/dist/Server/Services/Index.js +6 -2
  138. package/build/dist/Server/Services/Index.js.map +1 -1
  139. package/build/dist/Server/Services/LlmLogService.js +25 -9
  140. package/build/dist/Server/Services/LlmLogService.js.map +1 -1
  141. package/build/dist/Server/Services/OnCallReadinessService.js +74 -2
  142. package/build/dist/Server/Services/OnCallReadinessService.js.map +1 -1
  143. package/build/dist/Server/Services/ProjectService.js +56 -0
  144. package/build/dist/Server/Services/ProjectService.js.map +1 -1
  145. package/build/dist/Server/Services/UserMicrosoftTeamsService.js +183 -0
  146. package/build/dist/Server/Services/UserMicrosoftTeamsService.js.map +1 -0
  147. package/build/dist/Server/Services/UserNotificationMethodAdminService.js +138 -2
  148. package/build/dist/Server/Services/UserNotificationMethodAdminService.js.map +1 -1
  149. package/build/dist/Server/Services/UserNotificationRuleAdminService.js +32 -4
  150. package/build/dist/Server/Services/UserNotificationRuleAdminService.js.map +1 -1
  151. package/build/dist/Server/Services/UserNotificationRuleService.js +499 -67
  152. package/build/dist/Server/Services/UserNotificationRuleService.js.map +1 -1
  153. package/build/dist/Server/Services/UserNotificationSettingService.js +118 -0
  154. package/build/dist/Server/Services/UserNotificationSettingService.js.map +1 -1
  155. package/build/dist/Server/Services/UserService.js +26 -1
  156. package/build/dist/Server/Services/UserService.js.map +1 -1
  157. package/build/dist/Server/Services/UserSlackService.js +195 -0
  158. package/build/dist/Server/Services/UserSlackService.js.map +1 -0
  159. package/build/dist/Server/Services/WorkspaceProjectAuthTokenService.js +64 -1
  160. package/build/dist/Server/Services/WorkspaceProjectAuthTokenService.js.map +1 -1
  161. package/build/dist/Server/Services/WorkspaceUserAuthTokenService.js +70 -0
  162. package/build/dist/Server/Services/WorkspaceUserAuthTokenService.js.map +1 -1
  163. package/build/dist/Server/Services/WorkspaceUserNotificationService.js +129 -0
  164. package/build/dist/Server/Services/WorkspaceUserNotificationService.js.map +1 -0
  165. package/build/dist/Server/Utils/Marketing/MarketingEventUtil.js +94 -0
  166. package/build/dist/Server/Utils/Marketing/MarketingEventUtil.js.map +1 -0
  167. package/build/dist/Server/Utils/Marketing/MarketingEventWebhook.js +93 -0
  168. package/build/dist/Server/Utils/Marketing/MarketingEventWebhook.js.map +1 -0
  169. package/build/dist/Server/Utils/Monitor/MonitorLogUtil.js +12 -1
  170. package/build/dist/Server/Utils/Monitor/MonitorLogUtil.js.map +1 -1
  171. package/build/dist/Server/Utils/Monitor/MonitorPayloadRedaction.js +233 -0
  172. package/build/dist/Server/Utils/Monitor/MonitorPayloadRedaction.js.map +1 -0
  173. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +141 -4
  174. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
  175. package/build/dist/Types/Billing/PayAsYouGoPricing.js +1 -1
  176. package/build/dist/Types/Billing/PayAsYouGoPricing.js.map +1 -1
  177. package/build/dist/Types/Marketing/MarketingEvent.js +43 -0
  178. package/build/dist/Types/Marketing/MarketingEvent.js.map +1 -0
  179. package/build/dist/UI/Utils/NotificationMethodUtil.js +33 -1
  180. package/build/dist/UI/Utils/NotificationMethodUtil.js.map +1 -1
  181. package/build/dist/UI/Utils/PermissionGate.js +32 -0
  182. package/build/dist/UI/Utils/PermissionGate.js.map +1 -1
  183. package/package.json +1 -1
  184. package/Models/DatabaseModels/MarketingConversion.ts +0 -410
  185. package/Server/Services/MarketingConversionService.ts +0 -10
  186. package/Server/Utils/Marketing/ConversionUploadProvider.ts +0 -215
  187. package/Server/Utils/Marketing/ConversionUploadProviders.ts +0 -22
  188. package/Server/Utils/Marketing/Providers/GoogleAds.ts +0 -333
  189. package/Server/Utils/Marketing/Providers/LinkedIn.ts +0 -185
  190. package/Server/Utils/Marketing/Providers/Meta.ts +0 -182
  191. package/Server/Utils/Marketing/Providers/MicrosoftAds.ts +0 -223
  192. package/Server/Utils/Marketing/Providers/Reddit.ts +0 -208
  193. package/Tests/Server/Utils/Marketing/AdUploadableConversionTypes.test.ts +0 -315
  194. package/Tests/Server/Utils/Marketing/ConversionUploadProvider.test.ts +0 -300
  195. package/Tests/Server/Utils/Marketing/GoogleAds.test.ts +0 -592
  196. package/Tests/Server/Utils/Marketing/LinkedIn.test.ts +0 -282
  197. package/Tests/Server/Utils/Marketing/Meta.test.ts +0 -304
  198. package/Tests/Server/Utils/Marketing/MicrosoftAds.test.ts +0 -263
  199. package/Tests/Server/Utils/Marketing/Reddit.test.ts +0 -259
  200. package/Types/Marketing/MarketingConversion.ts +0 -53
  201. package/build/dist/Models/DatabaseModels/MarketingConversion.js +0 -445
  202. package/build/dist/Models/DatabaseModels/MarketingConversion.js.map +0 -1
  203. package/build/dist/Server/Services/MarketingConversionService.js +0 -9
  204. package/build/dist/Server/Services/MarketingConversionService.js.map +0 -1
  205. package/build/dist/Server/Utils/Marketing/ConversionUploadProvider.js +0 -93
  206. package/build/dist/Server/Utils/Marketing/ConversionUploadProvider.js.map +0 -1
  207. package/build/dist/Server/Utils/Marketing/ConversionUploadProviders.js +0 -20
  208. package/build/dist/Server/Utils/Marketing/ConversionUploadProviders.js.map +0 -1
  209. package/build/dist/Server/Utils/Marketing/Providers/GoogleAds.js +0 -221
  210. package/build/dist/Server/Utils/Marketing/Providers/GoogleAds.js.map +0 -1
  211. package/build/dist/Server/Utils/Marketing/Providers/LinkedIn.js +0 -137
  212. package/build/dist/Server/Utils/Marketing/Providers/LinkedIn.js.map +0 -1
  213. package/build/dist/Server/Utils/Marketing/Providers/Meta.js +0 -133
  214. package/build/dist/Server/Utils/Marketing/Providers/Meta.js.map +0 -1
  215. package/build/dist/Server/Utils/Marketing/Providers/MicrosoftAds.js +0 -143
  216. package/build/dist/Server/Utils/Marketing/Providers/MicrosoftAds.js.map +0 -1
  217. package/build/dist/Server/Utils/Marketing/Providers/Reddit.js +0 -145
  218. package/build/dist/Server/Utils/Marketing/Providers/Reddit.js.map +0 -1
  219. package/build/dist/Types/Marketing/MarketingConversion.js +0 -52
  220. package/build/dist/Types/Marketing/MarketingConversion.js.map +0 -1
@@ -24,9 +24,11 @@ import TeamMemberService from "../../../Server/Services/TeamMemberService";
24
24
  import TeamService from "../../../Server/Services/TeamService";
25
25
  import UserCallService from "../../../Server/Services/UserCallService";
26
26
  import UserEmailService from "../../../Server/Services/UserEmailService";
27
+ import UserMicrosoftTeamsService from "../../../Server/Services/UserMicrosoftTeamsService";
27
28
  import UserNotificationRuleService from "../../../Server/Services/UserNotificationRuleService";
28
29
  import UserPushService from "../../../Server/Services/UserPushService";
29
30
  import UserService from "../../../Server/Services/UserService";
31
+ import UserSlackService from "../../../Server/Services/UserSlackService";
30
32
  import UserSmsService from "../../../Server/Services/UserSmsService";
31
33
  import UserTelegramService from "../../../Server/Services/UserTelegramService";
32
34
  import UserWebhookService from "../../../Server/Services/UserWebhookService";
@@ -46,8 +48,10 @@ import TeamMember from "../../../Models/DatabaseModels/TeamMember";
46
48
  import User from "../../../Models/DatabaseModels/User";
47
49
  import UserCall from "../../../Models/DatabaseModels/UserCall";
48
50
  import UserEmail from "../../../Models/DatabaseModels/UserEmail";
51
+ import UserMicrosoftTeams from "../../../Models/DatabaseModels/UserMicrosoftTeams";
49
52
  import UserNotificationRule from "../../../Models/DatabaseModels/UserNotificationRule";
50
53
  import UserPush from "../../../Models/DatabaseModels/UserPush";
54
+ import UserSlack from "../../../Models/DatabaseModels/UserSlack";
51
55
  import UserSMS from "../../../Models/DatabaseModels/UserSMS";
52
56
  import UserTelegram from "../../../Models/DatabaseModels/UserTelegram";
53
57
  import UserWebhook from "../../../Models/DatabaseModels/UserWebhook";
@@ -185,6 +189,12 @@ const TELEGRAM_METHOD_ID: ObjectID = new ObjectID(
185
189
  const WEBHOOK_METHOD_ID: ObjectID = new ObjectID(
186
190
  "c0000000-0000-4000-8000-000000000007",
187
191
  );
192
+ const SLACK_METHOD_ID: ObjectID = new ObjectID(
193
+ "c0000000-0000-4000-8000-000000000008",
194
+ );
195
+ const MICROSOFT_TEAMS_METHOD_ID: ObjectID = new ObjectID(
196
+ "c0000000-0000-4000-8000-000000000009",
197
+ );
188
198
 
189
199
  /*
190
200
  * Raw identifiers that must never reach a caller. Each is deliberately
@@ -196,6 +206,18 @@ const RAW_SMS_PHONE: string = "+14155554821";
196
206
  const RAW_CALL_PHONE: string = "+442071838750";
197
207
  const RAW_WHATSAPP_PHONE: string = "+61293744000";
198
208
  const RAW_TELEGRAM_HANDLE: string = "@ada_night_pager";
209
+ const RAW_SLACK_USERNAME: string = "ada.pager.slack";
210
+ const RAW_MICROSOFT_TEAMS_USERNAME: string = "Ada Lovelace [Overnight]";
211
+ /*
212
+ * The addressable targets behind the two workspace channels - the Slack member
213
+ * id the bot DMs and the Entra object id the Teams bot resolves. They sit on
214
+ * the fixture rows deliberately, the same way the webhook fixture carries its
215
+ * bearer url: the production select must never ask for them, and if it ever
216
+ * widens the leak assertions downstream see a real target rather than an
217
+ * undefined.
218
+ */
219
+ const RAW_SLACK_USER_ID: string = "U0SLACKMEMBERLEAK";
220
+ const RAW_MICROSOFT_TEAMS_USER_ID: string = "aad-ENTRAIDLEAK-4242";
199
221
  const RAW_WEBHOOK_NAME: string = "Payments Incident Bridge";
200
222
  const RAW_WEBHOOK_URL: string =
201
223
  "https://hooks.example.com/T0LEAK/B0LEAK/xoxbSUPERSECRETTOKEN";
@@ -261,6 +283,8 @@ let smsFindBy: jest.SpyInstance;
261
283
  let callFindBy: jest.SpyInstance;
262
284
  let whatsAppFindBy: jest.SpyInstance;
263
285
  let telegramFindBy: jest.SpyInstance;
286
+ let slackFindBy: jest.SpyInstance;
287
+ let microsoftTeamsFindBy: jest.SpyInstance;
264
288
  let webhookFindBy: jest.SpyInstance;
265
289
  let notificationRuleFindBy: jest.SpyInstance;
266
290
  let incidentSeverityFindBy: jest.SpyInstance;
@@ -518,6 +542,52 @@ function telegramMethod(data: {
518
542
  return model;
519
543
  }
520
544
 
545
+ /*
546
+ * The two workspace fixtures deliberately CARRY their addressable ids -
547
+ * slackUserId and microsoftTeamsUserId - even though the production selects
548
+ * must never ask for them. If either select ever widens, the leak assertions
549
+ * downstream see a real target rather than an undefined.
550
+ */
551
+ function slackMethod(data: {
552
+ userId: ObjectID;
553
+ userName: string;
554
+ slackUserId?: string | undefined;
555
+ isVerified: boolean;
556
+ id?: ObjectID | undefined;
557
+ }): UserSlack {
558
+ const model: UserSlack = new UserSlack();
559
+ model.id = data.id || ObjectID.generate();
560
+ model.userId = data.userId;
561
+ model.slackUserName = data.userName;
562
+ model.isVerified = data.isVerified;
563
+
564
+ if (data.slackUserId !== undefined) {
565
+ model.slackUserId = data.slackUserId;
566
+ }
567
+
568
+ return model;
569
+ }
570
+
571
+ function microsoftTeamsMethod(data: {
572
+ userId: ObjectID;
573
+ userName: string;
574
+ microsoftTeamsUserId?: string | undefined;
575
+ isVerified: boolean;
576
+ id?: ObjectID | undefined;
577
+ }): UserMicrosoftTeams {
578
+ const model: UserMicrosoftTeams = new UserMicrosoftTeams();
579
+ model.id = data.id || ObjectID.generate();
580
+ model.userId = data.userId;
581
+ model.microsoftTeamsUserName = data.userName;
582
+ model.isVerified = data.isVerified;
583
+
584
+ if (data.microsoftTeamsUserId !== undefined) {
585
+ model.microsoftTeamsUserId = data.microsoftTeamsUserId;
586
+ }
587
+
588
+ return model;
589
+ }
590
+
521
591
  /*
522
592
  * A webhook fixture that deliberately CARRIES its bearer url, even though the
523
593
  * production select must never ask for it. If the select ever widens, the
@@ -602,6 +672,8 @@ function everySpy(): Array<jest.SpyInstance> {
602
672
  callFindBy,
603
673
  whatsAppFindBy,
604
674
  telegramFindBy,
675
+ slackFindBy,
676
+ microsoftTeamsFindBy,
605
677
  webhookFindBy,
606
678
  notificationRuleFindBy,
607
679
  incidentSeverityFindBy,
@@ -840,6 +912,12 @@ beforeEach(() => {
840
912
  telegramFindBy = jest
841
913
  .spyOn(UserTelegramService, "findBy")
842
914
  .mockResolvedValue([] as never);
915
+ slackFindBy = jest
916
+ .spyOn(UserSlackService, "findBy")
917
+ .mockResolvedValue([] as never);
918
+ microsoftTeamsFindBy = jest
919
+ .spyOn(UserMicrosoftTeamsService, "findBy")
920
+ .mockResolvedValue([] as never);
843
921
  webhookFindBy = jest
844
922
  .spyOn(UserWebhookService, "findBy")
845
923
  .mockResolvedValue([] as never);
@@ -1953,6 +2031,30 @@ describe("status", () => {
1953
2031
  ] as never);
1954
2032
  },
1955
2033
  ],
2034
+ [
2035
+ "Slack",
2036
+ (): void => {
2037
+ slackFindBy.mockResolvedValue([
2038
+ slackMethod({
2039
+ userId: USER_A_ID,
2040
+ userName: RAW_SLACK_USERNAME,
2041
+ isVerified: true,
2042
+ }),
2043
+ ] as never);
2044
+ },
2045
+ ],
2046
+ [
2047
+ "Microsoft Teams",
2048
+ (): void => {
2049
+ microsoftTeamsFindBy.mockResolvedValue([
2050
+ microsoftTeamsMethod({
2051
+ userId: USER_A_ID,
2052
+ userName: RAW_MICROSOFT_TEAMS_USERNAME,
2053
+ isVerified: true,
2054
+ }),
2055
+ ] as never);
2056
+ },
2057
+ ],
1956
2058
  [
1957
2059
  "Webhook",
1958
2060
  (): void => {
@@ -2509,12 +2611,14 @@ describe("opt-out", () => {
2509
2611
 
2510
2612
  /*
2511
2613
  * ---------------------------------------------------------------------------
2512
- * (G) All seven channels.
2614
+ * (G) All nine channels.
2513
2615
  *
2514
2616
  * TeamComplianceService counted call/SMS/email/push only, so a responder whose
2515
2617
  * only method was Telegram, WhatsApp or Webhook was reported non-compliant
2516
2618
  * while the runtime paged them perfectly happily. A false alarm teaches admins
2517
- * to ignore the table, which is worse than no table.
2619
+ * to ignore the table, which is worse than no table. Slack and Microsoft Teams
2620
+ * arrived after that defect was closed, and each gets the same "alone is
2621
+ * enough" pin so the defect cannot quietly return one channel at a time.
2518
2622
  * ---------------------------------------------------------------------------
2519
2623
  */
2520
2624
  describe("notification channels", () => {
@@ -2560,7 +2664,47 @@ describe("notification channels", () => {
2560
2664
  expect((await onlyUser()).status).not.toBe(ReadinessStatus.NotReachable);
2561
2665
  });
2562
2666
 
2563
- test("all seven channels are read, and listed in fallback-attempt order", async () => {
2667
+ /*
2668
+ * The two workspace channels have no verification-code flow - their rows are
2669
+ * born verified from the user's own OAuth workspace link - but the row still
2670
+ * carries isVerified, and a verified row alone has to be enough. These are
2671
+ * the same "alone is enough" pins the four-channel defect earned Telegram and
2672
+ * WhatsApp, written down the day the channel arrived rather than the day it
2673
+ * was missed.
2674
+ */
2675
+ test("a verified SLACK account alone makes a responder reachable", async () => {
2676
+ slackFindBy.mockResolvedValue([
2677
+ slackMethod({
2678
+ userId: USER_A_ID,
2679
+ userName: RAW_SLACK_USERNAME,
2680
+ isVerified: true,
2681
+ }),
2682
+ ] as never);
2683
+
2684
+ const readiness: UserReadiness = await onlyUser();
2685
+
2686
+ expect(readiness.status).not.toBe(ReadinessStatus.NotReachable);
2687
+ expect(methodTypes(readiness)).toEqual([ReadinessMethodType.Slack]);
2688
+ });
2689
+
2690
+ test("a verified MICROSOFT TEAMS account alone makes a responder reachable", async () => {
2691
+ microsoftTeamsFindBy.mockResolvedValue([
2692
+ microsoftTeamsMethod({
2693
+ userId: USER_A_ID,
2694
+ userName: RAW_MICROSOFT_TEAMS_USERNAME,
2695
+ isVerified: true,
2696
+ }),
2697
+ ] as never);
2698
+
2699
+ const readiness: UserReadiness = await onlyUser();
2700
+
2701
+ expect(readiness.status).not.toBe(ReadinessStatus.NotReachable);
2702
+ expect(methodTypes(readiness)).toEqual([
2703
+ ReadinessMethodType.MicrosoftTeams,
2704
+ ]);
2705
+ });
2706
+
2707
+ test("all nine channels are read, and listed in fallback-attempt order", async () => {
2564
2708
  pushFindBy.mockResolvedValue([
2565
2709
  pushMethod({
2566
2710
  userId: USER_A_ID,
@@ -2599,6 +2743,20 @@ describe("notification channels", () => {
2599
2743
  isVerified: true,
2600
2744
  }),
2601
2745
  ] as never);
2746
+ slackFindBy.mockResolvedValue([
2747
+ slackMethod({
2748
+ userId: USER_A_ID,
2749
+ userName: RAW_SLACK_USERNAME,
2750
+ isVerified: true,
2751
+ }),
2752
+ ] as never);
2753
+ microsoftTeamsFindBy.mockResolvedValue([
2754
+ microsoftTeamsMethod({
2755
+ userId: USER_A_ID,
2756
+ userName: RAW_MICROSOFT_TEAMS_USERNAME,
2757
+ isVerified: true,
2758
+ }),
2759
+ ] as never);
2602
2760
  webhookFindBy.mockResolvedValue([
2603
2761
  webhookMethod({ userId: USER_A_ID, name: RAW_WEBHOOK_NAME }),
2604
2762
  ] as never);
@@ -2607,11 +2765,15 @@ describe("notification channels", () => {
2607
2765
 
2608
2766
  /*
2609
2767
  * Display order IS fallback order, deliberately: the first row an admin
2610
- * reads is the channel a fallback page would actually arrive on.
2768
+ * reads is the channel a fallback page would actually arrive on. The
2769
+ * zero-cost tier - Push, Email, Slack, Microsoft Teams - leads, then the
2770
+ * paid channels in escalating-intrusiveness order, then Webhook.
2611
2771
  */
2612
2772
  expect(methodTypes(readiness)).toEqual([
2613
2773
  ReadinessMethodType.Push,
2614
2774
  ReadinessMethodType.Email,
2775
+ ReadinessMethodType.Slack,
2776
+ ReadinessMethodType.MicrosoftTeams,
2615
2777
  ReadinessMethodType.SMS,
2616
2778
  ReadinessMethodType.Call,
2617
2779
  ReadinessMethodType.WhatsApp,
@@ -2644,7 +2806,14 @@ describe("notification channels", () => {
2644
2806
  });
2645
2807
  });
2646
2808
 
2647
- test("both zero-cost channels are named when the responder has both", async () => {
2809
+ test("every zero-cost channel is named when the responder has all four - and no paid one alongside them", async () => {
2810
+ /*
2811
+ * The zero-cost tier is Push, Email, Slack, Microsoft Teams, and the
2812
+ * fallback takes ALL of them when any is present before it considers
2813
+ * spending money. The verified SMS staged here is the assertion's other
2814
+ * half: a sentence that named SMS beside four free channels would be
2815
+ * describing a fallback that does not happen.
2816
+ */
2648
2817
  pushFindBy.mockResolvedValue([
2649
2818
  pushMethod({ userId: USER_A_ID, isVerified: true }),
2650
2819
  ] as never);
@@ -2655,11 +2824,74 @@ describe("notification channels", () => {
2655
2824
  isVerified: true,
2656
2825
  }),
2657
2826
  ] as never);
2827
+ slackFindBy.mockResolvedValue([
2828
+ slackMethod({
2829
+ userId: USER_A_ID,
2830
+ userName: RAW_SLACK_USERNAME,
2831
+ isVerified: true,
2832
+ }),
2833
+ ] as never);
2834
+ microsoftTeamsFindBy.mockResolvedValue([
2835
+ microsoftTeamsMethod({
2836
+ userId: USER_A_ID,
2837
+ userName: RAW_MICROSOFT_TEAMS_USERNAME,
2838
+ isVerified: true,
2839
+ }),
2840
+ ] as never);
2841
+ smsFindBy.mockResolvedValue([
2842
+ smsMethod({
2843
+ userId: USER_A_ID,
2844
+ phone: RAW_SMS_PHONE,
2845
+ isVerified: true,
2846
+ }),
2847
+ ] as never);
2658
2848
 
2659
2849
  const readiness: UserReadiness = await onlyUser();
2660
2850
 
2661
2851
  expect(readiness.reasons[0]).toBe(
2662
- "No rules for Sev1 incidents - pages fall back to Push, Email",
2852
+ "No rules for Sev1 incidents - pages fall back to Push, Email, Slack, Microsoft Teams",
2853
+ );
2854
+ });
2855
+
2856
+ test("a verified SLACK account alone is a zero-cost fallback, pre-empting a paid channel", async () => {
2857
+ slackFindBy.mockResolvedValue([
2858
+ slackMethod({
2859
+ userId: USER_A_ID,
2860
+ userName: RAW_SLACK_USERNAME,
2861
+ isVerified: true,
2862
+ }),
2863
+ ] as never);
2864
+ smsFindBy.mockResolvedValue([
2865
+ smsMethod({
2866
+ userId: USER_A_ID,
2867
+ phone: RAW_SMS_PHONE,
2868
+ isVerified: true,
2869
+ }),
2870
+ ] as never);
2871
+
2872
+ expect((await onlyUser()).reasons[0]).toBe(
2873
+ "No rules for Sev1 incidents - pages fall back to Slack",
2874
+ );
2875
+ });
2876
+
2877
+ test("a verified MICROSOFT TEAMS account alone is a zero-cost fallback, pre-empting a paid channel", async () => {
2878
+ microsoftTeamsFindBy.mockResolvedValue([
2879
+ microsoftTeamsMethod({
2880
+ userId: USER_A_ID,
2881
+ userName: RAW_MICROSOFT_TEAMS_USERNAME,
2882
+ isVerified: true,
2883
+ }),
2884
+ ] as never);
2885
+ callFindBy.mockResolvedValue([
2886
+ callMethod({
2887
+ userId: USER_A_ID,
2888
+ phone: RAW_CALL_PHONE,
2889
+ isVerified: true,
2890
+ }),
2891
+ ] as never);
2892
+
2893
+ expect((await onlyUser()).reasons[0]).toBe(
2894
+ "No rules for Sev1 incidents - pages fall back to Microsoft Teams",
2663
2895
  );
2664
2896
  });
2665
2897
 
@@ -2779,11 +3011,12 @@ describe("notification channels", () => {
2779
3011
  * (G2) Method identity - referencing a method without reading it.
2780
3012
  *
2781
3013
  * The admin rule form has to POINT A RULE AT one of these methods, and it is not
2782
- * allowed to read the row it points at: the seven method models are scoped to
3014
+ * allowed to read the row it points at: the nine method models are scoped to
2783
3015
  * their owner precisely because their columns are the raw phone number, the
2784
- * webhook bearer url, the push device token, the telegram chat id and the
2785
- * verification code. Widening that scope so a dropdown could be populated was
2786
- * tried, and the exposure it opened could not be contained.
3016
+ * webhook bearer url, the push device token, the telegram chat id, the slack
3017
+ * member id, the teams object id and the verification code. Widening that scope
3018
+ * so a dropdown could be populated was tried, and the exposure it opened could
3019
+ * not be contained.
2787
3020
  *
2788
3021
  * `methodId` is what replaces it. The foreign key is not a secret - it is
2789
3022
  * already stored in plain sight on every rule its owner created, and it
@@ -2801,7 +3034,7 @@ describe("method identity", () => {
2801
3034
  attachDirectly(USER_A_ID);
2802
3035
  });
2803
3036
 
2804
- function attachEverySevenChannels(): void {
3037
+ function attachEveryNineChannels(): void {
2805
3038
  pushFindBy.mockResolvedValue([
2806
3039
  pushMethod({
2807
3040
  userId: USER_A_ID,
@@ -2850,6 +3083,24 @@ describe("method identity", () => {
2850
3083
  id: TELEGRAM_METHOD_ID,
2851
3084
  }),
2852
3085
  ] as never);
3086
+ slackFindBy.mockResolvedValue([
3087
+ slackMethod({
3088
+ userId: USER_A_ID,
3089
+ userName: RAW_SLACK_USERNAME,
3090
+ slackUserId: RAW_SLACK_USER_ID,
3091
+ isVerified: true,
3092
+ id: SLACK_METHOD_ID,
3093
+ }),
3094
+ ] as never);
3095
+ microsoftTeamsFindBy.mockResolvedValue([
3096
+ microsoftTeamsMethod({
3097
+ userId: USER_A_ID,
3098
+ userName: RAW_MICROSOFT_TEAMS_USERNAME,
3099
+ microsoftTeamsUserId: RAW_MICROSOFT_TEAMS_USER_ID,
3100
+ isVerified: true,
3101
+ id: MICROSOFT_TEAMS_METHOD_ID,
3102
+ }),
3103
+ ] as never);
2853
3104
  webhookFindBy.mockResolvedValue([
2854
3105
  webhookMethod({
2855
3106
  userId: USER_A_ID,
@@ -2860,8 +3111,8 @@ describe("method identity", () => {
2860
3111
  ] as never);
2861
3112
  }
2862
3113
 
2863
- test("all seven channels carry the id of their OWN row, which is what a rule references", async () => {
2864
- attachEverySevenChannels();
3114
+ test("all nine channels carry the id of their OWN row, which is what a rule references", async () => {
3115
+ attachEveryNineChannels();
2865
3116
 
2866
3117
  const readiness: UserReadiness = await onlyUser();
2867
3118
 
@@ -2887,17 +3138,26 @@ describe("method identity", () => {
2887
3138
  expect(
2888
3139
  methodOfType(readiness, ReadinessMethodType.Telegram).methodId.toString(),
2889
3140
  ).toBe(TELEGRAM_METHOD_ID.toString());
3141
+ expect(
3142
+ methodOfType(readiness, ReadinessMethodType.Slack).methodId.toString(),
3143
+ ).toBe(SLACK_METHOD_ID.toString());
3144
+ expect(
3145
+ methodOfType(
3146
+ readiness,
3147
+ ReadinessMethodType.MicrosoftTeams,
3148
+ ).methodId.toString(),
3149
+ ).toBe(MICROSOFT_TEAMS_METHOD_ID.toString());
2890
3150
  expect(
2891
3151
  methodOfType(readiness, ReadinessMethodType.Webhook).methodId.toString(),
2892
3152
  ).toBe(WEBHOOK_METHOD_ID.toString());
2893
3153
  });
2894
3154
 
2895
3155
  test("no methodId is the USER's id - a rule pointed at a user id points at no method at all", async () => {
2896
- attachEverySevenChannels();
3156
+ attachEveryNineChannels();
2897
3157
 
2898
3158
  const readiness: UserReadiness = await onlyUser();
2899
3159
 
2900
- expect(readiness.methods).toHaveLength(7);
3160
+ expect(readiness.methods).toHaveLength(9);
2901
3161
 
2902
3162
  for (const method of readiness.methods) {
2903
3163
  /*
@@ -3052,6 +3312,22 @@ describe("identifier exposure", () => {
3052
3312
  isVerified: true,
3053
3313
  }),
3054
3314
  ] as never);
3315
+ slackFindBy.mockResolvedValue([
3316
+ slackMethod({
3317
+ userId: USER_A_ID,
3318
+ userName: RAW_SLACK_USERNAME,
3319
+ slackUserId: RAW_SLACK_USER_ID,
3320
+ isVerified: true,
3321
+ }),
3322
+ ] as never);
3323
+ microsoftTeamsFindBy.mockResolvedValue([
3324
+ microsoftTeamsMethod({
3325
+ userId: USER_A_ID,
3326
+ userName: RAW_MICROSOFT_TEAMS_USERNAME,
3327
+ microsoftTeamsUserId: RAW_MICROSOFT_TEAMS_USER_ID,
3328
+ isVerified: true,
3329
+ }),
3330
+ ] as never);
3055
3331
  webhookFindBy.mockResolvedValue([
3056
3332
  webhookMethod({
3057
3333
  userId: USER_A_ID,
@@ -3090,6 +3366,15 @@ describe("identifier exposure", () => {
3090
3366
  expect(masked.get(ReadinessMethodType.Telegram)).toBe(
3091
3367
  `@ad${IDENTIFIER_MASK}`,
3092
3368
  );
3369
+ /*
3370
+ * Both workspace channels mask their human-facing NAME column with the
3371
+ * handle rule. The addressable ids - the Slack member id, the Entra object
3372
+ * id - are never selected at all, which the select tests below pin.
3373
+ */
3374
+ expect(masked.get(ReadinessMethodType.Slack)).toBe(`ad${IDENTIFIER_MASK}`);
3375
+ expect(masked.get(ReadinessMethodType.MicrosoftTeams)).toBe(
3376
+ `Ad${IDENTIFIER_MASK}`,
3377
+ );
3093
3378
  expect(masked.get(ReadinessMethodType.Webhook)).toBe(
3094
3379
  `Pa${IDENTIFIER_MASK}`,
3095
3380
  );
@@ -3098,7 +3383,7 @@ describe("identifier exposure", () => {
3098
3383
  test("a method carries FOUR fields and no fifth - the id is the ONLY thing beside the mask", async () => {
3099
3384
  const readiness: UserReadiness = await onlyUser();
3100
3385
 
3101
- expect(readiness.methods).toHaveLength(7);
3386
+ expect(readiness.methods).toHaveLength(9);
3102
3387
 
3103
3388
  /*
3104
3389
  * An exact key set, not a "does not contain the url" check, and this is the
@@ -3145,6 +3430,10 @@ describe("identifier exposure", () => {
3145
3430
  RAW_CALL_PHONE,
3146
3431
  RAW_WHATSAPP_PHONE,
3147
3432
  RAW_TELEGRAM_HANDLE,
3433
+ RAW_SLACK_USERNAME,
3434
+ RAW_SLACK_USER_ID,
3435
+ RAW_MICROSOFT_TEAMS_USERNAME,
3436
+ RAW_MICROSOFT_TEAMS_USER_ID,
3148
3437
  RAW_WEBHOOK_NAME,
3149
3438
  RAW_WEBHOOK_URL,
3150
3439
  RAW_PUSH_DEVICE,
@@ -3158,6 +3447,10 @@ describe("identifier exposure", () => {
3158
3447
  "2071838",
3159
3448
  "6129374",
3160
3449
  "night_pager",
3450
+ "pager.slack",
3451
+ "SLACKMEMBERLEAK",
3452
+ "Overnight",
3453
+ "ENTRAIDLEAK",
3161
3454
  "Incident Bridge",
3162
3455
  "SUPERSECRETTOKEN",
3163
3456
  "iPhone",
@@ -3200,6 +3493,30 @@ describe("identifier exposure", () => {
3200
3493
  expect(select["telegramUserHandle"]).toBe(true);
3201
3494
  });
3202
3495
 
3496
+ test("the slack read never asks for the member id, which is the addressable target", async () => {
3497
+ await policySummary();
3498
+
3499
+ const select: Record<string, unknown> = firstCall(slackFindBy).select!;
3500
+
3501
+ /*
3502
+ * Same rule as the telegram chat id: the member id is what the bot sends
3503
+ * to, the username is the label the owner recognises, and only the label
3504
+ * belongs on a readiness surface.
3505
+ */
3506
+ expect(Object.keys(select)).not.toContain("slackUserId");
3507
+ expect(select["slackUserName"]).toBe(true);
3508
+ });
3509
+
3510
+ test("the microsoft teams read never asks for the entra object id, which is the addressable target", async () => {
3511
+ await policySummary();
3512
+
3513
+ const select: Record<string, unknown> =
3514
+ firstCall(microsoftTeamsFindBy).select!;
3515
+
3516
+ expect(Object.keys(select)).not.toContain("microsoftTeamsUserId");
3517
+ expect(select["microsoftTeamsUserName"]).toBe(true);
3518
+ });
3519
+
3203
3520
  test("a method with no identifier at all still reports the bare mask, not an empty cell", async () => {
3204
3521
  pushFindBy.mockResolvedValue([
3205
3522
  pushMethod({ userId: USER_A_ID, isVerified: true }),
@@ -3349,6 +3666,8 @@ describe("batching and paging", () => {
3349
3666
  callFindBy,
3350
3667
  whatsAppFindBy,
3351
3668
  telegramFindBy,
3669
+ slackFindBy,
3670
+ microsoftTeamsFindBy,
3352
3671
  webhookFindBy,
3353
3672
  notificationRuleFindBy,
3354
3673
  ]) {
@@ -3364,6 +3683,8 @@ describe("batching and paging", () => {
3364
3683
  callFindBy,
3365
3684
  whatsAppFindBy,
3366
3685
  telegramFindBy,
3686
+ slackFindBy,
3687
+ microsoftTeamsFindBy,
3367
3688
  webhookFindBy,
3368
3689
  notificationRuleFindBy,
3369
3690
  ]) {
@@ -3393,7 +3714,7 @@ describe("batching and paging", () => {
3393
3714
  const calls: Array<FindByCall> = everyFindByCall();
3394
3715
 
3395
3716
  // A guard that asserts over an empty list guards nothing.
3396
- expect(calls.length).toBeGreaterThanOrEqual(15);
3717
+ expect(calls.length).toBeGreaterThanOrEqual(17);
3397
3718
 
3398
3719
  for (const call of calls) {
3399
3720
  /*
@@ -3467,7 +3788,7 @@ describe("batching and paging", () => {
3467
3788
 
3468
3789
  const calls: Array<FindByCall> = everyFindByCall();
3469
3790
 
3470
- expect(calls.length).toBeGreaterThanOrEqual(15);
3791
+ expect(calls.length).toBeGreaterThanOrEqual(17);
3471
3792
 
3472
3793
  for (const call of calls) {
3473
3794
  /*
@@ -3497,6 +3818,8 @@ describe("batching and paging", () => {
3497
3818
  callFindBy,
3498
3819
  whatsAppFindBy,
3499
3820
  telegramFindBy,
3821
+ slackFindBy,
3822
+ microsoftTeamsFindBy,
3500
3823
  webhookFindBy,
3501
3824
  notificationRuleFindBy,
3502
3825
  ]) {
@@ -3929,6 +4252,8 @@ describe("getReadinessForUsers", () => {
3929
4252
  callFindBy,
3930
4253
  whatsAppFindBy,
3931
4254
  telegramFindBy,
4255
+ slackFindBy,
4256
+ microsoftTeamsFindBy,
3932
4257
  webhookFindBy,
3933
4258
  notificationRuleFindBy,
3934
4259
  ]) {
@@ -148,6 +148,8 @@ type RuleMethodColumn =
148
148
  | "userPushId"
149
149
  | "userWhatsAppId"
150
150
  | "userTelegramId"
151
+ | "userSlackId"
152
+ | "userMicrosoftTeamsId"
151
153
  | "userWebhookId";
152
154
 
153
155
  type RuleIdColumn =
@@ -164,6 +166,8 @@ const METHOD_COLUMNS: Array<RuleMethodColumn> = [
164
166
  "userPushId",
165
167
  "userWhatsAppId",
166
168
  "userTelegramId",
169
+ "userSlackId",
170
+ "userMicrosoftTeamsId",
167
171
  "userWebhookId",
168
172
  ];
169
173
 
@@ -180,7 +184,7 @@ const ID_COLUMNS: Array<RuleIdColumn> = [
180
184
  * An in-memory stand-in for the UserNotificationRule table.
181
185
  *
182
186
  * Rows are real model instances, because that is what the job reads: it asks
183
- * rules for `.userId`, `.isOptOut`, `.notifyAfterMinutes` and the seven method
187
+ * rules for `.userId`, `.isOptOut`, `.notifyAfterMinutes` and the nine method
184
188
  * foreign keys, and hands `create` a model it built itself.
185
189
  * -------------------------------------------------------------------------
186
190
  */