@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.
- package/Models/DatabaseModels/EnterpriseLicense.ts +23 -0
- package/Models/DatabaseModels/Index.ts +4 -2
- package/Models/DatabaseModels/Monitor.ts +39 -9
- package/Models/DatabaseModels/UserMicrosoftTeams.ts +344 -0
- package/Models/DatabaseModels/UserNotificationRule.ts +144 -8
- package/Models/DatabaseModels/UserNotificationSetting.ts +34 -0
- package/Models/DatabaseModels/UserOnCallLogTimeline.ts +95 -0
- package/Models/DatabaseModels/UserSlack.ts +341 -0
- package/Models/DatabaseModels/WorkspaceProjectAuthToken.ts +9 -0
- package/Server/API/UserMicrosoftTeamsAPI.ts +127 -0
- package/Server/API/UserSlackAPI.ts +125 -0
- package/Server/EnvironmentConfig.ts +12 -107
- package/Server/Infrastructure/Postgres/SchemaMigrations/1788700000000-RemoveMarketingConversionUploadState.ts +33 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1788800000000-DropMarketingConversionAddEnterpriseLicenseEmail.ts +51 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1788900000000-RedactStoredMonitorIngestSecrets.ts +117 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1789000000000-AddUserSlackAndMicrosoftTeams.ts +197 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +8 -0
- package/Server/Infrastructure/Queue.ts +7 -0
- package/Server/Services/EnterpriseLicenseService.ts +60 -0
- package/Server/Services/Index.ts +6 -2
- package/Server/Services/LlmLogService.ts +25 -9
- package/Server/Services/OnCallReadinessService.ts +86 -2
- package/Server/Services/ProjectService.ts +67 -0
- package/Server/Services/UserMicrosoftTeamsService.ts +208 -0
- package/Server/Services/UserNotificationMethodAdminService.ts +167 -5
- package/Server/Services/UserNotificationRuleAdminService.ts +40 -6
- package/Server/Services/UserNotificationRuleService.ts +589 -11
- package/Server/Services/UserNotificationSettingService.ts +138 -0
- package/Server/Services/UserService.ts +28 -0
- package/Server/Services/UserSlackService.ts +218 -0
- package/Server/Services/WorkspaceProjectAuthTokenService.ts +67 -1
- package/Server/Services/WorkspaceUserAuthTokenService.ts +73 -0
- package/Server/Services/WorkspaceUserNotificationService.ts +190 -0
- package/Server/Utils/Marketing/MarketingEventUtil.ts +145 -0
- package/Server/Utils/Marketing/MarketingEventWebhook.ts +114 -0
- package/Server/Utils/Monitor/MonitorLogUtil.ts +14 -1
- package/Server/Utils/Monitor/MonitorPayloadRedaction.ts +321 -0
- package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +191 -4
- package/Tests/App/Dashboard/AdminNotificationRulesPage.test.tsx +22 -14
- package/Tests/App/Dashboard/AdminUserNotificationMethodsPage.test.tsx +22 -11
- package/Tests/App/Dashboard/OnCallPreventionGuards.test.tsx +37 -1
- package/Tests/App/Dashboard/OnCallRulesTable.test.tsx +18 -10
- package/Tests/Models/DatabaseModels/DomainRoleTierCoverage.test.ts +27 -1
- package/Tests/Models/DatabaseModels/MonitorSecretKeyColumnAccessControl.test.ts +214 -0
- package/Tests/Server/API/OnCallReadinessAPI.test.ts +145 -26
- package/Tests/Server/API/UserNotificationMethodAdminAPI.test.ts +2 -2
- package/Tests/Server/Services/AIServiceDailyBudget.test.ts +11 -1
- package/Tests/Server/Services/AdminRuleEditGuards.test.ts +22 -8
- package/Tests/Server/Services/DeliverNotificationForRuleExtraction.test.ts +18 -2
- package/Tests/Server/Services/EpisodeRuleSeverityRepair.test.ts +5 -1
- package/Tests/Server/Services/LlmLogServiceTokenAggregateParams.test.ts +640 -0
- package/Tests/Server/Services/NotificationChannelEventCoverage.test.ts +246 -22
- package/Tests/Server/Services/NotificationDeletionImpact.test.ts +136 -8
- package/Tests/Server/Services/OnCallNotificationFallback.test.ts +191 -8
- package/Tests/Server/Services/OnCallReadinessService.test.ts +343 -18
- package/Tests/Server/Services/SeverityRuleBackfill.test.ts +5 -1
- package/Tests/Server/Services/UserMicrosoftTeamsService.test.ts +343 -0
- package/Tests/Server/Services/UserNotificationMethodAdminService.test.ts +209 -8
- package/Tests/Server/Services/UserNotificationRuleAdminGuards.test.ts +22 -6
- package/Tests/Server/Services/UserNotificationRuleDefaultCreation.test.ts +12 -6
- package/Tests/Server/Services/UserNotificationRuleExecuteItem.test.ts +37 -3
- package/Tests/Server/Services/UserNotificationRuleWorkspaceDelivery.test.ts +669 -0
- package/Tests/Server/Services/UserNotificationSettingWorkspaceChannels.test.ts +410 -0
- package/Tests/Server/Services/UserSlackService.test.ts +407 -0
- package/Tests/Server/Services/WorkspaceProjectAuthTokenDisconnectCascade.test.ts +224 -0
- package/Tests/Server/Services/WorkspaceUserAuthTokenNotificationMethodCascade.test.ts +189 -0
- package/Tests/Server/Services/WorkspaceUserNotificationService.test.ts +496 -0
- package/Tests/Server/Types/Database/Permissions/AdminNotificationRuleAccess.test.ts +30 -17
- package/Tests/Server/Types/Database/Permissions/CreateOwnershipScoping.test.ts +8 -3
- package/Tests/Server/Types/Database/Permissions/OwnerOnlyColumns.test.ts +72 -3
- package/Tests/Server/Types/Database/Permissions/UserNotificationRuleScoping.test.ts +22 -18
- package/Tests/Server/Types/Database/Permissions/WorkspaceMethodStampedColumnCreate.test.ts +178 -0
- package/Tests/Server/Utils/Marketing/MarketingEventUtil.test.ts +259 -0
- package/Tests/Server/Utils/Marketing/MarketingEventWebhook.test.ts +257 -0
- package/Tests/Server/Utils/Monitor/MonitorLogUtilRedaction.test.ts +300 -0
- package/Tests/Server/Utils/Monitor/MonitorPayloadRedaction.test.ts +381 -0
- package/Tests/Server/Utils/Runbook/RunbookExecutePermission.test.ts +228 -0
- package/Tests/Server/Utils/SessionReplay/SessionReplayErasureTombstone.test.ts +189 -0
- package/Tests/Server/Utils/SessionReplay/SessionReplayUsage.test.ts +205 -0
- package/Tests/Server/Utils/Telemetry/AppMetrics.test.ts +275 -0
- package/Tests/Server/Utils/Telemetry/TelemetryContext.test.ts +279 -0
- package/Tests/Server/Utils/Workspace/MicrosoftTeamsDirectMessage.test.ts +454 -0
- package/Tests/UI/Utils/NotificationMethodUtil.test.ts +137 -9
- package/Tests/UI/Utils/PermissionGate.test.ts +105 -0
- package/Types/Marketing/MarketingEvent.ts +97 -0
- package/UI/Utils/NotificationMethodUtil.ts +41 -1
- package/UI/Utils/PermissionGate.ts +56 -0
- package/build/dist/Models/DatabaseModels/EnterpriseLicense.js +24 -0
- package/build/dist/Models/DatabaseModels/EnterpriseLicense.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Index.js +4 -2
- package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Monitor.js +39 -9
- package/build/dist/Models/DatabaseModels/Monitor.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserMicrosoftTeams.js +363 -0
- package/build/dist/Models/DatabaseModels/UserMicrosoftTeams.js.map +1 -0
- package/build/dist/Models/DatabaseModels/UserNotificationRule.js +145 -8
- package/build/dist/Models/DatabaseModels/UserNotificationRule.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserNotificationSetting.js +38 -0
- package/build/dist/Models/DatabaseModels/UserNotificationSetting.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserOnCallLogTimeline.js +96 -0
- package/build/dist/Models/DatabaseModels/UserOnCallLogTimeline.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserSlack.js +361 -0
- package/build/dist/Models/DatabaseModels/UserSlack.js.map +1 -0
- package/build/dist/Models/DatabaseModels/WorkspaceProjectAuthToken.js.map +1 -1
- package/build/dist/Server/API/UserMicrosoftTeamsAPI.js +82 -0
- package/build/dist/Server/API/UserMicrosoftTeamsAPI.js.map +1 -0
- package/build/dist/Server/API/UserSlackAPI.js +81 -0
- package/build/dist/Server/API/UserSlackAPI.js.map +1 -0
- package/build/dist/Server/EnvironmentConfig.js +10 -70
- package/build/dist/Server/EnvironmentConfig.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788700000000-RemoveMarketingConversionUploadState.js +26 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788700000000-RemoveMarketingConversionUploadState.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788800000000-DropMarketingConversionAddEnterpriseLicenseEmail.js +37 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788800000000-DropMarketingConversionAddEnterpriseLicenseEmail.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788900000000-RedactStoredMonitorIngestSecrets.js +106 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788900000000-RedactStoredMonitorIngestSecrets.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789000000000-AddUserSlackAndMicrosoftTeams.js +78 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789000000000-AddUserSlackAndMicrosoftTeams.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +8 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Infrastructure/Queue.js +7 -0
- package/build/dist/Server/Infrastructure/Queue.js.map +1 -1
- package/build/dist/Server/Services/EnterpriseLicenseService.js +65 -0
- package/build/dist/Server/Services/EnterpriseLicenseService.js.map +1 -1
- package/build/dist/Server/Services/Index.js +6 -2
- package/build/dist/Server/Services/Index.js.map +1 -1
- package/build/dist/Server/Services/LlmLogService.js +25 -9
- package/build/dist/Server/Services/LlmLogService.js.map +1 -1
- package/build/dist/Server/Services/OnCallReadinessService.js +74 -2
- package/build/dist/Server/Services/OnCallReadinessService.js.map +1 -1
- package/build/dist/Server/Services/ProjectService.js +56 -0
- package/build/dist/Server/Services/ProjectService.js.map +1 -1
- package/build/dist/Server/Services/UserMicrosoftTeamsService.js +183 -0
- package/build/dist/Server/Services/UserMicrosoftTeamsService.js.map +1 -0
- package/build/dist/Server/Services/UserNotificationMethodAdminService.js +138 -2
- package/build/dist/Server/Services/UserNotificationMethodAdminService.js.map +1 -1
- package/build/dist/Server/Services/UserNotificationRuleAdminService.js +32 -4
- package/build/dist/Server/Services/UserNotificationRuleAdminService.js.map +1 -1
- package/build/dist/Server/Services/UserNotificationRuleService.js +499 -67
- package/build/dist/Server/Services/UserNotificationRuleService.js.map +1 -1
- package/build/dist/Server/Services/UserNotificationSettingService.js +118 -0
- package/build/dist/Server/Services/UserNotificationSettingService.js.map +1 -1
- package/build/dist/Server/Services/UserService.js +26 -1
- package/build/dist/Server/Services/UserService.js.map +1 -1
- package/build/dist/Server/Services/UserSlackService.js +195 -0
- package/build/dist/Server/Services/UserSlackService.js.map +1 -0
- package/build/dist/Server/Services/WorkspaceProjectAuthTokenService.js +64 -1
- package/build/dist/Server/Services/WorkspaceProjectAuthTokenService.js.map +1 -1
- package/build/dist/Server/Services/WorkspaceUserAuthTokenService.js +70 -0
- package/build/dist/Server/Services/WorkspaceUserAuthTokenService.js.map +1 -1
- package/build/dist/Server/Services/WorkspaceUserNotificationService.js +129 -0
- package/build/dist/Server/Services/WorkspaceUserNotificationService.js.map +1 -0
- package/build/dist/Server/Utils/Marketing/MarketingEventUtil.js +94 -0
- package/build/dist/Server/Utils/Marketing/MarketingEventUtil.js.map +1 -0
- package/build/dist/Server/Utils/Marketing/MarketingEventWebhook.js +93 -0
- package/build/dist/Server/Utils/Marketing/MarketingEventWebhook.js.map +1 -0
- package/build/dist/Server/Utils/Monitor/MonitorLogUtil.js +12 -1
- package/build/dist/Server/Utils/Monitor/MonitorLogUtil.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorPayloadRedaction.js +233 -0
- package/build/dist/Server/Utils/Monitor/MonitorPayloadRedaction.js.map +1 -0
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +141 -4
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
- package/build/dist/Types/Marketing/MarketingEvent.js +43 -0
- package/build/dist/Types/Marketing/MarketingEvent.js.map +1 -0
- package/build/dist/UI/Utils/NotificationMethodUtil.js +33 -1
- package/build/dist/UI/Utils/NotificationMethodUtil.js.map +1 -1
- package/build/dist/UI/Utils/PermissionGate.js +32 -0
- package/build/dist/UI/Utils/PermissionGate.js.map +1 -1
- package/package.json +1 -1
- package/Models/DatabaseModels/MarketingConversion.ts +0 -410
- package/Server/Services/MarketingConversionService.ts +0 -10
- package/Server/Utils/Marketing/ConversionUploadProvider.ts +0 -215
- package/Server/Utils/Marketing/ConversionUploadProviders.ts +0 -22
- package/Server/Utils/Marketing/Providers/GoogleAds.ts +0 -333
- package/Server/Utils/Marketing/Providers/LinkedIn.ts +0 -185
- package/Server/Utils/Marketing/Providers/Meta.ts +0 -182
- package/Server/Utils/Marketing/Providers/MicrosoftAds.ts +0 -223
- package/Server/Utils/Marketing/Providers/Reddit.ts +0 -208
- package/Tests/Server/Utils/Marketing/AdUploadableConversionTypes.test.ts +0 -315
- package/Tests/Server/Utils/Marketing/ConversionUploadProvider.test.ts +0 -300
- package/Tests/Server/Utils/Marketing/GoogleAds.test.ts +0 -592
- package/Tests/Server/Utils/Marketing/LinkedIn.test.ts +0 -282
- package/Tests/Server/Utils/Marketing/Meta.test.ts +0 -304
- package/Tests/Server/Utils/Marketing/MicrosoftAds.test.ts +0 -263
- package/Tests/Server/Utils/Marketing/Reddit.test.ts +0 -259
- package/Types/Marketing/MarketingConversion.ts +0 -53
- package/build/dist/Models/DatabaseModels/MarketingConversion.js +0 -445
- package/build/dist/Models/DatabaseModels/MarketingConversion.js.map +0 -1
- package/build/dist/Server/Services/MarketingConversionService.js +0 -9
- package/build/dist/Server/Services/MarketingConversionService.js.map +0 -1
- package/build/dist/Server/Utils/Marketing/ConversionUploadProvider.js +0 -93
- package/build/dist/Server/Utils/Marketing/ConversionUploadProvider.js.map +0 -1
- package/build/dist/Server/Utils/Marketing/ConversionUploadProviders.js +0 -20
- package/build/dist/Server/Utils/Marketing/ConversionUploadProviders.js.map +0 -1
- package/build/dist/Server/Utils/Marketing/Providers/GoogleAds.js +0 -221
- package/build/dist/Server/Utils/Marketing/Providers/GoogleAds.js.map +0 -1
- package/build/dist/Server/Utils/Marketing/Providers/LinkedIn.js +0 -137
- package/build/dist/Server/Utils/Marketing/Providers/LinkedIn.js.map +0 -1
- package/build/dist/Server/Utils/Marketing/Providers/Meta.js +0 -133
- package/build/dist/Server/Utils/Marketing/Providers/Meta.js.map +0 -1
- package/build/dist/Server/Utils/Marketing/Providers/MicrosoftAds.js +0 -143
- package/build/dist/Server/Utils/Marketing/Providers/MicrosoftAds.js.map +0 -1
- package/build/dist/Server/Utils/Marketing/Providers/Reddit.js +0 -145
- package/build/dist/Server/Utils/Marketing/Providers/Reddit.js.map +0 -1
- package/build/dist/Types/Marketing/MarketingConversion.js +0 -52
- package/build/dist/Types/Marketing/MarketingConversion.js.map +0 -1
|
@@ -0,0 +1,669 @@
|
|
|
1
|
+
import UserNotificationRuleService from "../../../Server/Services/UserNotificationRuleService";
|
|
2
|
+
import UserOnCallLogService from "../../../Server/Services/UserOnCallLogService";
|
|
3
|
+
import UserOnCallLogTimelineService from "../../../Server/Services/UserOnCallLogTimelineService";
|
|
4
|
+
import ProjectCallSMSConfigService from "../../../Server/Services/ProjectCallSMSConfigService";
|
|
5
|
+
import IncidentService from "../../../Server/Services/IncidentService";
|
|
6
|
+
import AlertService from "../../../Server/Services/AlertService";
|
|
7
|
+
import AlertEpisodeService from "../../../Server/Services/AlertEpisodeService";
|
|
8
|
+
import IncidentEpisodeService from "../../../Server/Services/IncidentEpisodeService";
|
|
9
|
+
import WorkspaceUserNotificationService from "../../../Server/Services/WorkspaceUserNotificationService";
|
|
10
|
+
import ShortLinkService from "../../../Server/Services/ShortLinkService";
|
|
11
|
+
import DatabaseConfig from "../../../Server/DatabaseConfig";
|
|
12
|
+
import logger from "../../../Server/Utils/Logger";
|
|
13
|
+
import Hostname from "../../../Types/API/Hostname";
|
|
14
|
+
import Protocol from "../../../Types/API/Protocol";
|
|
15
|
+
import URL from "../../../Types/API/URL";
|
|
16
|
+
import Incident from "../../../Models/DatabaseModels/Incident";
|
|
17
|
+
import Alert from "../../../Models/DatabaseModels/Alert";
|
|
18
|
+
import AlertEpisode from "../../../Models/DatabaseModels/AlertEpisode";
|
|
19
|
+
import IncidentEpisode from "../../../Models/DatabaseModels/IncidentEpisode";
|
|
20
|
+
import ShortLink from "../../../Models/DatabaseModels/ShortLink";
|
|
21
|
+
import UserNotificationRule from "../../../Models/DatabaseModels/UserNotificationRule";
|
|
22
|
+
import UserOnCallLogTimeline from "../../../Models/DatabaseModels/UserOnCallLogTimeline";
|
|
23
|
+
import ObjectID from "../../../Types/ObjectID";
|
|
24
|
+
import { JSONObject } from "../../../Types/JSON";
|
|
25
|
+
import UserNotificationEventType from "../../../Types/UserNotification/UserNotificationEventType";
|
|
26
|
+
import UserNotificationStatus from "../../../Types/UserNotification/UserNotificationStatus";
|
|
27
|
+
import {
|
|
28
|
+
WorkspaceMessageBlock,
|
|
29
|
+
WorkspacePayloadMarkdown,
|
|
30
|
+
} from "../../../Types/Workspace/WorkspaceMessagePayload";
|
|
31
|
+
import WorkspaceType from "../../../Types/Workspace/WorkspaceType";
|
|
32
|
+
import { describe, expect, test, beforeEach, afterEach } from "@jest/globals";
|
|
33
|
+
|
|
34
|
+
/*
|
|
35
|
+
* The Slack / Microsoft Teams halves of deliverNotificationForRule, driven
|
|
36
|
+
* through the public executeNotificationRuleItem entrypoint exactly like the
|
|
37
|
+
* older channels' characterisation suite. What is pinned:
|
|
38
|
+
*
|
|
39
|
+
* 1. THE VERIFICATION GATE. A verified method with an address delivers; an
|
|
40
|
+
* unverified one writes exactly one "not verified" Error row and never
|
|
41
|
+
* reaches the sender; a verified relation whose address column was lost
|
|
42
|
+
* does neither (the same silent-skip every channel has).
|
|
43
|
+
*
|
|
44
|
+
* 2. THE TIMELINE ROW. Written BEFORE the send (the message embeds its id
|
|
45
|
+
* via the acknowledge link), stamped with the right method FK column,
|
|
46
|
+
* status Sending, and "Sending Slack message." / "Sending Microsoft
|
|
47
|
+
* Teams message." as its message.
|
|
48
|
+
*
|
|
49
|
+
* 3. THE HANDOFF. The sender is handed the workspace type, the stored
|
|
50
|
+
* workspace user id, the timeline row id (so it can flip Sent/Error
|
|
51
|
+
* itself), and the correlation ids of whatever fired.
|
|
52
|
+
*
|
|
53
|
+
* 4. FIRE-AND-FORGET. A sender rejection is caught and lands on the
|
|
54
|
+
* timeline row as Error; it never rejects the execution.
|
|
55
|
+
*
|
|
56
|
+
* 5. THE EVENT MAP. All four event types deliver on both channels, and the
|
|
57
|
+
* blocks handed over are the event's own generator output (markdown with
|
|
58
|
+
* the acknowledge short-link inside).
|
|
59
|
+
*/
|
|
60
|
+
|
|
61
|
+
const PROJECT_ID: ObjectID = new ObjectID(
|
|
62
|
+
"11111111-1111-4111-8111-111111111111",
|
|
63
|
+
);
|
|
64
|
+
const RULE_ID: ObjectID = new ObjectID("22222222-2222-4222-8222-222222222222");
|
|
65
|
+
const LOG_ID: ObjectID = new ObjectID("33333333-3333-4333-8333-333333333333");
|
|
66
|
+
const USER_ID: ObjectID = new ObjectID("44444444-4444-4444-8444-444444444444");
|
|
67
|
+
const INCIDENT_ID: ObjectID = new ObjectID(
|
|
68
|
+
"55555555-5555-4555-8555-555555555555",
|
|
69
|
+
);
|
|
70
|
+
const ALERT_ID: ObjectID = new ObjectID("66666666-6666-4666-8666-666666666666");
|
|
71
|
+
const ALERT_EPISODE_ID: ObjectID = new ObjectID(
|
|
72
|
+
"77777777-7777-4777-8777-777777777777",
|
|
73
|
+
);
|
|
74
|
+
const INCIDENT_EPISODE_ID: ObjectID = new ObjectID(
|
|
75
|
+
"88888888-8888-4888-8888-888888888888",
|
|
76
|
+
);
|
|
77
|
+
const METHOD_ID: ObjectID = new ObjectID(
|
|
78
|
+
"99999999-9999-4999-8999-999999999999",
|
|
79
|
+
);
|
|
80
|
+
const TIMELINE_ID: ObjectID = new ObjectID(
|
|
81
|
+
"aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
|
|
82
|
+
);
|
|
83
|
+
const POLICY_ID: ObjectID = new ObjectID(
|
|
84
|
+
"bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb",
|
|
85
|
+
);
|
|
86
|
+
const ESCALATION_RULE_ID: ObjectID = new ObjectID(
|
|
87
|
+
"cccccccc-cccc-4ccc-8ccc-cccccccccccc",
|
|
88
|
+
);
|
|
89
|
+
const TEAM_ID: ObjectID = new ObjectID("dddddddd-dddd-4ddd-8ddd-dddddddddddd");
|
|
90
|
+
const SCHEDULE_ID: ObjectID = new ObjectID(
|
|
91
|
+
"eeeeeeee-eeee-4eee-8eee-eeeeeeeeeeee",
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
const SLACK_USER_ID: string = "U0123ABCD";
|
|
95
|
+
const TEAMS_USER_ID: string = "entra-object-id-1";
|
|
96
|
+
const ACK_SHORT_URL: string = "https://oneuptime.example.com/l/abc123";
|
|
97
|
+
|
|
98
|
+
type ExecuteOptions = Parameters<
|
|
99
|
+
typeof UserNotificationRuleService.executeNotificationRuleItem
|
|
100
|
+
>[1];
|
|
101
|
+
|
|
102
|
+
interface TimelineRow {
|
|
103
|
+
status: UserNotificationStatus | undefined;
|
|
104
|
+
statusMessage: string | undefined;
|
|
105
|
+
userSlackId: ObjectID | undefined;
|
|
106
|
+
userMicrosoftTeamsId: ObjectID | undefined;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function flushMicrotasks(): Promise<void> {
|
|
110
|
+
return Promise.resolve()
|
|
111
|
+
.then((): Promise<void> => {
|
|
112
|
+
return Promise.resolve();
|
|
113
|
+
})
|
|
114
|
+
.then((): Promise<void> => {
|
|
115
|
+
return Promise.resolve();
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function executeOptions(
|
|
120
|
+
overrides: Partial<ExecuteOptions> = {},
|
|
121
|
+
): ExecuteOptions {
|
|
122
|
+
return {
|
|
123
|
+
projectId: PROJECT_ID,
|
|
124
|
+
userNotificationEventType: UserNotificationEventType.IncidentCreated,
|
|
125
|
+
triggeredByIncidentId: INCIDENT_ID,
|
|
126
|
+
onCallPolicyId: POLICY_ID,
|
|
127
|
+
onCallPolicyEscalationRuleId: ESCALATION_RULE_ID,
|
|
128
|
+
userBelongsToTeamId: TEAM_ID,
|
|
129
|
+
onCallScheduleId: SCHEDULE_ID,
|
|
130
|
+
userNotificationLogId: LOG_ID,
|
|
131
|
+
...overrides,
|
|
132
|
+
} as ExecuteOptions;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function ruleItem(channels: JSONObject = {}): UserNotificationRule {
|
|
136
|
+
return {
|
|
137
|
+
id: RULE_ID,
|
|
138
|
+
_id: RULE_ID.toString(),
|
|
139
|
+
userId: USER_ID,
|
|
140
|
+
...channels,
|
|
141
|
+
} as unknown as UserNotificationRule;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function verifiedSlack(): JSONObject {
|
|
145
|
+
return {
|
|
146
|
+
userSlack: {
|
|
147
|
+
id: METHOD_ID,
|
|
148
|
+
slackUserId: SLACK_USER_ID,
|
|
149
|
+
slackUserName: "alice",
|
|
150
|
+
isVerified: true,
|
|
151
|
+
},
|
|
152
|
+
} as unknown as JSONObject;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function verifiedTeams(): JSONObject {
|
|
156
|
+
return {
|
|
157
|
+
userMicrosoftTeams: {
|
|
158
|
+
id: METHOD_ID,
|
|
159
|
+
microsoftTeamsUserId: TEAMS_USER_ID,
|
|
160
|
+
microsoftTeamsUserName: "Alice Example",
|
|
161
|
+
isVerified: true,
|
|
162
|
+
},
|
|
163
|
+
} as unknown as JSONObject;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
describe("deliverNotificationForRule - Slack and Microsoft Teams", () => {
|
|
167
|
+
let findRule: jest.SpyInstance;
|
|
168
|
+
let sendDm: jest.SpyInstance;
|
|
169
|
+
let timelineCreate: jest.SpyInstance;
|
|
170
|
+
let timelineUpdate: jest.SpyInstance;
|
|
171
|
+
let timelineRows: Array<TimelineRow>;
|
|
172
|
+
|
|
173
|
+
beforeEach(() => {
|
|
174
|
+
timelineRows = [];
|
|
175
|
+
|
|
176
|
+
jest.spyOn(logger, "error").mockImplementation((): void => {
|
|
177
|
+
return undefined;
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
jest
|
|
181
|
+
.spyOn(UserOnCallLogService, "claimNotificationRuleExecution")
|
|
182
|
+
.mockResolvedValue(true as never);
|
|
183
|
+
|
|
184
|
+
findRule = jest
|
|
185
|
+
.spyOn(UserNotificationRuleService, "findOneById")
|
|
186
|
+
.mockResolvedValue(ruleItem(verifiedSlack()) as never);
|
|
187
|
+
|
|
188
|
+
jest
|
|
189
|
+
.spyOn(ProjectCallSMSConfigService, "getProjectDefaultTwilioConfig")
|
|
190
|
+
.mockResolvedValue(undefined as never);
|
|
191
|
+
|
|
192
|
+
jest.spyOn(IncidentService, "findOneById").mockResolvedValue({
|
|
193
|
+
id: INCIDENT_ID,
|
|
194
|
+
projectId: PROJECT_ID,
|
|
195
|
+
title: "Checkout is down",
|
|
196
|
+
incidentNumber: 42,
|
|
197
|
+
incidentNumberWithPrefix: "INC-42",
|
|
198
|
+
} as unknown as Incident as never);
|
|
199
|
+
|
|
200
|
+
jest.spyOn(AlertService, "findOneById").mockResolvedValue({
|
|
201
|
+
id: ALERT_ID,
|
|
202
|
+
projectId: PROJECT_ID,
|
|
203
|
+
title: "Disk almost full",
|
|
204
|
+
alertNumber: 7,
|
|
205
|
+
alertNumberWithPrefix: "ALR-7",
|
|
206
|
+
} as unknown as Alert as never);
|
|
207
|
+
|
|
208
|
+
jest.spyOn(AlertEpisodeService, "findOneById").mockResolvedValue({
|
|
209
|
+
id: ALERT_EPISODE_ID,
|
|
210
|
+
projectId: PROJECT_ID,
|
|
211
|
+
title: "Flapping disk alerts",
|
|
212
|
+
episodeNumber: 3,
|
|
213
|
+
episodeNumberWithPrefix: "AEP-3",
|
|
214
|
+
} as unknown as AlertEpisode as never);
|
|
215
|
+
|
|
216
|
+
jest.spyOn(IncidentEpisodeService, "findOneById").mockResolvedValue({
|
|
217
|
+
id: INCIDENT_EPISODE_ID,
|
|
218
|
+
projectId: PROJECT_ID,
|
|
219
|
+
title: "Checkout instability",
|
|
220
|
+
episodeNumber: 9,
|
|
221
|
+
episodeNumberWithPrefix: "IEP-9",
|
|
222
|
+
} as unknown as IncidentEpisode as never);
|
|
223
|
+
|
|
224
|
+
/*
|
|
225
|
+
* The dashboard-link and short-link plumbing the block generators sit on.
|
|
226
|
+
* Stubbing at this level (rather than stubbing the generators) keeps the
|
|
227
|
+
* real generator code in the loop, so the assertions below are about what
|
|
228
|
+
* an actual page carries.
|
|
229
|
+
*/
|
|
230
|
+
jest
|
|
231
|
+
.spyOn(DatabaseConfig, "getHost")
|
|
232
|
+
.mockResolvedValue(new Hostname("oneuptime.example.com") as never);
|
|
233
|
+
jest
|
|
234
|
+
.spyOn(DatabaseConfig, "getHttpProtocol")
|
|
235
|
+
.mockResolvedValue(Protocol.HTTPS as never);
|
|
236
|
+
jest
|
|
237
|
+
.spyOn(ShortLinkService, "saveShortLinkFor")
|
|
238
|
+
.mockResolvedValue({} as ShortLink as never);
|
|
239
|
+
jest
|
|
240
|
+
.spyOn(ShortLinkService, "getShortenedUrl")
|
|
241
|
+
.mockResolvedValue(URL.fromString(ACK_SHORT_URL) as never);
|
|
242
|
+
jest
|
|
243
|
+
.spyOn(IncidentService, "getIncidentLinkInDashboard")
|
|
244
|
+
.mockResolvedValue(
|
|
245
|
+
URL.fromString(
|
|
246
|
+
"https://oneuptime.example.com/dashboard/incident",
|
|
247
|
+
) as never,
|
|
248
|
+
);
|
|
249
|
+
jest
|
|
250
|
+
.spyOn(AlertService, "getAlertLinkInDashboard")
|
|
251
|
+
.mockResolvedValue(
|
|
252
|
+
URL.fromString(
|
|
253
|
+
"https://oneuptime.example.com/dashboard/alert",
|
|
254
|
+
) as never,
|
|
255
|
+
);
|
|
256
|
+
jest
|
|
257
|
+
.spyOn(AlertEpisodeService, "getEpisodeLinkInDashboard")
|
|
258
|
+
.mockResolvedValue(
|
|
259
|
+
URL.fromString(
|
|
260
|
+
"https://oneuptime.example.com/dashboard/alert-episode",
|
|
261
|
+
) as never,
|
|
262
|
+
);
|
|
263
|
+
jest
|
|
264
|
+
.spyOn(IncidentEpisodeService, "getEpisodeLinkInDashboard")
|
|
265
|
+
.mockResolvedValue(
|
|
266
|
+
URL.fromString(
|
|
267
|
+
"https://oneuptime.example.com/dashboard/incident-episode",
|
|
268
|
+
) as never,
|
|
269
|
+
);
|
|
270
|
+
|
|
271
|
+
sendDm = jest
|
|
272
|
+
.spyOn(WorkspaceUserNotificationService, "sendDirectMessageToUser")
|
|
273
|
+
.mockResolvedValue(undefined as never);
|
|
274
|
+
|
|
275
|
+
timelineCreate = jest.spyOn(UserOnCallLogTimelineService, "create");
|
|
276
|
+
timelineCreate.mockImplementation(
|
|
277
|
+
(createByArg: unknown): Promise<UserOnCallLogTimeline> => {
|
|
278
|
+
const data: UserOnCallLogTimeline = (
|
|
279
|
+
createByArg as { data: UserOnCallLogTimeline }
|
|
280
|
+
).data;
|
|
281
|
+
|
|
282
|
+
timelineRows.push({
|
|
283
|
+
status: data.status,
|
|
284
|
+
statusMessage: data.statusMessage,
|
|
285
|
+
userSlackId: data.userSlackId,
|
|
286
|
+
userMicrosoftTeamsId: data.userMicrosoftTeamsId,
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
return Promise.resolve({
|
|
290
|
+
id: TIMELINE_ID,
|
|
291
|
+
} as unknown as UserOnCallLogTimeline);
|
|
292
|
+
},
|
|
293
|
+
);
|
|
294
|
+
|
|
295
|
+
timelineUpdate = jest
|
|
296
|
+
.spyOn(UserOnCallLogTimelineService, "updateOneById")
|
|
297
|
+
.mockResolvedValue(undefined as never);
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
afterEach(() => {
|
|
301
|
+
jest.restoreAllMocks();
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
function execute(overrides: Partial<ExecuteOptions> = {}): Promise<void> {
|
|
305
|
+
return UserNotificationRuleService.executeNotificationRuleItem(
|
|
306
|
+
RULE_ID,
|
|
307
|
+
executeOptions(overrides),
|
|
308
|
+
);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
interface CapturedSendArg {
|
|
312
|
+
projectId: ObjectID;
|
|
313
|
+
workspaceType: WorkspaceType;
|
|
314
|
+
workspaceUserId: string;
|
|
315
|
+
messageBlocks: Array<WorkspaceMessageBlock>;
|
|
316
|
+
userId: ObjectID;
|
|
317
|
+
userOnCallLogTimelineId: ObjectID;
|
|
318
|
+
incidentId?: ObjectID;
|
|
319
|
+
alertId?: ObjectID;
|
|
320
|
+
alertEpisodeId?: ObjectID;
|
|
321
|
+
incidentEpisodeId?: ObjectID;
|
|
322
|
+
onCallPolicyId?: ObjectID;
|
|
323
|
+
onCallPolicyEscalationRuleId?: ObjectID;
|
|
324
|
+
teamId?: ObjectID;
|
|
325
|
+
onCallScheduleId?: ObjectID;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
function sendArg(): CapturedSendArg {
|
|
329
|
+
return sendDm.mock.calls[0][0] as CapturedSendArg;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function markdownText(): string {
|
|
333
|
+
const blocks: Array<WorkspaceMessageBlock> = sendArg().messageBlocks;
|
|
334
|
+
return (blocks[0] as WorkspacePayloadMarkdown).text;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/*
|
|
338
|
+
* ----------------------------------------------------------------------- *
|
|
339
|
+
* (A) The Slack happy path, incident created.
|
|
340
|
+
* -----------------------------------------------------------------------
|
|
341
|
+
*/
|
|
342
|
+
|
|
343
|
+
describe("a verified Slack method on an IncidentCreated page", () => {
|
|
344
|
+
test("creates the timeline row FIRST (Sending, stamped with userSlackId), then sends", async () => {
|
|
345
|
+
await execute();
|
|
346
|
+
|
|
347
|
+
expect(timelineRows).toHaveLength(1);
|
|
348
|
+
expect(timelineRows[0]?.status).toBe(UserNotificationStatus.Sending);
|
|
349
|
+
expect(timelineRows[0]?.statusMessage).toBe("Sending Slack message.");
|
|
350
|
+
expect(timelineRows[0]?.userSlackId?.toString()).toBe(
|
|
351
|
+
METHOD_ID.toString(),
|
|
352
|
+
);
|
|
353
|
+
|
|
354
|
+
expect(timelineCreate.mock.invocationCallOrder[0] as number).toBeLessThan(
|
|
355
|
+
sendDm.mock.invocationCallOrder[0] as number,
|
|
356
|
+
);
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
test("hands the sender the Slack workspace type, stored member id, and the timeline id", async () => {
|
|
360
|
+
await execute();
|
|
361
|
+
|
|
362
|
+
expect(sendDm).toHaveBeenCalledTimes(1);
|
|
363
|
+
const arg: CapturedSendArg = sendArg();
|
|
364
|
+
expect(arg.workspaceType).toBe(WorkspaceType.Slack);
|
|
365
|
+
expect(arg.workspaceUserId).toBe(SLACK_USER_ID);
|
|
366
|
+
expect(arg.userOnCallLogTimelineId.toString()).toBe(
|
|
367
|
+
TIMELINE_ID.toString(),
|
|
368
|
+
);
|
|
369
|
+
expect(arg.userId.toString()).toBe(USER_ID.toString());
|
|
370
|
+
expect(arg.projectId.toString()).toBe(PROJECT_ID.toString());
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
test("forwards every on-call correlation id the send should be attributed to", async () => {
|
|
374
|
+
await execute();
|
|
375
|
+
|
|
376
|
+
const arg: CapturedSendArg = sendArg();
|
|
377
|
+
expect(arg.incidentId?.toString()).toBe(INCIDENT_ID.toString());
|
|
378
|
+
expect(arg.onCallPolicyId?.toString()).toBe(POLICY_ID.toString());
|
|
379
|
+
expect(arg.onCallPolicyEscalationRuleId?.toString()).toBe(
|
|
380
|
+
ESCALATION_RULE_ID.toString(),
|
|
381
|
+
);
|
|
382
|
+
expect(arg.teamId?.toString()).toBe(TEAM_ID.toString());
|
|
383
|
+
expect(arg.onCallScheduleId?.toString()).toBe(SCHEDULE_ID.toString());
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
test("the message is markdown carrying the incident identifier and the acknowledge short-link", async () => {
|
|
387
|
+
await execute();
|
|
388
|
+
|
|
389
|
+
const text: string = markdownText();
|
|
390
|
+
expect(text).toContain("New incident assigned to you");
|
|
391
|
+
expect(text).toContain("INC-42");
|
|
392
|
+
expect(text).toContain("Checkout is down");
|
|
393
|
+
expect(text).toContain("You're getting this because you're on call.");
|
|
394
|
+
expect(text).toContain(ACK_SHORT_URL);
|
|
395
|
+
expect(text).toContain(
|
|
396
|
+
"https://oneuptime.example.com/dashboard/incident",
|
|
397
|
+
);
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
test("the message sticks to block types both platforms implement (markdown only)", async () => {
|
|
401
|
+
await execute();
|
|
402
|
+
|
|
403
|
+
for (const block of sendArg().messageBlocks) {
|
|
404
|
+
expect(block._type).toBe("WorkspacePayloadMarkdown");
|
|
405
|
+
}
|
|
406
|
+
});
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
/*
|
|
410
|
+
* ----------------------------------------------------------------------- *
|
|
411
|
+
* (B) The Microsoft Teams path.
|
|
412
|
+
* -----------------------------------------------------------------------
|
|
413
|
+
*/
|
|
414
|
+
|
|
415
|
+
describe("a verified Microsoft Teams method", () => {
|
|
416
|
+
beforeEach(() => {
|
|
417
|
+
findRule.mockResolvedValue(ruleItem(verifiedTeams()) as never);
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
test("delivers with the MicrosoftTeams workspace type and the stored Entra id", async () => {
|
|
421
|
+
await execute();
|
|
422
|
+
|
|
423
|
+
const arg: CapturedSendArg = sendArg();
|
|
424
|
+
expect(arg.workspaceType).toBe(WorkspaceType.MicrosoftTeams);
|
|
425
|
+
expect(arg.workspaceUserId).toBe(TEAMS_USER_ID);
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
test("its timeline row is stamped with userMicrosoftTeamsId and the Teams sending message", async () => {
|
|
429
|
+
await execute();
|
|
430
|
+
|
|
431
|
+
expect(timelineRows).toHaveLength(1);
|
|
432
|
+
expect(timelineRows[0]?.statusMessage).toBe(
|
|
433
|
+
"Sending Microsoft Teams message.",
|
|
434
|
+
);
|
|
435
|
+
expect(timelineRows[0]?.userMicrosoftTeamsId?.toString()).toBe(
|
|
436
|
+
METHOD_ID.toString(),
|
|
437
|
+
);
|
|
438
|
+
expect(timelineRows[0]?.userSlackId).toBeUndefined();
|
|
439
|
+
});
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
/*
|
|
443
|
+
* ----------------------------------------------------------------------- *
|
|
444
|
+
* (C) The verification gate.
|
|
445
|
+
* -----------------------------------------------------------------------
|
|
446
|
+
*/
|
|
447
|
+
|
|
448
|
+
describe("the verification gate", () => {
|
|
449
|
+
test("an unverified Slack method writes one 'not verified' Error row and never sends", async () => {
|
|
450
|
+
findRule.mockResolvedValue(
|
|
451
|
+
ruleItem({
|
|
452
|
+
userSlack: {
|
|
453
|
+
id: METHOD_ID,
|
|
454
|
+
slackUserId: SLACK_USER_ID,
|
|
455
|
+
isVerified: false,
|
|
456
|
+
},
|
|
457
|
+
} as unknown as JSONObject) as never,
|
|
458
|
+
);
|
|
459
|
+
|
|
460
|
+
await execute();
|
|
461
|
+
|
|
462
|
+
expect(sendDm).not.toHaveBeenCalled();
|
|
463
|
+
expect(timelineRows).toHaveLength(1);
|
|
464
|
+
expect(timelineRows[0]?.status).toBe(UserNotificationStatus.Error);
|
|
465
|
+
expect(timelineRows[0]?.statusMessage).toBe(
|
|
466
|
+
"Slack message not sent because the Slack account is not verified.",
|
|
467
|
+
);
|
|
468
|
+
expect(timelineRows[0]?.userSlackId?.toString()).toBe(
|
|
469
|
+
METHOD_ID.toString(),
|
|
470
|
+
);
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
test("an unverified Microsoft Teams method behaves the same, with its own message", async () => {
|
|
474
|
+
findRule.mockResolvedValue(
|
|
475
|
+
ruleItem({
|
|
476
|
+
userMicrosoftTeams: {
|
|
477
|
+
id: METHOD_ID,
|
|
478
|
+
microsoftTeamsUserId: TEAMS_USER_ID,
|
|
479
|
+
isVerified: false,
|
|
480
|
+
},
|
|
481
|
+
} as unknown as JSONObject) as never,
|
|
482
|
+
);
|
|
483
|
+
|
|
484
|
+
await execute();
|
|
485
|
+
|
|
486
|
+
expect(sendDm).not.toHaveBeenCalled();
|
|
487
|
+
expect(timelineRows[0]?.statusMessage).toBe(
|
|
488
|
+
"Microsoft Teams message not sent because the Microsoft Teams account is not verified.",
|
|
489
|
+
);
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
test("a verified relation whose address column is missing sends nothing (the same silent skip as every channel)", async () => {
|
|
493
|
+
findRule.mockResolvedValue(
|
|
494
|
+
ruleItem({
|
|
495
|
+
userSlack: {
|
|
496
|
+
id: METHOD_ID,
|
|
497
|
+
isVerified: true,
|
|
498
|
+
},
|
|
499
|
+
} as unknown as JSONObject) as never,
|
|
500
|
+
);
|
|
501
|
+
|
|
502
|
+
await execute();
|
|
503
|
+
|
|
504
|
+
expect(sendDm).not.toHaveBeenCalled();
|
|
505
|
+
expect(timelineRows).toHaveLength(0);
|
|
506
|
+
});
|
|
507
|
+
|
|
508
|
+
test("the unverified guard keys off the RELATION, not the address - a row with no member id still errors", async () => {
|
|
509
|
+
findRule.mockResolvedValue(
|
|
510
|
+
ruleItem({
|
|
511
|
+
userSlack: {
|
|
512
|
+
id: METHOD_ID,
|
|
513
|
+
isVerified: false,
|
|
514
|
+
},
|
|
515
|
+
} as unknown as JSONObject) as never,
|
|
516
|
+
);
|
|
517
|
+
|
|
518
|
+
await execute();
|
|
519
|
+
|
|
520
|
+
expect(timelineRows).toHaveLength(1);
|
|
521
|
+
expect(timelineRows[0]?.statusMessage).toContain("not verified");
|
|
522
|
+
});
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
/*
|
|
526
|
+
* ----------------------------------------------------------------------- *
|
|
527
|
+
* (D) Fire-and-forget failure handling.
|
|
528
|
+
* -----------------------------------------------------------------------
|
|
529
|
+
*/
|
|
530
|
+
|
|
531
|
+
describe("fire-and-forget failure handling", () => {
|
|
532
|
+
test("a sender rejection is caught and flips the timeline row to Error with the sender's message", async () => {
|
|
533
|
+
sendDm.mockRejectedValue(new Error("slack workspace revoked") as never);
|
|
534
|
+
|
|
535
|
+
await expect(execute()).resolves.toBeUndefined();
|
|
536
|
+
await flushMicrotasks();
|
|
537
|
+
|
|
538
|
+
expect(timelineUpdate).toHaveBeenCalledTimes(1);
|
|
539
|
+
const arg: {
|
|
540
|
+
id: ObjectID;
|
|
541
|
+
data: { status: UserNotificationStatus; statusMessage: string };
|
|
542
|
+
} = timelineUpdate.mock.calls[0][0] as {
|
|
543
|
+
id: ObjectID;
|
|
544
|
+
data: { status: UserNotificationStatus; statusMessage: string };
|
|
545
|
+
};
|
|
546
|
+
expect(arg.id.toString()).toBe(TIMELINE_ID.toString());
|
|
547
|
+
expect(arg.data.status).toBe(UserNotificationStatus.Error);
|
|
548
|
+
expect(arg.data.statusMessage).toBe("slack workspace revoked");
|
|
549
|
+
});
|
|
550
|
+
|
|
551
|
+
test("a rejection with no message falls back to the channel-named default", async () => {
|
|
552
|
+
sendDm.mockRejectedValue(new Error("") as never);
|
|
553
|
+
|
|
554
|
+
await execute();
|
|
555
|
+
await flushMicrotasks();
|
|
556
|
+
|
|
557
|
+
const arg: { data: { statusMessage: string } } = timelineUpdate.mock
|
|
558
|
+
.calls[0][0] as { data: { statusMessage: string } };
|
|
559
|
+
expect(arg.data.statusMessage).toBe("Error sending Slack message.");
|
|
560
|
+
});
|
|
561
|
+
});
|
|
562
|
+
|
|
563
|
+
/*
|
|
564
|
+
* ----------------------------------------------------------------------- *
|
|
565
|
+
* (E) The event map — all four event types deliver, on the right entity.
|
|
566
|
+
* -----------------------------------------------------------------------
|
|
567
|
+
*/
|
|
568
|
+
|
|
569
|
+
describe("the event map", () => {
|
|
570
|
+
test("AlertCreated delivers the alert message with the alert correlation id", async () => {
|
|
571
|
+
await execute({
|
|
572
|
+
userNotificationEventType: UserNotificationEventType.AlertCreated,
|
|
573
|
+
triggeredByIncidentId: undefined,
|
|
574
|
+
triggeredByAlertId: ALERT_ID,
|
|
575
|
+
});
|
|
576
|
+
|
|
577
|
+
const arg: CapturedSendArg = sendArg();
|
|
578
|
+
expect(arg.alertId?.toString()).toBe(ALERT_ID.toString());
|
|
579
|
+
expect(arg.incidentId).toBeUndefined();
|
|
580
|
+
expect(markdownText()).toContain("New alert assigned to you");
|
|
581
|
+
expect(markdownText()).toContain("ALR-7");
|
|
582
|
+
});
|
|
583
|
+
|
|
584
|
+
test("AlertEpisodeCreated delivers the alert-episode message", async () => {
|
|
585
|
+
await execute({
|
|
586
|
+
userNotificationEventType:
|
|
587
|
+
UserNotificationEventType.AlertEpisodeCreated,
|
|
588
|
+
triggeredByIncidentId: undefined,
|
|
589
|
+
triggeredByAlertEpisodeId: ALERT_EPISODE_ID,
|
|
590
|
+
});
|
|
591
|
+
|
|
592
|
+
const arg: CapturedSendArg = sendArg();
|
|
593
|
+
expect(arg.alertEpisodeId?.toString()).toBe(ALERT_EPISODE_ID.toString());
|
|
594
|
+
expect(markdownText()).toContain("New alert episode assigned to you");
|
|
595
|
+
expect(markdownText()).toContain("AEP-3");
|
|
596
|
+
});
|
|
597
|
+
|
|
598
|
+
test("IncidentEpisodeCreated delivers the incident-episode message", async () => {
|
|
599
|
+
await execute({
|
|
600
|
+
userNotificationEventType:
|
|
601
|
+
UserNotificationEventType.IncidentEpisodeCreated,
|
|
602
|
+
triggeredByIncidentId: undefined,
|
|
603
|
+
triggeredByIncidentEpisodeId: INCIDENT_EPISODE_ID,
|
|
604
|
+
});
|
|
605
|
+
|
|
606
|
+
const arg: CapturedSendArg = sendArg();
|
|
607
|
+
expect(arg.incidentEpisodeId?.toString()).toBe(
|
|
608
|
+
INCIDENT_EPISODE_ID.toString(),
|
|
609
|
+
);
|
|
610
|
+
expect(markdownText()).toContain("New incident episode assigned to you");
|
|
611
|
+
expect(markdownText()).toContain("IEP-9");
|
|
612
|
+
});
|
|
613
|
+
|
|
614
|
+
test("every event's message carries the acknowledge short-link - the whole point of writing the row first", async () => {
|
|
615
|
+
for (const eventOverrides of [
|
|
616
|
+
{
|
|
617
|
+
userNotificationEventType: UserNotificationEventType.AlertCreated,
|
|
618
|
+
triggeredByIncidentId: undefined,
|
|
619
|
+
triggeredByAlertId: ALERT_ID,
|
|
620
|
+
},
|
|
621
|
+
{
|
|
622
|
+
userNotificationEventType:
|
|
623
|
+
UserNotificationEventType.AlertEpisodeCreated,
|
|
624
|
+
triggeredByIncidentId: undefined,
|
|
625
|
+
triggeredByAlertEpisodeId: ALERT_EPISODE_ID,
|
|
626
|
+
},
|
|
627
|
+
{
|
|
628
|
+
userNotificationEventType:
|
|
629
|
+
UserNotificationEventType.IncidentEpisodeCreated,
|
|
630
|
+
triggeredByIncidentId: undefined,
|
|
631
|
+
triggeredByIncidentEpisodeId: INCIDENT_EPISODE_ID,
|
|
632
|
+
},
|
|
633
|
+
] as Array<Partial<ExecuteOptions>>) {
|
|
634
|
+
sendDm.mockClear();
|
|
635
|
+
await execute(eventOverrides);
|
|
636
|
+
expect(markdownText()).toContain(ACK_SHORT_URL);
|
|
637
|
+
}
|
|
638
|
+
});
|
|
639
|
+
});
|
|
640
|
+
|
|
641
|
+
/*
|
|
642
|
+
* ----------------------------------------------------------------------- *
|
|
643
|
+
* (F) Both channels on one rule.
|
|
644
|
+
* -----------------------------------------------------------------------
|
|
645
|
+
*/
|
|
646
|
+
|
|
647
|
+
describe("a rule carrying both workspace methods", () => {
|
|
648
|
+
test("delivers on both, one timeline row and one send per channel", async () => {
|
|
649
|
+
findRule.mockResolvedValue(
|
|
650
|
+
ruleItem({
|
|
651
|
+
...verifiedSlack(),
|
|
652
|
+
...verifiedTeams(),
|
|
653
|
+
}) as never,
|
|
654
|
+
);
|
|
655
|
+
|
|
656
|
+
await execute();
|
|
657
|
+
|
|
658
|
+
expect(sendDm).toHaveBeenCalledTimes(2);
|
|
659
|
+
|
|
660
|
+
const workspaceTypes: Array<WorkspaceType> = sendDm.mock.calls.map(
|
|
661
|
+
(call: Array<unknown>) => {
|
|
662
|
+
return (call[0] as { workspaceType: WorkspaceType }).workspaceType;
|
|
663
|
+
},
|
|
664
|
+
);
|
|
665
|
+
expect(workspaceTypes).toContain(WorkspaceType.Slack);
|
|
666
|
+
expect(workspaceTypes).toContain(WorkspaceType.MicrosoftTeams);
|
|
667
|
+
});
|
|
668
|
+
});
|
|
669
|
+
});
|