@oneuptime/common 12.0.18 → 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 (206) 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/EnterpriseLicenseService.ts +60 -0
  20. package/Server/Services/Index.ts +6 -2
  21. package/Server/Services/LlmLogService.ts +25 -9
  22. package/Server/Services/OnCallReadinessService.ts +86 -2
  23. package/Server/Services/ProjectService.ts +67 -0
  24. package/Server/Services/UserMicrosoftTeamsService.ts +208 -0
  25. package/Server/Services/UserNotificationMethodAdminService.ts +167 -5
  26. package/Server/Services/UserNotificationRuleAdminService.ts +40 -6
  27. package/Server/Services/UserNotificationRuleService.ts +589 -11
  28. package/Server/Services/UserNotificationSettingService.ts +138 -0
  29. package/Server/Services/UserService.ts +28 -0
  30. package/Server/Services/UserSlackService.ts +218 -0
  31. package/Server/Services/WorkspaceProjectAuthTokenService.ts +67 -1
  32. package/Server/Services/WorkspaceUserAuthTokenService.ts +73 -0
  33. package/Server/Services/WorkspaceUserNotificationService.ts +190 -0
  34. package/Server/Utils/Marketing/MarketingEventUtil.ts +145 -0
  35. package/Server/Utils/Marketing/MarketingEventWebhook.ts +114 -0
  36. package/Server/Utils/Monitor/MonitorLogUtil.ts +14 -1
  37. package/Server/Utils/Monitor/MonitorPayloadRedaction.ts +321 -0
  38. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +191 -4
  39. package/Tests/App/Dashboard/AdminNotificationRulesPage.test.tsx +22 -14
  40. package/Tests/App/Dashboard/AdminUserNotificationMethodsPage.test.tsx +22 -11
  41. package/Tests/App/Dashboard/OnCallPreventionGuards.test.tsx +37 -1
  42. package/Tests/App/Dashboard/OnCallRulesTable.test.tsx +18 -10
  43. package/Tests/Models/DatabaseModels/DomainRoleTierCoverage.test.ts +27 -1
  44. package/Tests/Models/DatabaseModels/MonitorSecretKeyColumnAccessControl.test.ts +214 -0
  45. package/Tests/Server/API/OnCallReadinessAPI.test.ts +145 -26
  46. package/Tests/Server/API/UserNotificationMethodAdminAPI.test.ts +2 -2
  47. package/Tests/Server/Services/AIServiceDailyBudget.test.ts +11 -1
  48. package/Tests/Server/Services/AdminRuleEditGuards.test.ts +22 -8
  49. package/Tests/Server/Services/DeliverNotificationForRuleExtraction.test.ts +18 -2
  50. package/Tests/Server/Services/EpisodeRuleSeverityRepair.test.ts +5 -1
  51. package/Tests/Server/Services/LlmLogServiceTokenAggregateParams.test.ts +640 -0
  52. package/Tests/Server/Services/NotificationChannelEventCoverage.test.ts +246 -22
  53. package/Tests/Server/Services/NotificationDeletionImpact.test.ts +136 -8
  54. package/Tests/Server/Services/OnCallNotificationFallback.test.ts +191 -8
  55. package/Tests/Server/Services/OnCallReadinessService.test.ts +343 -18
  56. package/Tests/Server/Services/SeverityRuleBackfill.test.ts +5 -1
  57. package/Tests/Server/Services/UserMicrosoftTeamsService.test.ts +343 -0
  58. package/Tests/Server/Services/UserNotificationMethodAdminService.test.ts +209 -8
  59. package/Tests/Server/Services/UserNotificationRuleAdminGuards.test.ts +22 -6
  60. package/Tests/Server/Services/UserNotificationRuleDefaultCreation.test.ts +12 -6
  61. package/Tests/Server/Services/UserNotificationRuleExecuteItem.test.ts +37 -3
  62. package/Tests/Server/Services/UserNotificationRuleWorkspaceDelivery.test.ts +669 -0
  63. package/Tests/Server/Services/UserNotificationSettingWorkspaceChannels.test.ts +410 -0
  64. package/Tests/Server/Services/UserSlackService.test.ts +407 -0
  65. package/Tests/Server/Services/WorkspaceProjectAuthTokenDisconnectCascade.test.ts +224 -0
  66. package/Tests/Server/Services/WorkspaceUserAuthTokenNotificationMethodCascade.test.ts +189 -0
  67. package/Tests/Server/Services/WorkspaceUserNotificationService.test.ts +496 -0
  68. package/Tests/Server/Types/Database/Permissions/AdminNotificationRuleAccess.test.ts +30 -17
  69. package/Tests/Server/Types/Database/Permissions/CreateOwnershipScoping.test.ts +8 -3
  70. package/Tests/Server/Types/Database/Permissions/OwnerOnlyColumns.test.ts +72 -3
  71. package/Tests/Server/Types/Database/Permissions/UserNotificationRuleScoping.test.ts +22 -18
  72. package/Tests/Server/Types/Database/Permissions/WorkspaceMethodStampedColumnCreate.test.ts +178 -0
  73. package/Tests/Server/Utils/Marketing/MarketingEventUtil.test.ts +259 -0
  74. package/Tests/Server/Utils/Marketing/MarketingEventWebhook.test.ts +257 -0
  75. package/Tests/Server/Utils/Monitor/MonitorLogUtilRedaction.test.ts +300 -0
  76. package/Tests/Server/Utils/Monitor/MonitorPayloadRedaction.test.ts +381 -0
  77. package/Tests/Server/Utils/Runbook/RunbookExecutePermission.test.ts +228 -0
  78. package/Tests/Server/Utils/SessionReplay/SessionReplayErasureTombstone.test.ts +189 -0
  79. package/Tests/Server/Utils/SessionReplay/SessionReplayUsage.test.ts +205 -0
  80. package/Tests/Server/Utils/Telemetry/AppMetrics.test.ts +275 -0
  81. package/Tests/Server/Utils/Telemetry/TelemetryContext.test.ts +279 -0
  82. package/Tests/Server/Utils/Workspace/MicrosoftTeamsDirectMessage.test.ts +454 -0
  83. package/Tests/UI/Utils/NotificationMethodUtil.test.ts +137 -9
  84. package/Tests/UI/Utils/PermissionGate.test.ts +105 -0
  85. package/Types/Marketing/MarketingEvent.ts +97 -0
  86. package/UI/Utils/NotificationMethodUtil.ts +41 -1
  87. package/UI/Utils/PermissionGate.ts +56 -0
  88. package/build/dist/Models/DatabaseModels/EnterpriseLicense.js +24 -0
  89. package/build/dist/Models/DatabaseModels/EnterpriseLicense.js.map +1 -1
  90. package/build/dist/Models/DatabaseModels/Index.js +4 -2
  91. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  92. package/build/dist/Models/DatabaseModels/Monitor.js +39 -9
  93. package/build/dist/Models/DatabaseModels/Monitor.js.map +1 -1
  94. package/build/dist/Models/DatabaseModels/UserMicrosoftTeams.js +363 -0
  95. package/build/dist/Models/DatabaseModels/UserMicrosoftTeams.js.map +1 -0
  96. package/build/dist/Models/DatabaseModels/UserNotificationRule.js +145 -8
  97. package/build/dist/Models/DatabaseModels/UserNotificationRule.js.map +1 -1
  98. package/build/dist/Models/DatabaseModels/UserNotificationSetting.js +38 -0
  99. package/build/dist/Models/DatabaseModels/UserNotificationSetting.js.map +1 -1
  100. package/build/dist/Models/DatabaseModels/UserOnCallLogTimeline.js +96 -0
  101. package/build/dist/Models/DatabaseModels/UserOnCallLogTimeline.js.map +1 -1
  102. package/build/dist/Models/DatabaseModels/UserSlack.js +361 -0
  103. package/build/dist/Models/DatabaseModels/UserSlack.js.map +1 -0
  104. package/build/dist/Models/DatabaseModels/WorkspaceProjectAuthToken.js.map +1 -1
  105. package/build/dist/Server/API/UserMicrosoftTeamsAPI.js +82 -0
  106. package/build/dist/Server/API/UserMicrosoftTeamsAPI.js.map +1 -0
  107. package/build/dist/Server/API/UserSlackAPI.js +81 -0
  108. package/build/dist/Server/API/UserSlackAPI.js.map +1 -0
  109. package/build/dist/Server/EnvironmentConfig.js +10 -70
  110. package/build/dist/Server/EnvironmentConfig.js.map +1 -1
  111. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788700000000-RemoveMarketingConversionUploadState.js +26 -0
  112. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788700000000-RemoveMarketingConversionUploadState.js.map +1 -0
  113. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788800000000-DropMarketingConversionAddEnterpriseLicenseEmail.js +37 -0
  114. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788800000000-DropMarketingConversionAddEnterpriseLicenseEmail.js.map +1 -0
  115. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788900000000-RedactStoredMonitorIngestSecrets.js +106 -0
  116. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788900000000-RedactStoredMonitorIngestSecrets.js.map +1 -0
  117. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789000000000-AddUserSlackAndMicrosoftTeams.js +78 -0
  118. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789000000000-AddUserSlackAndMicrosoftTeams.js.map +1 -0
  119. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +8 -0
  120. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  121. package/build/dist/Server/Infrastructure/Queue.js +7 -0
  122. package/build/dist/Server/Infrastructure/Queue.js.map +1 -1
  123. package/build/dist/Server/Services/EnterpriseLicenseService.js +65 -0
  124. package/build/dist/Server/Services/EnterpriseLicenseService.js.map +1 -1
  125. package/build/dist/Server/Services/Index.js +6 -2
  126. package/build/dist/Server/Services/Index.js.map +1 -1
  127. package/build/dist/Server/Services/LlmLogService.js +25 -9
  128. package/build/dist/Server/Services/LlmLogService.js.map +1 -1
  129. package/build/dist/Server/Services/OnCallReadinessService.js +74 -2
  130. package/build/dist/Server/Services/OnCallReadinessService.js.map +1 -1
  131. package/build/dist/Server/Services/ProjectService.js +56 -0
  132. package/build/dist/Server/Services/ProjectService.js.map +1 -1
  133. package/build/dist/Server/Services/UserMicrosoftTeamsService.js +183 -0
  134. package/build/dist/Server/Services/UserMicrosoftTeamsService.js.map +1 -0
  135. package/build/dist/Server/Services/UserNotificationMethodAdminService.js +138 -2
  136. package/build/dist/Server/Services/UserNotificationMethodAdminService.js.map +1 -1
  137. package/build/dist/Server/Services/UserNotificationRuleAdminService.js +32 -4
  138. package/build/dist/Server/Services/UserNotificationRuleAdminService.js.map +1 -1
  139. package/build/dist/Server/Services/UserNotificationRuleService.js +499 -67
  140. package/build/dist/Server/Services/UserNotificationRuleService.js.map +1 -1
  141. package/build/dist/Server/Services/UserNotificationSettingService.js +118 -0
  142. package/build/dist/Server/Services/UserNotificationSettingService.js.map +1 -1
  143. package/build/dist/Server/Services/UserService.js +26 -1
  144. package/build/dist/Server/Services/UserService.js.map +1 -1
  145. package/build/dist/Server/Services/UserSlackService.js +195 -0
  146. package/build/dist/Server/Services/UserSlackService.js.map +1 -0
  147. package/build/dist/Server/Services/WorkspaceProjectAuthTokenService.js +64 -1
  148. package/build/dist/Server/Services/WorkspaceProjectAuthTokenService.js.map +1 -1
  149. package/build/dist/Server/Services/WorkspaceUserAuthTokenService.js +70 -0
  150. package/build/dist/Server/Services/WorkspaceUserAuthTokenService.js.map +1 -1
  151. package/build/dist/Server/Services/WorkspaceUserNotificationService.js +129 -0
  152. package/build/dist/Server/Services/WorkspaceUserNotificationService.js.map +1 -0
  153. package/build/dist/Server/Utils/Marketing/MarketingEventUtil.js +94 -0
  154. package/build/dist/Server/Utils/Marketing/MarketingEventUtil.js.map +1 -0
  155. package/build/dist/Server/Utils/Marketing/MarketingEventWebhook.js +93 -0
  156. package/build/dist/Server/Utils/Marketing/MarketingEventWebhook.js.map +1 -0
  157. package/build/dist/Server/Utils/Monitor/MonitorLogUtil.js +12 -1
  158. package/build/dist/Server/Utils/Monitor/MonitorLogUtil.js.map +1 -1
  159. package/build/dist/Server/Utils/Monitor/MonitorPayloadRedaction.js +233 -0
  160. package/build/dist/Server/Utils/Monitor/MonitorPayloadRedaction.js.map +1 -0
  161. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +141 -4
  162. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
  163. package/build/dist/Types/Marketing/MarketingEvent.js +43 -0
  164. package/build/dist/Types/Marketing/MarketingEvent.js.map +1 -0
  165. package/build/dist/UI/Utils/NotificationMethodUtil.js +33 -1
  166. package/build/dist/UI/Utils/NotificationMethodUtil.js.map +1 -1
  167. package/build/dist/UI/Utils/PermissionGate.js +32 -0
  168. package/build/dist/UI/Utils/PermissionGate.js.map +1 -1
  169. package/package.json +1 -1
  170. package/Models/DatabaseModels/MarketingConversion.ts +0 -410
  171. package/Server/Services/MarketingConversionService.ts +0 -10
  172. package/Server/Utils/Marketing/ConversionUploadProvider.ts +0 -215
  173. package/Server/Utils/Marketing/ConversionUploadProviders.ts +0 -22
  174. package/Server/Utils/Marketing/Providers/GoogleAds.ts +0 -333
  175. package/Server/Utils/Marketing/Providers/LinkedIn.ts +0 -185
  176. package/Server/Utils/Marketing/Providers/Meta.ts +0 -182
  177. package/Server/Utils/Marketing/Providers/MicrosoftAds.ts +0 -223
  178. package/Server/Utils/Marketing/Providers/Reddit.ts +0 -208
  179. package/Tests/Server/Utils/Marketing/AdUploadableConversionTypes.test.ts +0 -315
  180. package/Tests/Server/Utils/Marketing/ConversionUploadProvider.test.ts +0 -300
  181. package/Tests/Server/Utils/Marketing/GoogleAds.test.ts +0 -592
  182. package/Tests/Server/Utils/Marketing/LinkedIn.test.ts +0 -282
  183. package/Tests/Server/Utils/Marketing/Meta.test.ts +0 -304
  184. package/Tests/Server/Utils/Marketing/MicrosoftAds.test.ts +0 -263
  185. package/Tests/Server/Utils/Marketing/Reddit.test.ts +0 -259
  186. package/Types/Marketing/MarketingConversion.ts +0 -53
  187. package/build/dist/Models/DatabaseModels/MarketingConversion.js +0 -445
  188. package/build/dist/Models/DatabaseModels/MarketingConversion.js.map +0 -1
  189. package/build/dist/Server/Services/MarketingConversionService.js +0 -9
  190. package/build/dist/Server/Services/MarketingConversionService.js.map +0 -1
  191. package/build/dist/Server/Utils/Marketing/ConversionUploadProvider.js +0 -93
  192. package/build/dist/Server/Utils/Marketing/ConversionUploadProvider.js.map +0 -1
  193. package/build/dist/Server/Utils/Marketing/ConversionUploadProviders.js +0 -20
  194. package/build/dist/Server/Utils/Marketing/ConversionUploadProviders.js.map +0 -1
  195. package/build/dist/Server/Utils/Marketing/Providers/GoogleAds.js +0 -221
  196. package/build/dist/Server/Utils/Marketing/Providers/GoogleAds.js.map +0 -1
  197. package/build/dist/Server/Utils/Marketing/Providers/LinkedIn.js +0 -137
  198. package/build/dist/Server/Utils/Marketing/Providers/LinkedIn.js.map +0 -1
  199. package/build/dist/Server/Utils/Marketing/Providers/Meta.js +0 -133
  200. package/build/dist/Server/Utils/Marketing/Providers/Meta.js.map +0 -1
  201. package/build/dist/Server/Utils/Marketing/Providers/MicrosoftAds.js +0 -143
  202. package/build/dist/Server/Utils/Marketing/Providers/MicrosoftAds.js.map +0 -1
  203. package/build/dist/Server/Utils/Marketing/Providers/Reddit.js +0 -145
  204. package/build/dist/Server/Utils/Marketing/Providers/Reddit.js.map +0 -1
  205. package/build/dist/Types/Marketing/MarketingConversion.js +0 -52
  206. package/build/dist/Types/Marketing/MarketingConversion.js.map +0 -1
