@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
@@ -0,0 +1,343 @@
1
+ import UserMicrosoftTeamsService from "../../../Server/Services/UserMicrosoftTeamsService";
2
+ import UserNotificationRuleService from "../../../Server/Services/UserNotificationRuleService";
3
+ import WorkspaceProjectAuthTokenService from "../../../Server/Services/WorkspaceProjectAuthTokenService";
4
+ import WorkspaceUserAuthTokenService from "../../../Server/Services/WorkspaceUserAuthTokenService";
5
+ import logger from "../../../Server/Utils/Logger";
6
+ import CreateBy from "../../../Server/Types/Database/CreateBy";
7
+ import { OnCreate } from "../../../Server/Types/Database/Hooks";
8
+ import UserMicrosoftTeams from "../../../Models/DatabaseModels/UserMicrosoftTeams";
9
+ import WorkspaceProjectAuthToken from "../../../Models/DatabaseModels/WorkspaceProjectAuthToken";
10
+ import WorkspaceUserAuthToken from "../../../Models/DatabaseModels/WorkspaceUserAuthToken";
11
+ import BadDataException from "../../../Types/Exception/BadDataException";
12
+ import ObjectID from "../../../Types/ObjectID";
13
+ import PositiveNumber from "../../../Types/PositiveNumber";
14
+ import LIMIT_MAX from "../../../Types/Database/LimitMax";
15
+ import WorkspaceType from "../../../Types/Workspace/WorkspaceType";
16
+ import { describe, expect, test, beforeEach, afterEach } from "@jest/globals";
17
+
18
+ /*
19
+ * The Microsoft Teams twin of UserSlackService.test.ts — the same
20
+ * creation-is-verification model, with the two Teams-specific differences
21
+ * pinned: the stored id is the Microsoft Entra object id from the user's own
22
+ * WorkspaceUserAuthToken, and the display name comes from that token's
23
+ * miscData (captured at OAuth time) rather than from a live API call.
24
+ */
25
+
26
+ const PROJECT_ID: ObjectID = new ObjectID(
27
+ "11111111-1111-4111-8111-111111111111",
28
+ );
29
+ const USER_ID: ObjectID = new ObjectID("22222222-2222-4222-8222-222222222222");
30
+ const METHOD_ID: ObjectID = new ObjectID(
31
+ "33333333-3333-4333-8333-333333333333",
32
+ );
33
+
34
+ const ENTRA_OBJECT_ID: string = "e6f1c1f7-aad0-4b6c-9c11-2f5b7c8d9e0f";
35
+
36
+ type OnBeforeCreateFunction = (
37
+ createBy: CreateBy<UserMicrosoftTeams>,
38
+ ) => Promise<OnCreate<UserMicrosoftTeams>>;
39
+
40
+ type OnCreateSuccessFunction = (
41
+ onCreate: OnCreate<UserMicrosoftTeams>,
42
+ createdItem: UserMicrosoftTeams,
43
+ ) => Promise<UserMicrosoftTeams>;
44
+
45
+ type OnBeforeDeleteFunction = (deleteBy: {
46
+ query: Record<string, unknown>;
47
+ props: { isRoot: boolean };
48
+ }) => Promise<unknown>;
49
+
50
+ function callOnBeforeCreate(
51
+ createBy: CreateBy<UserMicrosoftTeams>,
52
+ ): Promise<OnCreate<UserMicrosoftTeams>> {
53
+ return (
54
+ UserMicrosoftTeamsService as unknown as {
55
+ onBeforeCreate: OnBeforeCreateFunction;
56
+ }
57
+ ).onBeforeCreate(createBy);
58
+ }
59
+
60
+ function callOnCreateSuccess(
61
+ onCreate: OnCreate<UserMicrosoftTeams>,
62
+ createdItem: UserMicrosoftTeams,
63
+ ): Promise<UserMicrosoftTeams> {
64
+ return (
65
+ UserMicrosoftTeamsService as unknown as {
66
+ onCreateSuccess: OnCreateSuccessFunction;
67
+ }
68
+ ).onCreateSuccess(onCreate, createdItem);
69
+ }
70
+
71
+ function callOnBeforeDelete(deleteBy: {
72
+ query: Record<string, unknown>;
73
+ props: { isRoot: boolean };
74
+ }): Promise<unknown> {
75
+ return (
76
+ UserMicrosoftTeamsService as unknown as {
77
+ onBeforeDelete: OnBeforeDeleteFunction;
78
+ }
79
+ ).onBeforeDelete(deleteBy);
80
+ }
81
+
82
+ function createBy(
83
+ data: Partial<UserMicrosoftTeams> = {},
84
+ props: { isRoot: boolean } = { isRoot: false },
85
+ ): CreateBy<UserMicrosoftTeams> {
86
+ return {
87
+ data: {
88
+ projectId: PROJECT_ID,
89
+ userId: USER_ID,
90
+ ...data,
91
+ } as UserMicrosoftTeams,
92
+ props: props,
93
+ } as CreateBy<UserMicrosoftTeams>;
94
+ }
95
+
96
+ describe("UserMicrosoftTeamsService", () => {
97
+ let countBy: jest.SpyInstance;
98
+ let getProjectAuth: jest.SpyInstance;
99
+ let getUserAuth: jest.SpyInstance;
100
+ let loggerError: jest.SpyInstance;
101
+
102
+ beforeEach(() => {
103
+ loggerError = jest.spyOn(logger, "error").mockImplementation((): void => {
104
+ return undefined;
105
+ });
106
+
107
+ countBy = jest
108
+ .spyOn(UserMicrosoftTeamsService, "countBy")
109
+ .mockResolvedValue(new PositiveNumber(0) as never);
110
+
111
+ getProjectAuth = jest
112
+ .spyOn(WorkspaceProjectAuthTokenService, "getProjectAuth")
113
+ .mockResolvedValue({
114
+ id: PROJECT_ID,
115
+ authToken: "graph-delegated-token",
116
+ } as unknown as WorkspaceProjectAuthToken as never);
117
+
118
+ getUserAuth = jest
119
+ .spyOn(WorkspaceUserAuthTokenService, "getUserAuth")
120
+ .mockResolvedValue({
121
+ workspaceUserId: ENTRA_OBJECT_ID,
122
+ miscData: {
123
+ userId: USER_ID.toString(),
124
+ displayName: "Alice Example",
125
+ email: "alice@example.com",
126
+ },
127
+ } as unknown as WorkspaceUserAuthToken as never);
128
+ });
129
+
130
+ afterEach(() => {
131
+ jest.restoreAllMocks();
132
+ });
133
+
134
+ describe("onBeforeCreate", () => {
135
+ test("resolves the Entra object id from the user's own workspace link and stamps verified", async () => {
136
+ const result: OnCreate<UserMicrosoftTeams> =
137
+ await callOnBeforeCreate(createBy());
138
+
139
+ expect(result.createBy.data.microsoftTeamsUserId).toBe(ENTRA_OBJECT_ID);
140
+ expect(result.createBy.data.isVerified).toBe(true);
141
+
142
+ const authArg: {
143
+ workspaceType: WorkspaceType;
144
+ } = getUserAuth.mock.calls[0][0] as { workspaceType: WorkspaceType };
145
+ expect(authArg.workspaceType).toBe(WorkspaceType.MicrosoftTeams);
146
+ });
147
+
148
+ test("the display name comes from the OAuth-captured miscData, with no API call", async () => {
149
+ const result: OnCreate<UserMicrosoftTeams> =
150
+ await callOnBeforeCreate(createBy());
151
+
152
+ expect(result.createBy.data.microsoftTeamsUserName).toBe("Alice Example");
153
+ });
154
+
155
+ test("a link with no captured display name still creates, just without a label", async () => {
156
+ getUserAuth.mockResolvedValue({
157
+ workspaceUserId: ENTRA_OBJECT_ID,
158
+ miscData: { userId: USER_ID.toString() },
159
+ } as unknown as WorkspaceUserAuthToken as never);
160
+
161
+ const result: OnCreate<UserMicrosoftTeams> =
162
+ await callOnBeforeCreate(createBy());
163
+
164
+ expect(result.createBy.data.microsoftTeamsUserId).toBe(ENTRA_OBJECT_ID);
165
+ expect(result.createBy.data.microsoftTeamsUserName).toBeUndefined();
166
+ });
167
+
168
+ test("a non-root caller supplying isVerified is refused", async () => {
169
+ await expect(
170
+ callOnBeforeCreate(createBy({ isVerified: true })),
171
+ ).rejects.toThrow("isVerified cannot be set to true");
172
+ });
173
+
174
+ test("a non-root caller supplying microsoftTeamsUserId is refused - the address is never client input", async () => {
175
+ await expect(
176
+ callOnBeforeCreate(
177
+ createBy({ microsoftTeamsUserId: "attacker-entra-id" }),
178
+ ),
179
+ ).rejects.toThrow("microsoftTeamsUserId cannot be set directly");
180
+ });
181
+
182
+ test("a project that is not connected to Microsoft Teams is refused with the settings pointer", async () => {
183
+ getProjectAuth.mockResolvedValue(null as never);
184
+
185
+ await expect(callOnBeforeCreate(createBy())).rejects.toThrow(
186
+ "This project is not connected to Microsoft Teams. Please ask a project admin to connect Microsoft Teams in Project Settings > Microsoft Teams Integration.",
187
+ );
188
+ });
189
+
190
+ test("a user who has not connected their Microsoft Teams account is refused with the settings pointer", async () => {
191
+ getUserAuth.mockResolvedValue(null as never);
192
+
193
+ await expect(callOnBeforeCreate(createBy())).rejects.toThrow(
194
+ "Your Microsoft Teams account is not connected to OneUptime for this project. Please go to User Settings > Microsoft Teams Integration and connect your Microsoft Teams account first.",
195
+ );
196
+ });
197
+
198
+ test("a second row for the same (user, project) is refused as a duplicate", async () => {
199
+ countBy.mockResolvedValue(new PositiveNumber(1) as never);
200
+
201
+ await expect(callOnBeforeCreate(createBy())).rejects.toThrow(
202
+ "Microsoft Teams is already added as a notification method for this project.",
203
+ );
204
+
205
+ expect(getUserAuth).not.toHaveBeenCalled();
206
+ });
207
+
208
+ test("a missing projectId or userId is refused before anything is queried", async () => {
209
+ await expect(
210
+ callOnBeforeCreate({
211
+ data: { userId: USER_ID } as UserMicrosoftTeams,
212
+ props: { isRoot: false },
213
+ } as CreateBy<UserMicrosoftTeams>),
214
+ ).rejects.toThrow("projectId and userId are required");
215
+
216
+ expect(countBy).not.toHaveBeenCalled();
217
+ });
218
+
219
+ test("a user auth row with no workspaceUserId counts as not connected", async () => {
220
+ getUserAuth.mockResolvedValue({
221
+ workspaceUserId: "",
222
+ } as unknown as WorkspaceUserAuthToken as never);
223
+
224
+ await expect(callOnBeforeCreate(createBy())).rejects.toThrow(
225
+ BadDataException,
226
+ );
227
+ });
228
+ });
229
+
230
+ describe("onCreateSuccess", () => {
231
+ let seedRules: jest.SpyInstance;
232
+
233
+ beforeEach(() => {
234
+ seedRules = jest
235
+ .spyOn(
236
+ UserNotificationRuleService,
237
+ "addDefaultNotificationRulesForVerifiedMethod",
238
+ )
239
+ .mockResolvedValue(undefined as never);
240
+ });
241
+
242
+ test("seeds default rules pointed at the new row via userMicrosoftTeamsId", async () => {
243
+ const created: UserMicrosoftTeams = {
244
+ id: METHOD_ID,
245
+ projectId: PROJECT_ID,
246
+ userId: USER_ID,
247
+ } as unknown as UserMicrosoftTeams;
248
+
249
+ await callOnCreateSuccess(
250
+ {
251
+ createBy: createBy(),
252
+ carryForward: null,
253
+ } as OnCreate<UserMicrosoftTeams>,
254
+ created,
255
+ );
256
+
257
+ const arg: {
258
+ notificationMethod: { userMicrosoftTeamsId: ObjectID };
259
+ } = seedRules.mock.calls[0][0] as {
260
+ notificationMethod: { userMicrosoftTeamsId: ObjectID };
261
+ };
262
+ expect(arg.notificationMethod.userMicrosoftTeamsId.toString()).toBe(
263
+ METHOD_ID.toString(),
264
+ );
265
+ });
266
+
267
+ test("a seeding failure is swallowed and logged - the method row is already real", async () => {
268
+ seedRules.mockRejectedValue(new Error("severity read failed") as never);
269
+
270
+ const created: UserMicrosoftTeams = {
271
+ id: METHOD_ID,
272
+ projectId: PROJECT_ID,
273
+ userId: USER_ID,
274
+ } as unknown as UserMicrosoftTeams;
275
+
276
+ await expect(
277
+ callOnCreateSuccess(
278
+ {
279
+ createBy: createBy(),
280
+ carryForward: null,
281
+ } as OnCreate<UserMicrosoftTeams>,
282
+ created,
283
+ ),
284
+ ).resolves.toBe(created);
285
+
286
+ expect(loggerError).toHaveBeenCalled();
287
+ });
288
+ });
289
+
290
+ describe("onBeforeDelete", () => {
291
+ test("deletes every notification rule pointing at each row being deleted", async () => {
292
+ jest.spyOn(UserMicrosoftTeamsService, "findBy").mockResolvedValue([
293
+ {
294
+ id: METHOD_ID,
295
+ projectId: PROJECT_ID,
296
+ } as unknown as UserMicrosoftTeams,
297
+ ] as never);
298
+
299
+ const deleteRules: jest.SpyInstance = jest
300
+ .spyOn(UserNotificationRuleService, "deleteBy")
301
+ .mockResolvedValue([] as never);
302
+
303
+ await callOnBeforeDelete({
304
+ query: { _id: METHOD_ID },
305
+ props: { isRoot: true },
306
+ });
307
+
308
+ const arg: {
309
+ query: { userMicrosoftTeamsId: ObjectID; projectId: ObjectID };
310
+ limit: number;
311
+ } = deleteRules.mock.calls[0][0] as {
312
+ query: { userMicrosoftTeamsId: ObjectID; projectId: ObjectID };
313
+ limit: number;
314
+ };
315
+ expect(arg.query.userMicrosoftTeamsId.toString()).toBe(
316
+ METHOD_ID.toString(),
317
+ );
318
+ expect(arg.query.projectId.toString()).toBe(PROJECT_ID.toString());
319
+ expect(arg.limit).toBe(LIMIT_MAX);
320
+ });
321
+ });
322
+
323
+ describe("getDeletionImpact", () => {
324
+ test("delegates to the shared preview with methodType Microsoft Teams", async () => {
325
+ const getImpact: jest.SpyInstance = jest
326
+ .spyOn(
327
+ UserNotificationRuleService,
328
+ "getNotificationMethodDeletionImpact",
329
+ )
330
+ .mockResolvedValue({} as never);
331
+
332
+ await UserMicrosoftTeamsService.getDeletionImpact({
333
+ itemId: METHOD_ID,
334
+ projectId: PROJECT_ID,
335
+ });
336
+
337
+ const arg: { methodType: string } = getImpact.mock.calls[0][0] as {
338
+ methodType: string;
339
+ };
340
+ expect(arg.methodType).toBe("Microsoft Teams");
341
+ });
342
+ });
343
+ });
@@ -10,8 +10,10 @@ import ProjectService from "../../../Server/Services/ProjectService";
10
10
  import TeamMemberService from "../../../Server/Services/TeamMemberService";
