@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
@@ -0,0 +1,410 @@
1
+ import UserNotificationSettingService from "../../../Server/Services/UserNotificationSettingService";
2
+ import UserSlackService from "../../../Server/Services/UserSlackService";
3
+ import UserMicrosoftTeamsService from "../../../Server/Services/UserMicrosoftTeamsService";
4
+ import WorkspaceUserNotificationService from "../../../Server/Services/WorkspaceUserNotificationService";
5
+ import ProjectCallSMSConfigService from "../../../Server/Services/ProjectCallSMSConfigService";
6
+ import logger from "../../../Server/Utils/Logger";
7
+ import UserNotificationSetting from "../../../Models/DatabaseModels/UserNotificationSetting";
8
+ import UserSlack from "../../../Models/DatabaseModels/UserSlack";
9
+ import UserMicrosoftTeams from "../../../Models/DatabaseModels/UserMicrosoftTeams";
10
+ import { CallRequestMessage } from "../../../Types/Call/CallRequest";
11
+ import { EmailEnvelope } from "../../../Types/Email/EmailMessage";
12
+ import EmailTemplateType from "../../../Types/Email/EmailTemplateType";
13
+ import NotificationSettingEventType from "../../../Types/NotificationSetting/NotificationSettingEventType";
14
+ import ObjectID from "../../../Types/ObjectID";
15
+ import PushNotificationMessage from "../../../Types/PushNotification/PushNotificationMessage";
16
+ import { SMSMessage } from "../../../Types/SMS/SMS";
17
+ import { WhatsAppMessagePayload } from "../../../Types/WhatsApp/WhatsAppMessage";
18
+ import {
19
+ WorkspaceMessageBlock,
20
+ WorkspacePayloadMarkdown,
21
+ } from "../../../Types/Workspace/WorkspaceMessagePayload";
22
+ import WorkspaceType from "../../../Types/Workspace/WorkspaceType";
23
+ import { describe, expect, test, beforeEach, afterEach } from "@jest/globals";
24
+
25
+ /*
26
+ * sendUserNotification is the settings-based (non-on-call-rule) delivery path:
27
+ * "incident created and I own it", "I go on-call", and the ~40 other
28
+ * NotificationSettingEventType rows. Its callers were all written before the
29
+ * workspace channels existed, so they hand over payloads for the older
30
+ * channels only — which is why the Slack / Microsoft Teams block SYNTHESISES
31
+ * its markdown from the email subject + SMS body, exactly the way the
32
+ * Telegram fallback body works. Pinned here:
33
+ *
34
+ * 1. THE TOGGLES. alertBySlack / alertByMicrosoftTeams each gate their own
35
+ * channel and nothing else, and a settings row with both off sends no
36
+ * workspace message at all (including not looking the methods up).
37
+ *
38
+ * 2. THE ADDRESSES. Only VERIFIED method rows are read, each one gets its
39
+ * own send, and a row whose address column is empty is skipped.
40
+ *
41
+ * 3. THE BODY. `🔔 **{subject}**` + blank line + SMS body; subject-less
42
+ * notifications fall back to the generic headline; a notification with
43
+ * neither subject nor SMS body sends nothing rather than a blank DM.
44
+ *
45
+ * 4. ISOLATION. Sends are fire-and-forget: a rejected workspace send is
46
+ * logged and never rejects sendUserNotification.
47
+ */
48
+
49
+ const PROJECT_ID: ObjectID = new ObjectID(
50
+ "11111111-1111-4111-8111-111111111111",
51
+ );
52
+ const USER_ID: ObjectID = new ObjectID("22222222-2222-4222-8222-222222222222");
53
+ const INCIDENT_ID: ObjectID = new ObjectID(
54
+ "33333333-3333-4333-8333-333333333333",
55
+ );
56
+
57
+ const SLACK_USER_ID: string = "U0123ABCD";
58
+ const SECOND_SLACK_USER_ID: string = "U0456EFGH";
59
+ const TEAMS_USER_ID: string = "entra-object-id-1";
60
+
61
+ const EVENT_TYPE: NotificationSettingEventType =
62
+ NotificationSettingEventType.SEND_INCIDENT_CREATED_OWNER_NOTIFICATION;
63
+
64
+ type SendUserNotificationData = Parameters<
65
+ typeof UserNotificationSettingService.sendUserNotification
66
+ >[0];
67
+
68
+ function settingsRow(
69
+ overrides: Partial<UserNotificationSetting> = {},
70
+ ): UserNotificationSetting {
71
+ return {
72
+ alertByEmail: false,
73
+ alertBySMS: false,
74
+ alertByCall: false,
75
+ alertByPush: false,
76
+ alertByWhatsApp: false,
77
+ alertByTelegram: false,
78
+ alertBySlack: false,
79
+ alertByMicrosoftTeams: false,
80
+ alertByWebhook: false,
81
+ ...overrides,
82
+ } as unknown as UserNotificationSetting;
83
+ }
84
+
85
+ function notificationData(
86
+ overrides: Partial<SendUserNotificationData> = {},
87
+ ): SendUserNotificationData {
88
+ const emailEnvelope: EmailEnvelope = {
89
+ subject: "Incident created: Checkout is down",
90
+ templateType: EmailTemplateType.BlankTemplate,
91
+ vars: {},
92
+ } as unknown as EmailEnvelope;
93
+
94
+ const smsMessage: SMSMessage = {
95
+ message:
96
+ "OneUptime: New incident created: Checkout is down. Please acknowledge.",
97
+ } as SMSMessage;
98
+
99
+ return {
100
+ userId: USER_ID,
101
+ projectId: PROJECT_ID,
102
+ eventType: EVENT_TYPE,
103
+ emailEnvelope: emailEnvelope,
104
+ smsMessage: smsMessage,
105
+ callRequestMessage: {} as CallRequestMessage,
106
+ pushNotificationMessage: {} as PushNotificationMessage,
107
+ whatsAppMessage: {} as WhatsAppMessagePayload,
108
+ incidentId: INCIDENT_ID,
109
+ ...overrides,
110
+ } as SendUserNotificationData;
111
+ }
112
+
113
+ describe("UserNotificationSettingService.sendUserNotification - workspace channels", () => {
114
+ let findSettings: jest.SpyInstance;
115
+ let findSlacks: jest.SpyInstance;
116
+ let findTeams: jest.SpyInstance;
117
+ let sendDm: jest.SpyInstance;
118
+ let loggerError: jest.SpyInstance;
119
+
120
+ beforeEach(() => {
121
+ loggerError = jest.spyOn(logger, "error").mockImplementation((): void => {
122
+ return undefined;
123
+ });
124
+ jest.spyOn(logger, "warn").mockImplementation((): void => {
125
+ return undefined;
126
+ });
127
+
128
+ findSettings = jest
129
+ .spyOn(UserNotificationSettingService, "findOneBy")
130
+ .mockResolvedValue(settingsRow({ alertBySlack: true }) as never);
131
+
132
+ jest
133
+ .spyOn(ProjectCallSMSConfigService, "getProjectDefaultTwilioConfig")
134
+ .mockResolvedValue(undefined as never);
135
+
136
+ findSlacks = jest.spyOn(UserSlackService, "findBy").mockResolvedValue([
137
+ {
138
+ id: new ObjectID("44444444-4444-4444-8444-444444444444"),
139
+ slackUserId: SLACK_USER_ID,
140
+ } as unknown as UserSlack,
141
+ ] as never);
142
+
143
+ findTeams = jest
144
+ .spyOn(UserMicrosoftTeamsService, "findBy")
145
+ .mockResolvedValue([
146
+ {
147
+ id: new ObjectID("55555555-5555-4555-8555-555555555555"),
148
+ microsoftTeamsUserId: TEAMS_USER_ID,
149
+ } as unknown as UserMicrosoftTeams,
150
+ ] as never);
151
+
152
+ sendDm = jest
153
+ .spyOn(WorkspaceUserNotificationService, "sendDirectMessageToUser")
154
+ .mockResolvedValue(undefined as never);
155
+ });
156
+
157
+ afterEach(() => {
158
+ jest.restoreAllMocks();
159
+ });
160
+
161
+ function flushMicrotasks(): Promise<void> {
162
+ return Promise.resolve()
163
+ .then((): Promise<void> => {
164
+ return Promise.resolve();
165
+ })
166
+ .then((): Promise<void> => {
167
+ return Promise.resolve();
168
+ });
169
+ }
170
+
171
+ interface CapturedSendArg {
172
+ projectId: ObjectID;
173
+ workspaceType: WorkspaceType;
174
+ workspaceUserId: string;
175
+ messageBlocks: Array<WorkspaceMessageBlock>;
176
+ userId: ObjectID;
177
+ incidentId?: ObjectID;
178
+ userOnCallLogTimelineId?: ObjectID;
179
+ }
180
+
181
+ function sendArgs(): Array<CapturedSendArg> {
182
+ return sendDm.mock.calls.map((call: Array<unknown>) => {
183
+ return call[0] as CapturedSendArg;
184
+ });
185
+ }
186
+
187
+ /*
188
+ * ----------------------------------------------------------------------- *
189
+ * (A) The toggles.
190
+ * -----------------------------------------------------------------------
191
+ */
192
+
193
+ describe("the per-channel toggles", () => {
194
+ test("alertBySlack on sends to Slack and never reads the Teams methods", async () => {
195
+ await UserNotificationSettingService.sendUserNotification(
196
+ notificationData(),
197
+ );
198
+
199
+ expect(sendDm).toHaveBeenCalledTimes(1);
200
+ expect(sendArgs()[0]?.workspaceType).toBe(WorkspaceType.Slack);
201
+ expect(sendArgs()[0]?.workspaceUserId).toBe(SLACK_USER_ID);
202
+ expect(findTeams).not.toHaveBeenCalled();
203
+ });
204
+
205
+ test("alertByMicrosoftTeams on sends to Teams and never reads the Slack methods", async () => {
206
+ findSettings.mockResolvedValue(
207
+ settingsRow({ alertByMicrosoftTeams: true }) as never,
208
+ );
209
+
210
+ await UserNotificationSettingService.sendUserNotification(
211
+ notificationData(),
212
+ );
213
+
214
+ expect(sendDm).toHaveBeenCalledTimes(1);
215
+ expect(sendArgs()[0]?.workspaceType).toBe(WorkspaceType.MicrosoftTeams);
216
+ expect(sendArgs()[0]?.workspaceUserId).toBe(TEAMS_USER_ID);
217
+ expect(findSlacks).not.toHaveBeenCalled();
218
+ });
219
+
220
+ test("both toggles on sends one message per channel", async () => {
221
+ findSettings.mockResolvedValue(
222
+ settingsRow({
223
+ alertBySlack: true,
224
+ alertByMicrosoftTeams: true,
225
+ }) as never,
226
+ );
227
+
228
+ await UserNotificationSettingService.sendUserNotification(
229
+ notificationData(),
230
+ );
231
+
232
+ expect(sendDm).toHaveBeenCalledTimes(2);
233
+ const workspaceTypes: Array<WorkspaceType> = sendArgs().map(
234
+ (arg: CapturedSendArg) => {
235
+ return arg.workspaceType;
236
+ },
237
+ );
238
+ expect(workspaceTypes).toContain(WorkspaceType.Slack);
239
+ expect(workspaceTypes).toContain(WorkspaceType.MicrosoftTeams);
240
+ });
241
+
242
+ test("both toggles off sends nothing and does not even look the methods up", async () => {
243
+ findSettings.mockResolvedValue(settingsRow() as never);
244
+
245
+ await UserNotificationSettingService.sendUserNotification(
246
+ notificationData(),
247
+ );
248
+
249
+ expect(sendDm).not.toHaveBeenCalled();
250
+ expect(findSlacks).not.toHaveBeenCalled();
251
+ expect(findTeams).not.toHaveBeenCalled();
252
+ });
253
+
254
+ test("no settings row at all sends nothing", async () => {
255
+ findSettings.mockResolvedValue(null as never);
256
+
257
+ await UserNotificationSettingService.sendUserNotification(
258
+ notificationData(),
259
+ );
260
+
261
+ expect(sendDm).not.toHaveBeenCalled();
262
+ });
263
+ });
264
+
265
+ /*
266
+ * ----------------------------------------------------------------------- *
267
+ * (B) The address reads.
268
+ * -----------------------------------------------------------------------
269
+ */
270
+
271
+ describe("the address reads", () => {
272
+ test("only VERIFIED Slack rows are read, scoped to this user and project", async () => {
273
+ await UserNotificationSettingService.sendUserNotification(
274
+ notificationData(),
275
+ );
276
+
277
+ const arg: {
278
+ query: { userId: ObjectID; projectId: ObjectID; isVerified: boolean };
279
+ props: { isRoot: boolean };
280
+ } = findSlacks.mock.calls[0][0] as {
281
+ query: { userId: ObjectID; projectId: ObjectID; isVerified: boolean };
282
+ props: { isRoot: boolean };
283
+ };
284
+ expect(arg.query.userId.toString()).toBe(USER_ID.toString());
285
+ expect(arg.query.projectId.toString()).toBe(PROJECT_ID.toString());
286
+ expect(arg.query.isVerified).toBe(true);
287
+ expect(arg.props.isRoot).toBe(true);
288
+ });
289
+
290
+ test("every linked account gets its own send", async () => {
291
+ findSlacks.mockResolvedValue([
292
+ { slackUserId: SLACK_USER_ID } as unknown as UserSlack,
293
+ { slackUserId: SECOND_SLACK_USER_ID } as unknown as UserSlack,
294
+ ] as never);
295
+
296
+ await UserNotificationSettingService.sendUserNotification(
297
+ notificationData(),
298
+ );
299
+
300
+ expect(sendDm).toHaveBeenCalledTimes(2);
301
+ const targets: Array<string> = sendArgs().map((arg: CapturedSendArg) => {
302
+ return arg.workspaceUserId;
303
+ });
304
+ expect(targets).toContain(SLACK_USER_ID);
305
+ expect(targets).toContain(SECOND_SLACK_USER_ID);
306
+ });
307
+
308
+ test("a row whose address column is empty is skipped rather than sent to nobody", async () => {
309
+ findSlacks.mockResolvedValue([
310
+ { slackUserId: "" } as unknown as UserSlack,
311
+ { slackUserId: SLACK_USER_ID } as unknown as UserSlack,
312
+ ] as never);
313
+
314
+ await UserNotificationSettingService.sendUserNotification(
315
+ notificationData(),
316
+ );
317
+
318
+ expect(sendDm).toHaveBeenCalledTimes(1);
319
+ expect(sendArgs()[0]?.workspaceUserId).toBe(SLACK_USER_ID);
320
+ });
321
+ });
322
+
323
+ /*
324
+ * ----------------------------------------------------------------------- *
325
+ * (C) The synthesised body.
326
+ * -----------------------------------------------------------------------
327
+ */
328
+
329
+ describe("the synthesised markdown body", () => {
330
+ function firstBlockText(): string {
331
+ const block: WorkspaceMessageBlock | undefined =
332
+ sendArgs()[0]?.messageBlocks[0];
333
+ return (block as WorkspacePayloadMarkdown).text;
334
+ }
335
+
336
+ test("is built from the email subject (bold, belled) plus the SMS body", async () => {
337
+ await UserNotificationSettingService.sendUserNotification(
338
+ notificationData(),
339
+ );
340
+
341
+ const text: string = firstBlockText();
342
+ expect(text).toContain("🔔 **Incident created: Checkout is down**");
343
+ expect(text).toContain(
344
+ "OneUptime: New incident created: Checkout is down. Please acknowledge.",
345
+ );
346
+ });
347
+
348
+ test("a subject-less notification falls back to the generic headline", async () => {
349
+ await UserNotificationSettingService.sendUserNotification(
350
+ notificationData({
351
+ emailEnvelope: {
352
+ subject: "",
353
+ templateType: EmailTemplateType.BlankTemplate,
354
+ vars: {},
355
+ } as unknown as EmailEnvelope,
356
+ }),
357
+ );
358
+
359
+ expect(firstBlockText()).toContain("🔔 **OneUptime notification**");
360
+ });
361
+
362
+ test("neither subject nor SMS body sends nothing rather than a blank DM", async () => {
363
+ await UserNotificationSettingService.sendUserNotification(
364
+ notificationData({
365
+ emailEnvelope: {
366
+ subject: "",
367
+ templateType: EmailTemplateType.BlankTemplate,
368
+ vars: {},
369
+ } as unknown as EmailEnvelope,
370
+ smsMessage: { message: "" } as SMSMessage,
371
+ }),
372
+ );
373
+
374
+ expect(sendDm).not.toHaveBeenCalled();
375
+ });
376
+
377
+ test("the context ids ride along so the workspace log can attribute the message", async () => {
378
+ await UserNotificationSettingService.sendUserNotification(
379
+ notificationData(),
380
+ );
381
+
382
+ expect(sendArgs()[0]?.incidentId?.toString()).toBe(
383
+ INCIDENT_ID.toString(),
384
+ );
385
+ expect(sendArgs()[0]?.userId.toString()).toBe(USER_ID.toString());
386
+ // The settings path has no on-call timeline row to flip.
387
+ expect(sendArgs()[0]?.userOnCallLogTimelineId).toBeUndefined();
388
+ });
389
+ });
390
+
391
+ /*
392
+ * ----------------------------------------------------------------------- *
393
+ * (D) Failure isolation.
394
+ * -----------------------------------------------------------------------
395
+ */
396
+
397
+ describe("failure isolation", () => {
398
+ test("a rejected workspace send is logged and never rejects sendUserNotification", async () => {
399
+ sendDm.mockRejectedValue(new Error("workspace unreachable") as never);
400
+
401
+ await expect(
402
+ UserNotificationSettingService.sendUserNotification(notificationData()),
403
+ ).resolves.toBeUndefined();
404
+
405
+ await flushMicrotasks();
406
+
407
+ expect(loggerError).toHaveBeenCalled();
408
+ });
409
+ });
410
+ });