@@ -0,0 +1,454 @@
1
+ import { describe, expect, test, afterEach, beforeEach } from "@jest/globals";
2
+
3
+ /*
4
+ * Tests for the Microsoft Teams DIRECT MESSAGE path —
5
+ * MicrosoftTeamsUtil.sendDirectMessageToUserAsBot, the sender behind the
6
+ * "Microsoft Teams" user notification method.
7
+ *
8
+ * The problem it solves, and the reason it exists next to the older
9
+ * sendDirectMessageToUser: WorkspaceUserAuthToken stores the user's Microsoft
10
+ * ENTRA OBJECT ID, but the Graph-based helper posts to /chats/{chatId} and so
11
+ * needs a CHAT id — the two id spaces never meet. This method resolves a
12
+ * deliverable conversation from the Entra id instead, two ways:
13
+ *
14
+ * 1. REUSE. A personal chat previously captured from a bot activity (whose
15
+ * roster contained this Entra id) is known-deliverable and carries the
16
+ * regional serviceUrl, so it is preferred and delivered through the
17
+ * existing sendAdaptiveCardToChat path.
18
+ *
19
+ * 2. CREATE. Otherwise Bot Framework createConversation is asked to resolve
20
+ * (or create) the 1:1 conversation from the member's Entra id — which
21
+ * works exactly when the OneUptime app is installed for that user, so
22
+ * Microsoft's opaque roster rejection is translated into "install the
23
+ * OneUptime app" before anybody reads it off an on-call timeline.
24
+ */
25
+
26
+ jest.mock("../../../../Server/EnvironmentConfig", () => {
27
+ return {
28
+ ...(jest.requireActual("../../../../Server/EnvironmentConfig") as Record<
29
+ string,
30
+ unknown
31
+ >),
32
+ MicrosoftTeamsAppClientId: "11111111-2222-3333-4444-555555555555",
33
+ MicrosoftTeamsAppClientSecret: "test-secret",
34
+ MicrosoftTeamsAppTenantId: "test-tenant",
35
+ };
36
+ });
37
+
38
+ jest.mock("botbuilder", () => {
39
+ return {
40
+ CloudAdapter: class CloudAdapter {},
41
+ ConfigurationBotFrameworkAuthentication: class ConfigurationBotFrameworkAuthentication {},
42
+ TeamsActivityHandler: class TeamsActivityHandler {},
43
+ TurnContext: class TurnContext {},
44
+ ActivityHandler: class ActivityHandler {},
45
+ MessageFactory: {
46
+ text: jest.fn(),
47
+ attachment: jest.fn((attachment: unknown) => {
48
+ return { type: "message", attachments: [attachment] };
49
+ }),
50
+ },
51
+ CardFactory: { heroCard: jest.fn() },
52
+ TeamsInfo: {
53
+ getMembers: jest.fn(),
54
+ getPagedMembers: jest.fn(),
55
+ },
56
+ };
57
+ });
58
+
59
+ import MicrosoftTeamsUtil from "../../../../Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams";
60
+ import WorkspaceProjectAuthTokenService from "../../../../Server/Services/WorkspaceProjectAuthTokenService";
61
+ import WorkspaceProjectAuthToken, {
62
+ MicrosoftTeamsChat,
63
+ MicrosoftTeamsMiscData,
64
+ } from "../../../../Models/DatabaseModels/WorkspaceProjectAuthToken";
65
+ import logger from "../../../../Server/Utils/Logger";
66
+ import ObjectID from "../../../../Types/ObjectID";
67
+ import BadDataException from "../../../../Types/Exception/BadDataException";
68
+ import {
69
+ WorkspaceMessageBlock,
70
+ WorkspacePayloadMarkdown,
71
+ } from "../../../../Types/Workspace/WorkspaceMessagePayload";
72
+ import type { ConversationParameters, TurnContext } from "botbuilder";
73
+
74
+ const MOCK_APP_CLIENT_ID: string = "11111111-2222-3333-4444-555555555555";
75
+ const PROJECT_ID: ObjectID = new ObjectID(
76
+ "11111111-1111-4111-8111-111111111111",
77
+ );
78
+ const TENANT_ID: string = "tenant-xyz";
79
+ const ENTRA_OBJECT_ID: string = "e6f1c1f7-aad0-4b6c-9c11-2f5b7c8d9e0f";
80
+ const PERSONAL_CHAT_ID: string = "a:1personalchat";
81
+ const DEFAULT_SERVICE_URL: string = "https://smba.trafficmanager.net/teams/";
82
+ const REGIONAL_SERVICE_URL: string = "https://smba.emea.teams.microsoft.com/";
83
+ const ROSTER_ERROR_TEXT: string =
84
+ "The bot is not part of the conversation roster.";
85
+
86
+ function markdownBlocks(): Array<WorkspaceMessageBlock> {
87
+ const block: WorkspacePayloadMarkdown = {
88
+ _type: "WorkspacePayloadMarkdown",
89
+ text: "**incident** page body",
90
+ };
91
+ return [block];
92
+ }
93
+
94
+ function personalChat(
95
+ overrides: Partial<MicrosoftTeamsChat> = {},
96
+ ): MicrosoftTeamsChat {
97
+ return {
98
+ id: PERSONAL_CHAT_ID,
99
+ name: "Alice Example",
100
+ chatType: "personal",
101
+ serviceUrl: REGIONAL_SERVICE_URL,
102
+ memberAadObjectIds: [ENTRA_OBJECT_ID],
103
+ ...overrides,
104
+ } as MicrosoftTeamsChat;
105
+ }
106
+
107
+ function miscData(
108
+ overrides: Partial<MicrosoftTeamsMiscData> = {},
109
+ ): MicrosoftTeamsMiscData {
110
+ return {
111
+ tenantId: TENANT_ID,
112
+ teamId: "team-1",
113
+ teamName: "Engineering",
114
+ botId: "bot-1",
115
+ ...overrides,
116
+ } as MicrosoftTeamsMiscData;
117
+ }
118
+
119
+ function mockProjectAuth(
120
+ overrides: Partial<MicrosoftTeamsMiscData> = {},
121
+ ): jest.SpyInstance {
122
+ return jest
123
+ .spyOn(WorkspaceProjectAuthTokenService, "getProjectAuth")
124
+ .mockResolvedValue({
125
+ id: PROJECT_ID,
126
+ workspaceProjectId: TENANT_ID,
127
+ miscData: miscData(overrides),
128
+ } as unknown as WorkspaceProjectAuthToken as never);
129
+ }
130
+
131
+ interface FakeCreateAdapter {
132
+ createConversationAsync: jest.Mock;
133
+ capturedParams: Array<ConversationParameters>;
134
+ capturedServiceUrls: Array<string>;
135
+ sentActivities: Array<unknown>;
136
+ }
137
+
138
+ function installFakeCreateAdapter(options?: {
139
+ createError?: unknown;
140
+ }): FakeCreateAdapter {
141
+ const capturedParams: Array<ConversationParameters> = [];
142
+ const capturedServiceUrls: Array<string> = [];
143
+ const sentActivities: Array<unknown> = [];
144
+
145
+ const createConversationAsync: jest.Mock = jest.fn(
146
+ async (
147
+ _appId: string,
148
+ _channelId: string,
149
+ serviceUrl: string,
150
+ _audience: string,
151
+ conversationParameters: ConversationParameters,
152
+ logic: (context: TurnContext) => Promise<void>,
153
+ ): Promise<void> => {
154
+ capturedServiceUrls.push(serviceUrl);
155
+ capturedParams.push(conversationParameters);
156
+
157
+ if (options && "createError" in options) {
158
+ throw options.createError;
159
+ }
160
+
161
+ const fakeContext: TurnContext = {
162
+ sendActivity: async (activity: unknown): Promise<unknown> => {
163
+ sentActivities.push(activity);
164
+ return { id: "msg-123" };
165
+ },
166
+ } as unknown as TurnContext;
167
+
168
+ await logic(fakeContext);
169
+ },
170
+ );
171
+
172
+ jest
173
+ .spyOn(MicrosoftTeamsUtil as any, "getBotAdapter")
174
+ .mockReturnValue({ createConversationAsync: createConversationAsync });
175
+
176
+ return {
177
+ createConversationAsync: createConversationAsync,
178
+ capturedParams: capturedParams,
179
+ capturedServiceUrls: capturedServiceUrls,
180
+ sentActivities: sentActivities,
181
+ };
182
+ }
183
+
184
+ describe("MicrosoftTeamsUtil.sendDirectMessageToUserAsBot", () => {
185
+ let sendToChat: jest.SpyInstance;
186
+
187
+ beforeEach(() => {
188
+ jest.spyOn(logger, "error").mockImplementation((): void => {
189
+ return undefined;
190
+ });
191
+ jest.spyOn(logger, "debug").mockImplementation((): void => {
192
+ return undefined;
193
+ });
194
+
195
+ sendToChat = jest
196
+ .spyOn(MicrosoftTeamsUtil, "sendAdaptiveCardToChat")
197
+ .mockResolvedValue({
198
+ channel: {
199
+ id: PERSONAL_CHAT_ID,
200
+ name: "Alice Example",
201
+ workspaceType: "MicrosoftTeams",
202
+ },
203
+ threadId: "msg-1",
204
+ } as never);
205
+ });
206
+
207
+ afterEach(() => {
208
+ jest.restoreAllMocks();
209
+ });
210
+
211
+ /*
212
+ * ----------------------------------------------------------------------- *
213
+ * (A) Reusing a captured personal chat.
214
+ * -----------------------------------------------------------------------
215
+ */
216
+
217
+ describe("reusing a captured personal chat", () => {
218
+ test("a personal chat whose roster carries the Entra id is delivered through sendAdaptiveCardToChat", async () => {
219
+ mockProjectAuth({
220
+ availableChats: { [PERSONAL_CHAT_ID]: personalChat() },
221
+ });
222
+ const adapter: FakeCreateAdapter = installFakeCreateAdapter();
223
+
224
+ await MicrosoftTeamsUtil.sendDirectMessageToUserAsBot({
225
+ projectId: PROJECT_ID,
226
+ workspaceUserId: ENTRA_OBJECT_ID,
227
+ messageBlocks: markdownBlocks(),
228
+ });
229
+
230
+ expect(sendToChat).toHaveBeenCalledTimes(1);
231
+ const arg: { chatId: string; projectId: ObjectID } = sendToChat.mock
232
+ .calls[0][0] as { chatId: string; projectId: ObjectID };
233
+ expect(arg.chatId).toBe(PERSONAL_CHAT_ID);
234
+ expect(arg.projectId.toString()).toBe(PROJECT_ID.toString());
235
+
236
+ // The Bot Framework create path is not touched at all.
237
+ expect(adapter.createConversationAsync).not.toHaveBeenCalled();
238
+ });
239
+
240
+ test("a personal chat belonging to somebody ELSE is not reused", async () => {
241
+ mockProjectAuth({
242
+ availableChats: {
243
+ [PERSONAL_CHAT_ID]: personalChat({
244
+ memberAadObjectIds: ["some-other-user"],
245
+ }),
246
+ },
247
+ });
248
+ installFakeCreateAdapter();
249
+
250
+ await MicrosoftTeamsUtil.sendDirectMessageToUserAsBot({
251
+ projectId: PROJECT_ID,
252
+ workspaceUserId: ENTRA_OBJECT_ID,
253
+ messageBlocks: markdownBlocks(),
254
+ });
255
+
256
+ expect(sendToChat).not.toHaveBeenCalled();
257
+ });
258
+
259
+ test("a GROUP chat carrying the Entra id is not reused - a page is a direct message, not a group post", async () => {
260
+ mockProjectAuth({
261
+ availableChats: {
262
+ [PERSONAL_CHAT_ID]: personalChat({ chatType: "groupChat" }),
263
+ },
264
+ });
265
+ installFakeCreateAdapter();
266
+
267
+ await MicrosoftTeamsUtil.sendDirectMessageToUserAsBot({
268
+ projectId: PROJECT_ID,
269
+ workspaceUserId: ENTRA_OBJECT_ID,
270
+ messageBlocks: markdownBlocks(),
271
+ });
272
+
273
+ expect(sendToChat).not.toHaveBeenCalled();
274
+ });
275
+
276
+ test("a legacy personal chat with NO captured roster is not matched (absent means unknown, not mine)", async () => {
277
+ mockProjectAuth({
278
+ availableChats: {
279
+ [PERSONAL_CHAT_ID]: personalChat({ memberAadObjectIds: undefined }),
280
+ },
281
+ });
282
+ installFakeCreateAdapter();
283
+
284
+ await MicrosoftTeamsUtil.sendDirectMessageToUserAsBot({
285
+ projectId: PROJECT_ID,
286
+ workspaceUserId: ENTRA_OBJECT_ID,
287
+ messageBlocks: markdownBlocks(),
288
+ });
289
+
290
+ expect(sendToChat).not.toHaveBeenCalled();
291
+ });
292
+ });
293
+
294
+ /*
295
+ * ----------------------------------------------------------------------- *
296
+ * (B) Creating the conversation from the Entra id.
297
+ * -----------------------------------------------------------------------
298
+ */
299
+
300
+ describe("creating the 1:1 conversation via Bot Framework", () => {
301
+ test("createConversation is asked for a non-group conversation with the member's Entra id and the tenant", async () => {
302
+ mockProjectAuth();
303
+ const adapter: FakeCreateAdapter = installFakeCreateAdapter();
304
+
305
+ await MicrosoftTeamsUtil.sendDirectMessageToUserAsBot({
306
+ projectId: PROJECT_ID,
307
+ workspaceUserId: ENTRA_OBJECT_ID,
308
+ messageBlocks: markdownBlocks(),
309
+ });
310
+
311
+ expect(adapter.createConversationAsync).toHaveBeenCalledTimes(1);
312
+ const params: ConversationParameters = adapter
313
+ .capturedParams[0] as ConversationParameters;
314
+ expect(params.isGroup).toBe(false);
315
+ expect(params.members?.[0]?.id).toBe(ENTRA_OBJECT_ID);
316
+ expect(params.tenantId).toBe(TENANT_ID);
317
+ expect((params.channelData as { tenant: { id: string } }).tenant.id).toBe(
318
+ TENANT_ID,
319
+ );
320
+ // 28:<appId> is the Teams-side bot account id.
321
+ expect(params.bot?.id).toBe(`28:${MOCK_APP_CLIENT_ID}`);
322
+ });
323
+
324
+ test("the adaptive card is sent inside the created conversation", async () => {
325
+ mockProjectAuth();
326
+ const adapter: FakeCreateAdapter = installFakeCreateAdapter();
327
+
328
+ await MicrosoftTeamsUtil.sendDirectMessageToUserAsBot({
329
+ projectId: PROJECT_ID,
330
+ workspaceUserId: ENTRA_OBJECT_ID,
331
+ messageBlocks: markdownBlocks(),
332
+ });
333
+
334
+ expect(adapter.sentActivities).toHaveLength(1);
335
+ const activity: { attachments: Array<{ contentType: string }> } = adapter
336
+ .sentActivities[0] as { attachments: Array<{ contentType: string }> };
337
+ expect(activity.attachments[0]?.contentType).toBe(
338
+ "application/vnd.microsoft.card.adaptive",
339
+ );
340
+ });
341
+
342
+ test("falls back to the commercial-cloud serviceUrl when nothing regional was ever captured", async () => {
343
+ mockProjectAuth();
344
+ const adapter: FakeCreateAdapter = installFakeCreateAdapter();
345
+
346
+ await MicrosoftTeamsUtil.sendDirectMessageToUserAsBot({
347
+ projectId: PROJECT_ID,
348
+ workspaceUserId: ENTRA_OBJECT_ID,
349
+ messageBlocks: markdownBlocks(),
350
+ });
351
+
352
+ expect(adapter.capturedServiceUrls[0]).toBe(DEFAULT_SERVICE_URL);
353
+ });
354
+
355
+ test("prefers a regional serviceUrl captured on ANY chat (required for GCC/DoD)", async () => {
356
+ mockProjectAuth({
357
+ availableChats: {
358
+ "a:someoneelse": personalChat({
359
+ id: "a:someoneelse",
360
+ memberAadObjectIds: ["some-other-user"],
361
+ serviceUrl: REGIONAL_SERVICE_URL,
362
+ }),
363
+ },
364
+ });
365
+ const adapter: FakeCreateAdapter = installFakeCreateAdapter();
366
+
367
+ await MicrosoftTeamsUtil.sendDirectMessageToUserAsBot({
368
+ projectId: PROJECT_ID,
369
+ workspaceUserId: ENTRA_OBJECT_ID,
370
+ messageBlocks: markdownBlocks(),
371
+ });
372
+
373
+ expect(adapter.capturedServiceUrls[0]).toBe(REGIONAL_SERVICE_URL);
374
+ });
375
+ });
376
+
377
+ /*
378
+ * ----------------------------------------------------------------------- *
379
+ * (C) Failure translation and refusals.
380
+ * -----------------------------------------------------------------------
381
+ */
382
+
383
+ describe("failure translation and refusals", () => {
384
+ test("Microsoft's roster rejection becomes 'install the OneUptime app', the one fix that works", async () => {
385
+ mockProjectAuth();
386
+ installFakeCreateAdapter({
387
+ createError: new Error(ROSTER_ERROR_TEXT),
388
+ });
389
+
390
+ await expect(
391
+ MicrosoftTeamsUtil.sendDirectMessageToUserAsBot({
392
+ projectId: PROJECT_ID,
393
+ workspaceUserId: ENTRA_OBJECT_ID,
394
+ messageBlocks: markdownBlocks(),
395
+ }),
396
+ ).rejects.toThrow(
397
+ "The OneUptime app is not installed for this Microsoft Teams user. Ask them to add the OneUptime app in Microsoft Teams (personal scope) so the bot can message them directly.",
398
+ );
399
+ });
400
+
401
+ test("every other Bot Framework error passes through untouched", async () => {
402
+ mockProjectAuth();
403
+ installFakeCreateAdapter({
404
+ createError: new Error("throttled: too many requests"),
405
+ });
406
+
407
+ await expect(
408
+ MicrosoftTeamsUtil.sendDirectMessageToUserAsBot({
409
+ projectId: PROJECT_ID,
410
+ workspaceUserId: ENTRA_OBJECT_ID,
411
+ messageBlocks: markdownBlocks(),
412
+ }),
413
+ ).rejects.toThrow("throttled: too many requests");
414
+ });
415
+
416
+ test("a project with no Teams integration is refused before the Bot Framework is touched", async () => {
417
+ jest
418
+ .spyOn(WorkspaceProjectAuthTokenService, "getProjectAuth")
419
+ .mockResolvedValue(null as never);
420
+ const adapter: FakeCreateAdapter = installFakeCreateAdapter();
421
+
422
+ await expect(
423
+ MicrosoftTeamsUtil.sendDirectMessageToUserAsBot({
424
+ projectId: PROJECT_ID,
425
+ workspaceUserId: ENTRA_OBJECT_ID,
426
+ messageBlocks: markdownBlocks(),
427
+ }),
428
+ ).rejects.toThrow(
429
+ "Microsoft Teams integration not found for this project",
430
+ );
431
+
432
+ expect(adapter.createConversationAsync).not.toHaveBeenCalled();
433
+ });
434
+
435
+ test("a project auth row with no tenant id is refused", async () => {
436
+ jest
437
+ .spyOn(WorkspaceProjectAuthTokenService, "getProjectAuth")
438
+ .mockResolvedValue({
439
+ id: PROJECT_ID,
440
+ workspaceProjectId: undefined,
441
+ miscData: miscData(),
442
+ } as unknown as WorkspaceProjectAuthToken as never);
443
+ installFakeCreateAdapter();
444
+
445
+ await expect(
446
+ MicrosoftTeamsUtil.sendDirectMessageToUserAsBot({
447
+ projectId: PROJECT_ID,
448
+ workspaceUserId: ENTRA_OBJECT_ID,
449
+ messageBlocks: markdownBlocks(),
450
+ }),
451
+ ).rejects.toThrow(BadDataException);
452
+ });
453
+ });
454
+ });
@@ -7,8 +7,10 @@ import NotificationMethodUtil, {
7
7
  import { DropdownOption } from "../../../UI/Components/Dropdown/Dropdown";
8
8
  import UserCall from "../../../Models/DatabaseModels/UserCall";
9
9
  import UserEmail from "../../../Models/DatabaseModels/UserEmail";
10
+ import UserMicrosoftTeams from "../../../Models/DatabaseModels/UserMicrosoftTeams";
10
11
  import UserNotificationRule from "../../../Models/DatabaseModels/UserNotificationRule";
11
12
  import UserPush from "../../../Models/DatabaseModels/UserPush";
13
+ import UserSlack from "../../../Models/DatabaseModels/UserSlack";
12
14
  import UserSMS from "../../../Models/DatabaseModels/UserSMS";
13
15
  import UserTelegram from "../../../Models/DatabaseModels/UserTelegram";
14
16
  import UserWebhook from "../../../Models/DatabaseModels/UserWebhook";
@@ -95,6 +97,34 @@ function push(id: string, deviceName?: string): UserPush {
95
97
  return model;
96
98
  }
97
99
 
100
+ function slack(id: string, userName?: string, userId?: string): UserSlack {
101
+ const model: UserSlack = new UserSlack();
102
+ model.id = new ObjectID(id);
103
+ if (userName !== undefined) {
104
+ model.slackUserName = userName;
105
+ }
106
+ if (userId !== undefined) {
107
+ model.slackUserId = userId;
108
+ }
109
+ return model;
110
+ }
111
+
112
+ function microsoftTeams(
113
+ id: string,
114
+ userName?: string,
115
+ userId?: string,
116
+ ): UserMicrosoftTeams {
117
+ const model: UserMicrosoftTeams = new UserMicrosoftTeams();
118
+ model.id = new ObjectID(id);
119
+ if (userName !== undefined) {
120
+ model.microsoftTeamsUserName = userName;
121
+ }
122
+ if (userId !== undefined) {
123
+ model.microsoftTeamsUserId = userId;
124
+ }
125
+ return model;
126
+ }
127
+
98
128
  function emptyModels(): NotificationMethodModels {
99
129
  return {
100
130
  userCalls: [],
@@ -103,20 +133,25 @@ function emptyModels(): NotificationMethodModels {
103
133
  userPush: [],
104
134
  userWhatsApps: [],
105
135
  userTelegrams: [],
136
+ userSlacks: [],
137
+ userMicrosoftTeamsAccounts: [],
106
138
  userWebhooks: [],
107
139
  };
108
140
  }
109
141
 
110
142
  // A 24-hex-char string is a valid ObjectID.
111
- const ID: Record<"a" | "b" | "c" | "d" | "e" | "f" | "g", string> = {
112
- a: "aaaaaaaaaaaaaaaaaaaaaaaa",
113
- b: "bbbbbbbbbbbbbbbbbbbbbbbb",
114
- c: "cccccccccccccccccccccccc",
115
- d: "dddddddddddddddddddddddd",
116
- e: "eeeeeeeeeeeeeeeeeeeeeeee",
117
- f: "ffffffffffffffffffffffff",
118
- g: "abcabcabcabcabcabcabcabc",
119
- };
143
+ const ID: Record<"a" | "b" | "c" | "d" | "e" | "f" | "g" | "h" | "i", string> =
144
+ {
145
+ a: "aaaaaaaaaaaaaaaaaaaaaaaa",
146
+ b: "bbbbbbbbbbbbbbbbbbbbbbbb",
147
+ c: "cccccccccccccccccccccccc",
148
+ d: "dddddddddddddddddddddddd",
149
+ e: "eeeeeeeeeeeeeeeeeeeeeeee",
150
+ f: "ffffffffffffffffffffffff",
151
+ g: "abcabcabcabcabcabcabcabc",
152
+ h: "cdecdecdecdecdecdecdecde",
153
+ i: "fabfabfabfabfabfabfabfab",
154
+ };
120
155
 
121
156
  /*
122
157
  * A minimal stand-in for a model row: getDisplayItems only needs getColumnValue,
@@ -144,7 +179,9 @@ describe("NotificationMethodUtil.getSelectForNotificationMethods", () => {
144
179
  [
145
180
  "userCall",
146
181
  "userEmail",
182
+ "userMicrosoftTeams",
147
183
  "userPush",
184
+ "userSlack",
148
185
  "userSms",
149
186
  "userTelegram",
150
187
  "userWebhook",
@@ -171,6 +208,14 @@ describe("NotificationMethodUtil.getSelectForNotificationMethods", () => {
171
208
  telegramUserHandle: true,
172
209
  telegramChatId: true,
173
210
  });
211
+ expect(select["userSlack"]).toEqual({
212
+ slackUserName: true,
213
+ slackUserId: true,
214
+ });
215
+ expect(select["userMicrosoftTeams"]).toEqual({
216
+ microsoftTeamsUserName: true,
217
+ microsoftTeamsUserId: true,
218
+ });
174
219
  });
175
220
 
176
221
  test("selects ONLY name for a webhook — never the credential-bearing URL or secret", () => {
@@ -284,6 +329,51 @@ describe("NotificationMethodUtil.getDisplayItems", () => {
284
329
  expect(items).toEqual([{ title: "Telegram", value: "999" }]);
285
330
  });
286
331
 
332
+ test("prefers the Slack user name over the Slack member id", () => {
333
+ const items: Array<NotificationMethodDisplayItem> =
334
+ NotificationMethodUtil.getDisplayItems(
335
+ reader({
336
+ userSlack: { slackUserName: "alice", slackUserId: "U0123ABCD" },
337
+ }),
338
+ );
339
+
340
+ expect(items).toEqual([{ title: "Slack", value: "alice" }]);
341
+ });
342
+
343
+ test("falls back to the Slack member id when the user name is absent", () => {
344
+ const items: Array<NotificationMethodDisplayItem> =
345
+ NotificationMethodUtil.getDisplayItems(
346
+ reader({ userSlack: { slackUserId: "U0123ABCD" } }),
347
+ );
348
+
349
+ expect(items).toEqual([{ title: "Slack", value: "U0123ABCD" }]);
350
+ });
351
+
352
+ test("prefers the Microsoft Teams user name over the Teams user id", () => {
353
+ const items: Array<NotificationMethodDisplayItem> =
354
+ NotificationMethodUtil.getDisplayItems(
355
+ reader({
356
+ userMicrosoftTeams: {
357
+ microsoftTeamsUserName: "Alice Example",
358
+ microsoftTeamsUserId: "29:1abc",
359
+ },
360
+ }),
361
+ );
362
+
363
+ expect(items).toEqual([
364
+ { title: "Microsoft Teams", value: "Alice Example" },
365
+ ]);
366
+ });
367
+
368
+ test("falls back to the Teams user id when the user name is absent", () => {
369
+ const items: Array<NotificationMethodDisplayItem> =
370
+ NotificationMethodUtil.getDisplayItems(
371
+ reader({ userMicrosoftTeams: { microsoftTeamsUserId: "29:1abc" } }),
372
+ );
373
+
374
+ expect(items).toEqual([{ title: "Microsoft Teams", value: "29:1abc" }]);
375
+ });
376
+
287
377
  test("ignores a relation whose value is not an object", () => {
288
378
  expect(
289
379
  NotificationMethodUtil.getDisplayItems(
@@ -306,12 +396,14 @@ describe("NotificationMethodUtil.getDisplayItems", () => {
306
396
  reader({
307
397
  userPush: { deviceName: "Pixel" },
308
398
  userEmail: { email: "jane@example.com" },
399
+ userSlack: { slackUserName: "alice" },
309
400
  userWebhook: { name: "Hook" },
310
401
  }),
311
402
  );
312
403
 
313
404
  expect(items).toEqual([
314
405
  { title: "Email", value: "jane@example.com" },
406
+ { title: "Slack", value: "alice" },
315
407
  { title: "Webhook", value: "Hook" },
316
408
  { title: "Push", value: "Pixel" },
317
409
  ]);
@@ -370,6 +462,32 @@ describe("NotificationMethodUtil.getLabel", () => {
370
462
  );
371
463
  });
372
464
 
465
+ test("labels a Slack method by user name, then member id, then Unknown Account", () => {
466
+ expect(NotificationMethodUtil.getLabel(slack(ID.a, "alice"))).toBe(
467
+ "Slack: alice",
468
+ );
469
+ expect(
470
+ NotificationMethodUtil.getLabel(slack(ID.b, undefined, "U0123ABCD")),
471
+ ).toBe("Slack: U0123ABCD");
472
+ expect(NotificationMethodUtil.getLabel(slack(ID.c))).toBe(
473
+ "Slack: Unknown Account",
474
+ );
475
+ });
476
+
477
+ test("labels a Teams method by user name, then user id, then Unknown Account", () => {
478
+ expect(
479
+ NotificationMethodUtil.getLabel(microsoftTeams(ID.a, "Alice Example")),
480
+ ).toBe("Microsoft Teams: Alice Example");
481
+ expect(
482
+ NotificationMethodUtil.getLabel(
483
+ microsoftTeams(ID.b, undefined, "29:1abc"),
484
+ ),
485
+ ).toBe("Microsoft Teams: 29:1abc");
486
+ expect(NotificationMethodUtil.getLabel(microsoftTeams(ID.c))).toBe(
487
+ "Microsoft Teams: Unknown Account",
488
+ );
489
+ });
490
+
373
491
  test("labels call, sms, whatsapp with the phone number and their prefix", () => {
374
492
  expect(NotificationMethodUtil.getLabel(call(ID.a, "+15555550100"))).toBe(
375
493
  "Call: +15555550100",
@@ -418,6 +536,8 @@ describe("NotificationMethodUtil.getDropdownOptions", () => {
418
536
  userPush: [push(ID.d, "Pixel")],
419
537
  userWhatsApps: [whatsApp(ID.e, "+15555550102")],
420
538
  userTelegrams: [telegram(ID.f, "@jane")],
539
+ userSlacks: [slack(ID.h, "alice")],
540
+ userMicrosoftTeamsAccounts: [microsoftTeams(ID.i, "Alice Example")],
421
541
  userWebhooks: [webhook(ID.g, "Hook")],
422
542
  };
423
543
 
@@ -434,6 +554,8 @@ describe("NotificationMethodUtil.getDropdownOptions", () => {
434
554
  "Push: Pixel",
435
555
  "WhatsApp: +15555550102",
436
556
  "Telegram: @jane",
557
+ "Slack: alice",
558
+ "Microsoft Teams: Alice Example",
437
559
  "Webhook: Hook",
438
560
  ]);
439
561
  });
@@ -465,6 +587,8 @@ describe("NotificationMethodUtil.setSelectedMethodOnRule", () => {
465
587
  userPush: [push(ID.d, "Pixel")],
466
588
  userWhatsApps: [whatsApp(ID.e, "+15555550102")],
467
589
  userTelegrams: [telegram(ID.f, "@jane")],
590
+ userSlacks: [slack(ID.h, "alice")],
591
+ userMicrosoftTeamsAccounts: [microsoftTeams(ID.i, "Alice Example")],
468
592
  userWebhooks: [webhook(ID.g, "Hook")],
469
593
  };
470
594
  }
@@ -489,6 +613,8 @@ describe("NotificationMethodUtil.setSelectedMethodOnRule", () => {
489
613
  expect(rule.userPushId).toBeUndefined();
490
614
  expect(rule.userWhatsAppId).toBeUndefined();
491
615
  expect(rule.userTelegramId).toBeUndefined();
616
+ expect(rule.userSlackId).toBeUndefined();
617
+ expect(rule.userMicrosoftTeamsId).toBeUndefined();
492
618
  });
493
619
 
494
620
  test("maps each method type to its own foreign key", () => {
@@ -499,6 +625,8 @@ describe("NotificationMethodUtil.setSelectedMethodOnRule", () => {
499
625
  { id: ID.d, key: "userPushId" },
500
626
  { id: ID.e, key: "userWhatsAppId" },
501
627
  { id: ID.f, key: "userTelegramId" },
628
+ { id: ID.h, key: "userSlackId" },
629
+ { id: ID.i, key: "userMicrosoftTeamsId" },
502
630
  { id: ID.g, key: "userWebhookId" },
503
631
  ];
504
632