11
11
  import UserCallService from "../../../Server/Services/UserCallService";
12
12
  import UserEmailService from "../../../Server/Services/UserEmailService";
13
+ import UserMicrosoftTeamsService from "../../../Server/Services/UserMicrosoftTeamsService";
13
14
  import UserNotificationRuleService from "../../../Server/Services/UserNotificationRuleService";
14
15
  import UserPushService from "../../../Server/Services/UserPushService";
16
+ import UserSlackService from "../../../Server/Services/UserSlackService";
15
17
  import UserService from "../../../Server/Services/UserService";
16
18
  import UserSmsService from "../../../Server/Services/UserSmsService";
17
19
  import UserTelegramService from "../../../Server/Services/UserTelegramService";
@@ -21,7 +23,9 @@ import Project from "../../../Models/DatabaseModels/Project";
21
23
  import User from "../../../Models/DatabaseModels/User";
22
24
  import UserCall from "../../../Models/DatabaseModels/UserCall";
23
25
  import UserEmail from "../../../Models/DatabaseModels/UserEmail";
26
+ import UserMicrosoftTeams from "../../../Models/DatabaseModels/UserMicrosoftTeams";
24
27
  import UserPush from "../../../Models/DatabaseModels/UserPush";
28
+ import UserSlack from "../../../Models/DatabaseModels/UserSlack";
25
29
  import UserSMS from "../../../Models/DatabaseModels/UserSMS";
26
30
  import UserTelegram from "../../../Models/DatabaseModels/UserTelegram";
27
31
  import UserWebhook from "../../../Models/DatabaseModels/UserWebhook";
@@ -69,15 +73,18 @@ import { beforeEach, describe, expect, test } from "@jest/globals";
69
73
  * 4. MEMBERSHIP IS A CLAIM ABOUT A PROJECT. Holding an administrative
70
74
  * permission somewhere is not a licence to write for a user id anywhere.
71
75
  *
72
- * 5. THE THREE UNADDABLE CHANNELS STAY UNADDABLE. Push has no value to type,
73
- * Telegram needs the account holder to talk to the bot, and a webhook
74
- * would be live the instant it was written because UserWebhook has no
75
- * verification at all.
76
+ * 5. THE FIVE UNADDABLE CHANNELS STAY UNADDABLE. Push has no value to type,
77
+ * Telegram needs the account holder to talk to the bot, Slack and
78
+ * Microsoft Teams are pointers at the owner's own OAuth workspace link,
79
+ * and a webhook would be live the instant it was written because
80
+ * UserWebhook has no verification at all.
76
81
  */
77
82
 
78
83
  const RAW_EMAIL: string = "jane.ops@example.com";
79
84
  const RAW_PHONE: string = "+14155554821";
80
85
  const RAW_TELEGRAM_HANDLE: string = "@janeops_oncall";
86
+ const RAW_SLACK_USERNAME: string = "ops.jane.slack";
87
+ const RAW_TEAMS_USERNAME: string = "Jane Ops (Teams)";
81
88
  const RAW_WEBHOOK_NAME: string = "payments-hook";
82
89
  const RAW_DEVICE_NAME: string = "Jane's iPhone";
83
90
 
@@ -85,6 +92,8 @@ const ALL_RAW_VALUES: Array<string> = [
85
92
  RAW_EMAIL,
86
93
  RAW_PHONE,
87
94
  RAW_TELEGRAM_HANDLE,
95
+ RAW_SLACK_USERNAME,
96
+ RAW_TEAMS_USERNAME,
88
97
  RAW_WEBHOOK_NAME,
89
98
  RAW_DEVICE_NAME,
90
99
  ];
@@ -105,6 +114,8 @@ let callFindBy: jest.SpyInstance;
105
114
  let whatsAppFindBy: jest.SpyInstance;
106
115
  let pushFindBy: jest.SpyInstance;
107
116
  let telegramFindBy: jest.SpyInstance;
117
+ let slackFindBy: jest.SpyInstance;
118
+ let microsoftTeamsFindBy: jest.SpyInstance;
108
119
  let webhookFindBy: jest.SpyInstance;
109
120
 
110
121
  let emailCreate: jest.SpyInstance;
@@ -112,6 +123,10 @@ let smsCreate: jest.SpyInstance;
112
123
  let smsFindOneBy: jest.SpyInstance;
113
124
  let smsDelete: jest.SpyInstance;
114
125
  let smsResend: jest.SpyInstance;
126
+ let slackFindOneBy: jest.SpyInstance;
127
+ let slackDelete: jest.SpyInstance;
128
+ let microsoftTeamsFindOneBy: jest.SpyInstance;
129
+ let microsoftTeamsDelete: jest.SpyInstance;
115
130
  let deletionImpact: jest.SpyInstance;
116
131
 
117
132
  function props(): DatabaseCommonInteractionProps {
@@ -182,6 +197,31 @@ function buildTelegramRow(): UserTelegram {
182
197
  return row;
183
198
  }
184
199
 
200
+ /*
201
+ * Born verified: a UserSlack / UserMicrosoftTeams row is a pointer at the
202
+ * owner's own OAuth workspace link, so creation is verification and there is
203
+ * no verification-code flow for these two channels at all.
204
+ */
205
+ function buildSlackRow(): UserSlack {
206
+ const row: UserSlack = new UserSlack();
207
+ row._id = ObjectID.generate().toString();
208
+ row.projectId = projectId;
209
+ row.userId = targetUserId;
210
+ row.slackUserName = RAW_SLACK_USERNAME;
211
+ row.isVerified = true;
212
+ return row;
213
+ }
214
+
215
+ function buildMicrosoftTeamsRow(): UserMicrosoftTeams {
216
+ const row: UserMicrosoftTeams = new UserMicrosoftTeams();
217
+ row._id = ObjectID.generate().toString();
218
+ row.projectId = projectId;
219
+ row.userId = targetUserId;
220
+ row.microsoftTeamsUserName = RAW_TEAMS_USERNAME;
221
+ row.isVerified = true;
222
+ return row;
223
+ }
224
+
185
225
  function buildWebhookRow(): UserWebhook {
186
226
  const row: UserWebhook = new UserWebhook();
187
227
  row._id = ObjectID.generate().toString();
@@ -284,6 +324,12 @@ beforeEach(() => {
284
324
  telegramFindBy = jest
285
325
  .spyOn(UserTelegramService, "findBy")
286
326
  .mockResolvedValue([buildTelegramRow()] as never);
327
+ slackFindBy = jest
328
+ .spyOn(UserSlackService, "findBy")
329
+ .mockResolvedValue([buildSlackRow()] as never);
330
+ microsoftTeamsFindBy = jest
331
+ .spyOn(UserMicrosoftTeamsService, "findBy")
332
+ .mockResolvedValue([buildMicrosoftTeamsRow()] as never);
287
333
  webhookFindBy = jest
288
334
  .spyOn(UserWebhookService, "findBy")
289
335
  .mockResolvedValue([buildWebhookRow()] as never);
@@ -298,6 +344,12 @@ beforeEach(() => {
298
344
  jest.spyOn(UserWhatsAppService, "findOneBy").mockResolvedValue(null as never);
299
345
  jest.spyOn(UserPushService, "findOneBy").mockResolvedValue(null as never);
300
346
  jest.spyOn(UserTelegramService, "findOneBy").mockResolvedValue(null as never);
347
+ slackFindOneBy = jest
348
+ .spyOn(UserSlackService, "findOneBy")
349
+ .mockResolvedValue(buildSlackRow() as never);
350
+ microsoftTeamsFindOneBy = jest
351
+ .spyOn(UserMicrosoftTeamsService, "findOneBy")
352
+ .mockResolvedValue(buildMicrosoftTeamsRow() as never);
301
353
  jest.spyOn(UserWebhookService, "findOneBy").mockResolvedValue(null as never);
302
354
 
303
355
  emailCreate = jest
@@ -336,6 +388,14 @@ beforeEach(() => {
336
388
  .spyOn(UserSmsService, "deleteOneById")
337
389
  .mockResolvedValue(undefined as never);
338
390
 
391
+ slackDelete = jest
392
+ .spyOn(UserSlackService, "deleteOneById")
393
+ .mockResolvedValue(undefined as never);
394
+
395
+ microsoftTeamsDelete = jest
396
+ .spyOn(UserMicrosoftTeamsService, "deleteOneById")
397
+ .mockResolvedValue(undefined as never);
398
+
339
399
  smsResend = jest
340
400
  .spyOn(UserSmsService, "resendVerificationCode")
341
401
  .mockResolvedValue(undefined as never);
@@ -368,7 +428,7 @@ describe("listing", () => {
368
428
  userId: targetUserId,
369
429
  });
370
430
 
371
- expect(methods).toHaveLength(5);
431
+ expect(methods).toHaveLength(7);
372
432
 
373
433
  /*
374
434
  * The real maskIdentifier, not a stub, so these are the exact shapes an
@@ -383,6 +443,18 @@ describe("listing", () => {
383
443
  expect(
384
444
  viewFor(methods, ReadinessMethodType.Telegram).maskedIdentifier,
385
445
  ).toBe(`@ja${IDENTIFIER_MASK}`);
446
+ expect(viewFor(methods, ReadinessMethodType.Slack).maskedIdentifier).toBe(
447
+ `op${IDENTIFIER_MASK}`,
448
+ );
449
+ expect(
450
+ viewFor(methods, ReadinessMethodType.MicrosoftTeams).maskedIdentifier,
451
+ ).toBe(`Ja${IDENTIFIER_MASK}`);
452
+
453
+ // Born verified: the OAuth workspace link IS the verification.
454
+ expect(viewFor(methods, ReadinessMethodType.Slack).isVerified).toBe(true);
455
+ expect(
456
+ viewFor(methods, ReadinessMethodType.MicrosoftTeams).isVerified,
457
+ ).toBe(true);
386
458
 
387
459
  /*
388
460
  * Scanned as a whole rather than field by field, so a view that grew a raw
@@ -419,6 +491,19 @@ describe("listing", () => {
419
491
  expect(telegramSelect.telegramChatId).toBeUndefined();
420
492
  expect(telegramSelect.telegramUserHandle).toBe(true);
421
493
 
494
+ /*
495
+ * Same rule for the two workspace channels: the Slack member id and the
496
+ * Teams user id are the addressable targets the workspace bot sends to,
497
+ * and the username is the label the owner recognises.
498
+ */
499
+ const slackSelect: any = slackFindBy.mock.calls[0]![0].select;
500
+ expect(slackSelect.slackUserId).toBeUndefined();
501
+ expect(slackSelect.slackUserName).toBe(true);
502
+
503
+ const teamsSelect: any = microsoftTeamsFindBy.mock.calls[0]![0].select;
504
+ expect(teamsSelect.microsoftTeamsUserId).toBeUndefined();
505
+ expect(teamsSelect.microsoftTeamsUserName).toBe(true);
506
+
422
507
  // Verification codes are live account-takeover material. Never selected.
423
508
  for (const spy of [emailFindBy, smsFindBy, callFindBy, whatsAppFindBy]) {
424
509
  const select: any = spy.mock.calls[0]![0].select;
@@ -432,7 +517,14 @@ describe("listing", () => {
432
517
  userId: targetUserId,
433
518
  });
434
519
 
435
- for (const spy of [emailFindBy, smsFindBy, telegramFindBy, webhookFindBy]) {
520
+ for (const spy of [
521
+ emailFindBy,
522
+ smsFindBy,
523
+ telegramFindBy,
524
+ slackFindBy,
525
+ microsoftTeamsFindBy,
526
+ webhookFindBy,
527
+ ]) {
436
528
  const call: any = spy.mock.calls[0]![0];
437
529
 
438
530
  expect(call.query.projectId.toString()).toBe(projectId.toString());
@@ -447,7 +539,7 @@ describe("listing", () => {
447
539
  }
448
540
  });
449
541
 
450
- test("marks the three channels an administrator cannot create", async () => {
542
+ test("marks the five channels an administrator cannot create", async () => {
451
543
  const methods: Array<AdminNotificationMethodView> =
452
544
  await UserNotificationMethodAdminService.listMethodsForUser({
453
545
  projectId: projectId,
@@ -465,6 +557,12 @@ describe("listing", () => {
465
557
  expect(viewFor(methods, ReadinessMethodType.Telegram).isAdminAddable).toBe(
466
558
  false,
467
559
  );
560
+ expect(viewFor(methods, ReadinessMethodType.Slack).isAdminAddable).toBe(
561
+ false,
562
+ );
563
+ expect(
564
+ viewFor(methods, ReadinessMethodType.MicrosoftTeams).isAdminAddable,
565
+ ).toBe(false);
468
566
  expect(viewFor(methods, ReadinessMethodType.Webhook).isAdminAddable).toBe(
469
567
  false,
470
568
  );
@@ -509,10 +607,12 @@ describe("adding", () => {
509
607
  expect(smsCreate.mock.calls[0]![0].props.isRoot).toBe(true);
510
608
  });
511
609
 
512
- test("refuses the three channels that cannot be verified by their owner", async () => {
610
+ test("refuses the five channels the owner has to add themselves", async () => {
513
611
  for (const methodType of [
514
612
  "Push",
515
613
  "Telegram",
614
+ "Slack",
615
+ "Microsoft Teams",
516
616
  "Webhook",
517
617
  "Carrier Pigeon",
518
618
  ]) {
@@ -763,6 +863,94 @@ describe("removing", () => {
763
863
  expectNothingRawIn(mail.vars);
764
864
  });
765
865
 
866
+ test("deletes a Slack account through its own service, and the mail carries only the mask", async () => {
867
+ /*
868
+ * Delete is the one verb an administrator gets on the workspace channels:
869
+ * they cannot add one (the OAuth link is the owner's) and there is no code
870
+ * to resend, but a leaver's Slack account still has to be clearable.
871
+ */
872
+ const slackMethodId: ObjectID = ObjectID.generate();
873
+
874
+ await UserNotificationMethodAdminService.deleteMethodForUser({
875
+ projectId: projectId,
876
+ targetUserId: targetUserId,
877
+ actorUserId: actorUserId,
878
+ methodType: "Slack",
879
+ methodId: slackMethodId,
880
+ props: props(),
881
+ });
882
+
883
+ expect(slackDelete).toHaveBeenCalled();
884
+ expect(slackDelete.mock.calls[0]![0].id.toString()).toBe(
885
+ slackMethodId.toString(),
886
+ );
887
+
888
+ // The ownership lookup is scoped to the project and the user, like SMS.
889
+ const lookup: any = slackFindOneBy.mock.calls[0]![0];
890
+ expect(lookup.query._id.toString()).toBe(slackMethodId.toString());
891
+ expect(lookup.query.projectId.toString()).toBe(projectId.toString());
892
+ expect(lookup.query.userId.toString()).toBe(targetUserId.toString());
893
+
894
+ await new Promise<void>((resolve: () => void): void => {
895
+ setTimeout(resolve, 0);
896
+ });
897
+
898
+ const mail: any = mailSpy.mock.calls[0]![0];
899
+
900
+ expect(mail.subject).toContain("removed");
901
+ expect(mail.vars.message).toContain(`op${IDENTIFIER_MASK}`);
902
+ expectNothingRawIn(mail.vars);
903
+ });
904
+
905
+ test("deletes a Microsoft Teams account through its own service", async () => {
906
+ const teamsMethodId: ObjectID = ObjectID.generate();
907
+
908
+ await UserNotificationMethodAdminService.deleteMethodForUser({
909
+ projectId: projectId,
910
+ targetUserId: targetUserId,
911
+ actorUserId: actorUserId,
912
+ methodType: "Microsoft Teams",
913
+ methodId: teamsMethodId,
914
+ props: props(),
915
+ });
916
+
917
+ expect(microsoftTeamsDelete).toHaveBeenCalled();
918
+ expect(microsoftTeamsDelete.mock.calls[0]![0].id.toString()).toBe(
919
+ teamsMethodId.toString(),
920
+ );
921
+
922
+ const lookup: any = microsoftTeamsFindOneBy.mock.calls[0]![0];
923
+ expect(lookup.query._id.toString()).toBe(teamsMethodId.toString());
924
+ expect(lookup.query.projectId.toString()).toBe(projectId.toString());
925
+ expect(lookup.query.userId.toString()).toBe(targetUserId.toString());
926
+
927
+ await new Promise<void>((resolve: () => void): void => {
928
+ setTimeout(resolve, 0);
929
+ });
930
+
931
+ const mail: any = mailSpy.mock.calls[0]![0];
932
+
933
+ expect(mail.vars.message).toContain(`Ja${IDENTIFIER_MASK}`);
934
+ expectNothingRawIn(mail.vars);
935
+ });
936
+
937
+ test("refuses a Slack method that belongs to somebody else", async () => {
938
+ slackFindOneBy.mockResolvedValue(null as never);
939
+
940
+ await expect(
941
+ UserNotificationMethodAdminService.deleteMethodForUser({
942
+ projectId: projectId,
943
+ targetUserId: targetUserId,
944
+ actorUserId: actorUserId,
945
+ methodType: "Slack",
946
+ methodId: ObjectID.generate(),
947
+ props: props(),
948
+ }),
949
+ ).rejects.toThrow(/could not be found for this user/);
950
+
951
+ expect(slackDelete).not.toHaveBeenCalled();
952
+ });
953
+
766
954
  test("refuses a method that belongs to somebody else", async () => {
767
955
  /*
768
956
  * The row is looked up by (id, projectId, userId) together, so a row
@@ -906,6 +1094,19 @@ describe("resending a verification code", () => {
906
1094
  ).rejects.toThrow(/do not use a verification code/);
907
1095
  });
908
1096
 
1097
+ test("refuses the workspace channels - the OAuth link is the verification, there is no code", async () => {
1098
+ for (const methodType of ["Slack", "Microsoft Teams"]) {
1099
+ await expect(
1100
+ UserNotificationMethodAdminService.resendVerificationCodeForUser({
1101
+ projectId: projectId,
1102
+ targetUserId: targetUserId,
1103
+ methodType: methodType,
1104
+ methodId: ObjectID.generate(),
1105
+ }),
1106
+ ).rejects.toThrow(/do not use a verification code/);
1107
+ }
1108
+ });
1109
+
909
1110
  test("refuses a method that is not this user's", async () => {
910
1111
  smsFindOneBy.mockResolvedValue(null as never);
911
1112