@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.
- 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/BillingService.ts +8 -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/App/Dashboard/PayAsYouGoNotices.test.tsx +17 -0
- 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/BillingService.test.ts +20 -0
- 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/Types/Kubernetes/KubernetesObjectParsers.test.ts +794 -0
- package/Tests/Types/Kubernetes/KubernetesRightSizing.test.ts +53 -0
- package/Tests/Types/Measurement/MeasurementAggregationType.test.ts +100 -0
- package/Tests/Types/Monitor/MonitorStepConfigHelpers.test.ts +114 -0
- package/Tests/Types/Workspace/WorkspaceType.test.ts +53 -0
- package/Tests/UI/Utils/NotificationMethodUtil.test.ts +137 -9
- package/Tests/UI/Utils/PermissionGate.test.ts +105 -0
- package/Tests/Utils/Dashboard/Components/DashboardComponentDefaults.test.ts +257 -0
- package/Types/Billing/PayAsYouGoPricing.ts +1 -1
- 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/BillingService.js +6 -0
- package/build/dist/Server/Services/BillingService.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/Billing/PayAsYouGoPricing.js +1 -1
- package/build/dist/Types/Billing/PayAsYouGoPricing.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,407 @@
|
|
|
1
|
+
import UserSlackService from "../../../Server/Services/UserSlackService";
|
|
2
|
+
import UserNotificationRuleService from "../../../Server/Services/UserNotificationRuleService";
|
|
3
|
+
import WorkspaceProjectAuthTokenService from "../../../Server/Services/WorkspaceProjectAuthTokenService";
|
|
4
|
+
import WorkspaceUserAuthTokenService from "../../../Server/Services/WorkspaceUserAuthTokenService";
|
|
5
|
+
import SlackUtil from "../../../Server/Utils/Workspace/Slack/Slack";
|
|
6
|
+
import logger from "../../../Server/Utils/Logger";
|
|
7
|
+
import CreateBy from "../../../Server/Types/Database/CreateBy";
|
|
8
|
+
import { OnCreate } from "../../../Server/Types/Database/Hooks";
|
|
9
|
+
import UserSlack from "../../../Models/DatabaseModels/UserSlack";
|
|
10
|
+
import WorkspaceProjectAuthToken from "../../../Models/DatabaseModels/WorkspaceProjectAuthToken";
|
|
11
|
+
import WorkspaceUserAuthToken from "../../../Models/DatabaseModels/WorkspaceUserAuthToken";
|
|
12
|
+
import BadDataException from "../../../Types/Exception/BadDataException";
|
|
13
|
+
import ObjectID from "../../../Types/ObjectID";
|
|
14
|
+
import PositiveNumber from "../../../Types/PositiveNumber";
|
|
15
|
+
import LIMIT_MAX from "../../../Types/Database/LimitMax";
|
|
16
|
+
import WorkspaceType from "../../../Types/Workspace/WorkspaceType";
|
|
17
|
+
import { describe, expect, test, beforeEach, afterEach } from "@jest/globals";
|
|
18
|
+
|
|
19
|
+
/*
|
|
20
|
+
* A UserSlack row is a POINTER at the user's existing OAuth workspace link,
|
|
21
|
+
* never a hand-typed address. Everything pinned here follows from that:
|
|
22
|
+
*
|
|
23
|
+
* 1. CREATION IS VERIFICATION. onBeforeCreate resolves the Slack member id
|
|
24
|
+
* from the caller's OWN WorkspaceUserAuthToken and stamps
|
|
25
|
+
* isVerified: true. A client-supplied slackUserId or isVerified is
|
|
26
|
+
* refused outright — accepting either would let a caller aim a bot at an
|
|
27
|
+
* arbitrary member id without ever proving they control that account.
|
|
28
|
+
*
|
|
29
|
+
* 2. THE GATES. No project connection, no user connection, or an existing
|
|
30
|
+
* row each refuse the create with a message that names the settings page
|
|
31
|
+
* that fixes it.
|
|
32
|
+
*
|
|
33
|
+
* 3. THE SEEDING. A row is born verified, so default on-call rules are
|
|
34
|
+
* seeded at create time (like webhooks) — and a seeding failure is
|
|
35
|
+
* logged, never thrown, because the method row itself is real.
|
|
36
|
+
*
|
|
37
|
+
* 4. THE CASCADE. Deleting the method deletes every rule pointing at it via
|
|
38
|
+
* the service hook, in addition to the FK cascade.
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
const PROJECT_ID: ObjectID = new ObjectID(
|
|
42
|
+
"11111111-1111-4111-8111-111111111111",
|
|
43
|
+
);
|
|
44
|
+
const USER_ID: ObjectID = new ObjectID("22222222-2222-4222-8222-222222222222");
|
|
45
|
+
const METHOD_ID: ObjectID = new ObjectID(
|
|
46
|
+
"33333333-3333-4333-8333-333333333333",
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
const SLACK_MEMBER_ID: string = "U0123ABCD";
|
|
50
|
+
const BOT_TOKEN: string = "xoxb-project-bot-token";
|
|
51
|
+
|
|
52
|
+
type OnBeforeCreateFunction = (
|
|
53
|
+
createBy: CreateBy<UserSlack>,
|
|
54
|
+
) => Promise<OnCreate<UserSlack>>;
|
|
55
|
+
|
|
56
|
+
type OnCreateSuccessFunction = (
|
|
57
|
+
onCreate: OnCreate<UserSlack>,
|
|
58
|
+
createdItem: UserSlack,
|
|
59
|
+
) => Promise<UserSlack>;
|
|
60
|
+
|
|
61
|
+
type OnBeforeDeleteFunction = (deleteBy: {
|
|
62
|
+
query: Record<string, unknown>;
|
|
63
|
+
props: { isRoot: boolean };
|
|
64
|
+
}) => Promise<unknown>;
|
|
65
|
+
|
|
66
|
+
function callOnBeforeCreate(
|
|
67
|
+
createBy: CreateBy<UserSlack>,
|
|
68
|
+
): Promise<OnCreate<UserSlack>> {
|
|
69
|
+
return (
|
|
70
|
+
UserSlackService as unknown as { onBeforeCreate: OnBeforeCreateFunction }
|
|
71
|
+
).onBeforeCreate(createBy);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function callOnCreateSuccess(
|
|
75
|
+
onCreate: OnCreate<UserSlack>,
|
|
76
|
+
createdItem: UserSlack,
|
|
77
|
+
): Promise<UserSlack> {
|
|
78
|
+
return (
|
|
79
|
+
UserSlackService as unknown as {
|
|
80
|
+
onCreateSuccess: OnCreateSuccessFunction;
|
|
81
|
+
}
|
|
82
|
+
).onCreateSuccess(onCreate, createdItem);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function callOnBeforeDelete(deleteBy: {
|
|
86
|
+
query: Record<string, unknown>;
|
|
87
|
+
props: { isRoot: boolean };
|
|
88
|
+
}): Promise<unknown> {
|
|
89
|
+
return (
|
|
90
|
+
UserSlackService as unknown as { onBeforeDelete: OnBeforeDeleteFunction }
|
|
91
|
+
).onBeforeDelete(deleteBy);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function createBy(
|
|
95
|
+
data: Partial<UserSlack> = {},
|
|
96
|
+
props: { isRoot: boolean } = { isRoot: false },
|
|
97
|
+
): CreateBy<UserSlack> {
|
|
98
|
+
return {
|
|
99
|
+
data: {
|
|
100
|
+
projectId: PROJECT_ID,
|
|
101
|
+
userId: USER_ID,
|
|
102
|
+
...data,
|
|
103
|
+
} as UserSlack,
|
|
104
|
+
props: props,
|
|
105
|
+
} as CreateBy<UserSlack>;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
describe("UserSlackService", () => {
|
|
109
|
+
let countBy: jest.SpyInstance;
|
|
110
|
+
let getProjectAuth: jest.SpyInstance;
|
|
111
|
+
let getUserAuth: jest.SpyInstance;
|
|
112
|
+
let getUsername: jest.SpyInstance;
|
|
113
|
+
let loggerError: jest.SpyInstance;
|
|
114
|
+
|
|
115
|
+
beforeEach(() => {
|
|
116
|
+
loggerError = jest.spyOn(logger, "error").mockImplementation((): void => {
|
|
117
|
+
return undefined;
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
countBy = jest
|
|
121
|
+
.spyOn(UserSlackService, "countBy")
|
|
122
|
+
.mockResolvedValue(new PositiveNumber(0) as never);
|
|
123
|
+
|
|
124
|
+
getProjectAuth = jest
|
|
125
|
+
.spyOn(WorkspaceProjectAuthTokenService, "getProjectAuth")
|
|
126
|
+
.mockResolvedValue({
|
|
127
|
+
id: PROJECT_ID,
|
|
128
|
+
authToken: BOT_TOKEN,
|
|
129
|
+
} as unknown as WorkspaceProjectAuthToken as never);
|
|
130
|
+
|
|
131
|
+
getUserAuth = jest
|
|
132
|
+
.spyOn(WorkspaceUserAuthTokenService, "getUserAuth")
|
|
133
|
+
.mockResolvedValue({
|
|
134
|
+
workspaceUserId: SLACK_MEMBER_ID,
|
|
135
|
+
} as unknown as WorkspaceUserAuthToken as never);
|
|
136
|
+
|
|
137
|
+
getUsername = jest
|
|
138
|
+
.spyOn(SlackUtil, "getUsernameFromUserId")
|
|
139
|
+
.mockResolvedValue("alice" as never);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
afterEach(() => {
|
|
143
|
+
jest.restoreAllMocks();
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
/*
|
|
147
|
+
* ----------------------------------------------------------------------- *
|
|
148
|
+
* (A) onBeforeCreate — creation is verification.
|
|
149
|
+
* -----------------------------------------------------------------------
|
|
150
|
+
*/
|
|
151
|
+
|
|
152
|
+
describe("onBeforeCreate", () => {
|
|
153
|
+
test("resolves the member id from the user's own workspace link and stamps verified", async () => {
|
|
154
|
+
const result: OnCreate<UserSlack> = await callOnBeforeCreate(createBy());
|
|
155
|
+
|
|
156
|
+
expect(result.createBy.data.slackUserId).toBe(SLACK_MEMBER_ID);
|
|
157
|
+
expect(result.createBy.data.isVerified).toBe(true);
|
|
158
|
+
expect(result.createBy.data.slackUserName).toBe("alice");
|
|
159
|
+
|
|
160
|
+
// The link is resolved for exactly this (project, user, Slack).
|
|
161
|
+
const authArg: {
|
|
162
|
+
projectId: ObjectID;
|
|
163
|
+
userId: ObjectID;
|
|
164
|
+
workspaceType: WorkspaceType;
|
|
165
|
+
} = getUserAuth.mock.calls[0][0] as {
|
|
166
|
+
projectId: ObjectID;
|
|
167
|
+
userId: ObjectID;
|
|
168
|
+
workspaceType: WorkspaceType;
|
|
169
|
+
};
|
|
170
|
+
expect(authArg.projectId.toString()).toBe(PROJECT_ID.toString());
|
|
171
|
+
expect(authArg.userId.toString()).toBe(USER_ID.toString());
|
|
172
|
+
expect(authArg.workspaceType).toBe(WorkspaceType.Slack);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
test("a non-root caller supplying isVerified is refused", async () => {
|
|
176
|
+
await expect(
|
|
177
|
+
callOnBeforeCreate(createBy({ isVerified: true })),
|
|
178
|
+
).rejects.toThrow("isVerified cannot be set to true");
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
test("a non-root caller supplying slackUserId is refused - the address is never client input", async () => {
|
|
182
|
+
await expect(
|
|
183
|
+
callOnBeforeCreate(createBy({ slackUserId: "U-ATTACKER" })),
|
|
184
|
+
).rejects.toThrow("slackUserId cannot be set directly");
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
test("a project that is not connected to Slack is refused with the settings pointer", async () => {
|
|
188
|
+
getProjectAuth.mockResolvedValue(null as never);
|
|
189
|
+
|
|
190
|
+
await expect(callOnBeforeCreate(createBy())).rejects.toThrow(
|
|
191
|
+
"This project is not connected to Slack. Please ask a project admin to connect Slack in Project Settings > Slack Integration.",
|
|
192
|
+
);
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
test("a user who has not connected their Slack account is refused with the settings pointer", async () => {
|
|
196
|
+
getUserAuth.mockResolvedValue(null as never);
|
|
197
|
+
|
|
198
|
+
await expect(callOnBeforeCreate(createBy())).rejects.toThrow(
|
|
199
|
+
"Your Slack account is not connected to OneUptime for this project. Please go to User Settings > Slack Integration and connect your Slack account first.",
|
|
200
|
+
);
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
test("a user auth row with no workspaceUserId counts as not connected", async () => {
|
|
204
|
+
getUserAuth.mockResolvedValue({
|
|
205
|
+
workspaceUserId: undefined,
|
|
206
|
+
} as unknown as WorkspaceUserAuthToken as never);
|
|
207
|
+
|
|
208
|
+
await expect(callOnBeforeCreate(createBy())).rejects.toThrow(
|
|
209
|
+
BadDataException,
|
|
210
|
+
);
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
test("a second row for the same (user, project) is refused as a duplicate", async () => {
|
|
214
|
+
countBy.mockResolvedValue(new PositiveNumber(1) as never);
|
|
215
|
+
|
|
216
|
+
await expect(callOnBeforeCreate(createBy())).rejects.toThrow(
|
|
217
|
+
"Slack is already added as a notification method for this project.",
|
|
218
|
+
);
|
|
219
|
+
|
|
220
|
+
// Refused before any workspace lookup happens.
|
|
221
|
+
expect(getUserAuth).not.toHaveBeenCalled();
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
test("a missing projectId or userId is refused before anything is queried", async () => {
|
|
225
|
+
await expect(
|
|
226
|
+
callOnBeforeCreate({
|
|
227
|
+
data: { projectId: PROJECT_ID } as UserSlack,
|
|
228
|
+
props: { isRoot: false },
|
|
229
|
+
} as CreateBy<UserSlack>),
|
|
230
|
+
).rejects.toThrow("projectId and userId are required");
|
|
231
|
+
|
|
232
|
+
expect(countBy).not.toHaveBeenCalled();
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
test("a username lookup failure is logged and does NOT stop the method being added", async () => {
|
|
236
|
+
getUsername.mockRejectedValue(new Error("slack api down") as never);
|
|
237
|
+
|
|
238
|
+
const result: OnCreate<UserSlack> = await callOnBeforeCreate(createBy());
|
|
239
|
+
|
|
240
|
+
expect(result.createBy.data.slackUserId).toBe(SLACK_MEMBER_ID);
|
|
241
|
+
expect(result.createBy.data.isVerified).toBe(true);
|
|
242
|
+
expect(result.createBy.data.slackUserName).toBeUndefined();
|
|
243
|
+
expect(loggerError).toHaveBeenCalled();
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
test("the username lookup uses the PROJECT bot token, not the user's token", async () => {
|
|
247
|
+
await callOnBeforeCreate(createBy());
|
|
248
|
+
|
|
249
|
+
const arg: { authToken: string; userId: string } = getUsername.mock
|
|
250
|
+
.calls[0][0] as { authToken: string; userId: string };
|
|
251
|
+
expect(arg.authToken).toBe(BOT_TOKEN);
|
|
252
|
+
expect(arg.userId).toBe(SLACK_MEMBER_ID);
|
|
253
|
+
});
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
/*
|
|
257
|
+
* ----------------------------------------------------------------------- *
|
|
258
|
+
* (B) onCreateSuccess — default-rule seeding.
|
|
259
|
+
* -----------------------------------------------------------------------
|
|
260
|
+
*/
|
|
261
|
+
|
|
262
|
+
describe("onCreateSuccess", () => {
|
|
263
|
+
let seedRules: jest.SpyInstance;
|
|
264
|
+
|
|
265
|
+
beforeEach(() => {
|
|
266
|
+
seedRules = jest
|
|
267
|
+
.spyOn(
|
|
268
|
+
UserNotificationRuleService,
|
|
269
|
+
"addDefaultNotificationRulesForVerifiedMethod",
|
|
270
|
+
)
|
|
271
|
+
.mockResolvedValue(undefined as never);
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
test("seeds default rules pointed at the new row via userSlackId", async () => {
|
|
275
|
+
const created: UserSlack = {
|
|
276
|
+
id: METHOD_ID,
|
|
277
|
+
projectId: PROJECT_ID,
|
|
278
|
+
userId: USER_ID,
|
|
279
|
+
} as unknown as UserSlack;
|
|
280
|
+
|
|
281
|
+
await callOnCreateSuccess(
|
|
282
|
+
{ createBy: createBy(), carryForward: null } as OnCreate<UserSlack>,
|
|
283
|
+
created,
|
|
284
|
+
);
|
|
285
|
+
|
|
286
|
+
expect(seedRules).toHaveBeenCalledTimes(1);
|
|
287
|
+
const arg: {
|
|
288
|
+
projectId: ObjectID;
|
|
289
|
+
userId: ObjectID;
|
|
290
|
+
notificationMethod: { userSlackId: ObjectID };
|
|
291
|
+
} = seedRules.mock.calls[0][0] as {
|
|
292
|
+
projectId: ObjectID;
|
|
293
|
+
userId: ObjectID;
|
|
294
|
+
notificationMethod: { userSlackId: ObjectID };
|
|
295
|
+
};
|
|
296
|
+
expect(arg.projectId.toString()).toBe(PROJECT_ID.toString());
|
|
297
|
+
expect(arg.userId.toString()).toBe(USER_ID.toString());
|
|
298
|
+
expect(arg.notificationMethod.userSlackId.toString()).toBe(
|
|
299
|
+
METHOD_ID.toString(),
|
|
300
|
+
);
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
test("a seeding failure is swallowed and logged - the method row is already real", async () => {
|
|
304
|
+
seedRules.mockRejectedValue(new Error("severity read failed") as never);
|
|
305
|
+
|
|
306
|
+
const created: UserSlack = {
|
|
307
|
+
id: METHOD_ID,
|
|
308
|
+
projectId: PROJECT_ID,
|
|
309
|
+
userId: USER_ID,
|
|
310
|
+
} as unknown as UserSlack;
|
|
311
|
+
|
|
312
|
+
await expect(
|
|
313
|
+
callOnCreateSuccess(
|
|
314
|
+
{ createBy: createBy(), carryForward: null } as OnCreate<UserSlack>,
|
|
315
|
+
created,
|
|
316
|
+
),
|
|
317
|
+
).resolves.toBe(created);
|
|
318
|
+
|
|
319
|
+
expect(loggerError).toHaveBeenCalled();
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
test("an item missing its ids seeds nothing rather than seeding garbage", async () => {
|
|
323
|
+
await callOnCreateSuccess(
|
|
324
|
+
{ createBy: createBy(), carryForward: null } as OnCreate<UserSlack>,
|
|
325
|
+
{ id: METHOD_ID } as unknown as UserSlack,
|
|
326
|
+
);
|
|
327
|
+
|
|
328
|
+
expect(seedRules).not.toHaveBeenCalled();
|
|
329
|
+
});
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
/*
|
|
333
|
+
* ----------------------------------------------------------------------- *
|
|
334
|
+
* (C) onBeforeDelete — the rule cascade.
|
|
335
|
+
* -----------------------------------------------------------------------
|
|
336
|
+
*/
|
|
337
|
+
|
|
338
|
+
describe("onBeforeDelete", () => {
|
|
339
|
+
test("deletes every notification rule pointing at each row being deleted", async () => {
|
|
340
|
+
jest.spyOn(UserSlackService, "findBy").mockResolvedValue([
|
|
341
|
+
{
|
|
342
|
+
id: METHOD_ID,
|
|
343
|
+
projectId: PROJECT_ID,
|
|
344
|
+
} as unknown as UserSlack,
|
|
345
|
+
] as never);
|
|
346
|
+
|
|
347
|
+
const deleteRules: jest.SpyInstance = jest
|
|
348
|
+
.spyOn(UserNotificationRuleService, "deleteBy")
|
|
349
|
+
.mockResolvedValue([] as never);
|
|
350
|
+
|
|
351
|
+
await callOnBeforeDelete({
|
|
352
|
+
query: { _id: METHOD_ID },
|
|
353
|
+
props: { isRoot: true },
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
expect(deleteRules).toHaveBeenCalledTimes(1);
|
|
357
|
+
const arg: {
|
|
358
|
+
query: { userSlackId: ObjectID; projectId: ObjectID };
|
|
359
|
+
limit: number;
|
|
360
|
+
props: { isRoot: boolean };
|
|
361
|
+
} = deleteRules.mock.calls[0][0] as {
|
|
362
|
+
query: { userSlackId: ObjectID; projectId: ObjectID };
|
|
363
|
+
limit: number;
|
|
364
|
+
props: { isRoot: boolean };
|
|
365
|
+
};
|
|
366
|
+
expect(arg.query.userSlackId.toString()).toBe(METHOD_ID.toString());
|
|
367
|
+
expect(arg.query.projectId.toString()).toBe(PROJECT_ID.toString());
|
|
368
|
+
expect(arg.limit).toBe(LIMIT_MAX);
|
|
369
|
+
expect(arg.props.isRoot).toBe(true);
|
|
370
|
+
});
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
/*
|
|
374
|
+
* ----------------------------------------------------------------------- *
|
|
375
|
+
* (D) getDeletionImpact — the shared preview, on the Slack channel.
|
|
376
|
+
* -----------------------------------------------------------------------
|
|
377
|
+
*/
|
|
378
|
+
|
|
379
|
+
describe("getDeletionImpact", () => {
|
|
380
|
+
test("delegates to the shared preview with methodType Slack", async () => {
|
|
381
|
+
const getImpact: jest.SpyInstance = jest
|
|
382
|
+
.spyOn(
|
|
383
|
+
UserNotificationRuleService,
|
|
384
|
+
"getNotificationMethodDeletionImpact",
|
|
385
|
+
)
|
|
386
|
+
.mockResolvedValue({} as never);
|
|
387
|
+
|
|
388
|
+
await UserSlackService.getDeletionImpact({
|
|
389
|
+
itemId: METHOD_ID,
|
|
390
|
+
projectId: PROJECT_ID,
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
const arg: {
|
|
394
|
+
projectId: ObjectID;
|
|
395
|
+
methodType: string;
|
|
396
|
+
methodId: ObjectID;
|
|
397
|
+
} = getImpact.mock.calls[0][0] as {
|
|
398
|
+
projectId: ObjectID;
|
|
399
|
+
methodType: string;
|
|
400
|
+
methodId: ObjectID;
|
|
401
|
+
};
|
|
402
|
+
expect(arg.projectId.toString()).toBe(PROJECT_ID.toString());
|
|
403
|
+
expect(arg.methodType).toBe("Slack");
|
|
404
|
+
expect(arg.methodId.toString()).toBe(METHOD_ID.toString());
|
|
405
|
+
});
|
|
406
|
+
});
|
|
407
|
+
});
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import WorkspaceProjectAuthTokenService from "../../../Server/Services/WorkspaceProjectAuthTokenService";
|
|
2
|
+
import WorkspaceUserAuthTokenService from "../../../Server/Services/WorkspaceUserAuthTokenService";
|
|
3
|
+
import WorkspaceProjectAuthToken from "../../../Models/DatabaseModels/WorkspaceProjectAuthToken";
|
|
4
|
+
import ObjectID from "../../../Types/ObjectID";
|
|
5
|
+
import LIMIT_MAX from "../../../Types/Database/LimitMax";
|
|
6
|
+
import WorkspaceType from "../../../Types/Workspace/WorkspaceType";
|
|
7
|
+
import { describe, expect, test, beforeEach, afterEach } from "@jest/globals";
|
|
8
|
+
|
|
9
|
+
/*
|
|
10
|
+
* The PROJECT-level half of the workspace disconnect cascade.
|
|
11
|
+
*
|
|
12
|
+
* The dashboard's "Uninstall OneUptime from Slack / Microsoft Teams" button
|
|
13
|
+
* deletes the WorkspaceProjectAuthToken row through the generic CRUD path.
|
|
14
|
+
* Before this hook existed, that path deleted ONLY the project token: every
|
|
15
|
+
* member's WorkspaceUserAuthToken survived, and with it every UserSlack /
|
|
16
|
+
* UserMicrosoftTeams notification method — verified-looking rows pointing at
|
|
17
|
+
* a workspace the sender deterministically refuses. The on-call fallback
|
|
18
|
+
* would pick them into its zero-cost tier and stop looking, and the
|
|
19
|
+
* readiness surface would keep reporting the responder reachable: the exact
|
|
20
|
+
* false green both of those surfaces exist to make impossible. (The
|
|
21
|
+
* Slack-side app_uninstall webhook already deleted the user tokens — but it
|
|
22
|
+
* only fires for uninstalls initiated inside Slack, and Microsoft Teams has
|
|
23
|
+
* no uninstall webhook at all.)
|
|
24
|
+
*
|
|
25
|
+
* The hook deletes the workspace's user tokens THROUGH THEIR SERVICE, so
|
|
26
|
+
* WorkspaceUserAuthTokenService.onBeforeDelete fires and cascades to the
|
|
27
|
+
* notification methods and their rules — one chain, tested per link:
|
|
28
|
+
* project token -> user tokens (here), user token -> method rows
|
|
29
|
+
* (WorkspaceUserAuthTokenNotificationMethodCascade.test.ts), method row ->
|
|
30
|
+
* rules (UserSlackService.test.ts / UserMicrosoftTeamsService.test.ts).
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
const PROJECT_ID: ObjectID = new ObjectID(
|
|
34
|
+
"11111111-1111-4111-8111-111111111111",
|
|
35
|
+
);
|
|
36
|
+
const OTHER_PROJECT_ID: ObjectID = new ObjectID(
|
|
37
|
+
"22222222-2222-4222-8222-222222222222",
|
|
38
|
+
);
|
|
39
|
+
const TOKEN_ID: ObjectID = new ObjectID("33333333-3333-4333-8333-333333333333");
|
|
40
|
+
|
|
41
|
+
type OnBeforeDeleteFunction = (deleteBy: {
|
|
42
|
+
query: Record<string, unknown>;
|
|
43
|
+
props: { isRoot: boolean };
|
|
44
|
+
}) => Promise<unknown>;
|
|
45
|
+
|
|
46
|
+
function callOnBeforeDelete(deleteBy: {
|
|
47
|
+
query: Record<string, unknown>;
|
|
48
|
+
props: { isRoot: boolean };
|
|
49
|
+
}): Promise<unknown> {
|
|
50
|
+
return (
|
|
51
|
+
WorkspaceProjectAuthTokenService as unknown as {
|
|
52
|
+
onBeforeDelete: OnBeforeDeleteFunction;
|
|
53
|
+
}
|
|
54
|
+
).onBeforeDelete(deleteBy);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function projectTokenRow(
|
|
58
|
+
workspaceType: WorkspaceType,
|
|
59
|
+
projectId: ObjectID = PROJECT_ID,
|
|
60
|
+
): WorkspaceProjectAuthToken {
|
|
61
|
+
return {
|
|
62
|
+
id: TOKEN_ID,
|
|
63
|
+
projectId: projectId,
|
|
64
|
+
workspaceType: workspaceType,
|
|
65
|
+
} as unknown as WorkspaceProjectAuthToken;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
describe("WorkspaceProjectAuthTokenService.onBeforeDelete - workspace disconnect cascade", () => {
|
|
69
|
+
let findTokens: jest.SpyInstance;
|
|
70
|
+
let deleteUserTokens: jest.SpyInstance;
|
|
71
|
+
|
|
72
|
+
beforeEach(() => {
|
|
73
|
+
findTokens = jest
|
|
74
|
+
.spyOn(WorkspaceProjectAuthTokenService, "findBy")
|
|
75
|
+
.mockResolvedValue([] as never);
|
|
76
|
+
|
|
77
|
+
deleteUserTokens = jest
|
|
78
|
+
.spyOn(WorkspaceUserAuthTokenService, "deleteBy")
|
|
79
|
+
.mockResolvedValue([] as never);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
afterEach(() => {
|
|
83
|
+
jest.restoreAllMocks();
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test("deleting the Slack project token deletes every Slack user token in the project, through the service", async () => {
|
|
87
|
+
findTokens.mockResolvedValue([
|
|
88
|
+
projectTokenRow(WorkspaceType.Slack),
|
|
89
|
+
] as never);
|
|
90
|
+
|
|
91
|
+
await callOnBeforeDelete({
|
|
92
|
+
query: { _id: TOKEN_ID },
|
|
93
|
+
props: { isRoot: false },
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
expect(deleteUserTokens).toHaveBeenCalledTimes(1);
|
|
97
|
+
const arg: {
|
|
98
|
+
query: { projectId: ObjectID; workspaceType: WorkspaceType };
|
|
99
|
+
limit: number;
|
|
100
|
+
props: { isRoot: boolean };
|
|
101
|
+
} = deleteUserTokens.mock.calls[0][0] as {
|
|
102
|
+
query: { projectId: ObjectID; workspaceType: WorkspaceType };
|
|
103
|
+
limit: number;
|
|
104
|
+
props: { isRoot: boolean };
|
|
105
|
+
};
|
|
106
|
+
expect(arg.query.projectId.toString()).toBe(PROJECT_ID.toString());
|
|
107
|
+
expect(arg.query.workspaceType).toBe(WorkspaceType.Slack);
|
|
108
|
+
expect(arg.limit).toBe(LIMIT_MAX);
|
|
109
|
+
/*
|
|
110
|
+
* isRoot matters twice over: the user tokens belong to OTHER users, whom
|
|
111
|
+
* the disconnecting admin's own scope could never delete — and it is the
|
|
112
|
+
* deleteBy going THROUGH the service (rather than raw) that fires the
|
|
113
|
+
* user-token hook which takes the notification methods down with it.
|
|
114
|
+
*/
|
|
115
|
+
expect(arg.props.isRoot).toBe(true);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test("the cascade is scoped to the WORKSPACE TYPE being disconnected - a Teams disconnect leaves Slack links alone", async () => {
|
|
119
|
+
findTokens.mockResolvedValue([
|
|
120
|
+
projectTokenRow(WorkspaceType.MicrosoftTeams),
|
|
121
|
+
] as never);
|
|
122
|
+
|
|
123
|
+
await callOnBeforeDelete({
|
|
124
|
+
query: { _id: TOKEN_ID },
|
|
125
|
+
props: { isRoot: true },
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
expect(deleteUserTokens).toHaveBeenCalledTimes(1);
|
|
129
|
+
const arg: { query: { workspaceType: WorkspaceType } } = deleteUserTokens
|
|
130
|
+
.mock.calls[0][0] as { query: { workspaceType: WorkspaceType } };
|
|
131
|
+
expect(arg.query.workspaceType).toBe(WorkspaceType.MicrosoftTeams);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test("a bulk delete spanning both workspace types cascades each token to its own workspace", async () => {
|
|
135
|
+
findTokens.mockResolvedValue([
|
|
136
|
+
projectTokenRow(WorkspaceType.Slack),
|
|
137
|
+
projectTokenRow(WorkspaceType.MicrosoftTeams),
|
|
138
|
+
] as never);
|
|
139
|
+
|
|
140
|
+
await callOnBeforeDelete({
|
|
141
|
+
query: { projectId: PROJECT_ID },
|
|
142
|
+
props: { isRoot: true },
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
expect(deleteUserTokens).toHaveBeenCalledTimes(2);
|
|
146
|
+
const workspaceTypes: Array<WorkspaceType> =
|
|
147
|
+
deleteUserTokens.mock.calls.map((call: Array<unknown>) => {
|
|
148
|
+
return (call[0] as { query: { workspaceType: WorkspaceType } }).query
|
|
149
|
+
.workspaceType;
|
|
150
|
+
});
|
|
151
|
+
expect(workspaceTypes).toContain(WorkspaceType.Slack);
|
|
152
|
+
expect(workspaceTypes).toContain(WorkspaceType.MicrosoftTeams);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
test("each token's cascade is scoped to that token's OWN project", async () => {
|
|
156
|
+
findTokens.mockResolvedValue([
|
|
157
|
+
projectTokenRow(WorkspaceType.Slack, PROJECT_ID),
|
|
158
|
+
projectTokenRow(WorkspaceType.Slack, OTHER_PROJECT_ID),
|
|
159
|
+
] as never);
|
|
160
|
+
|
|
161
|
+
await callOnBeforeDelete({
|
|
162
|
+
query: { workspaceType: WorkspaceType.Slack },
|
|
163
|
+
props: { isRoot: true },
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
const projectIds: Array<string> = deleteUserTokens.mock.calls.map(
|
|
167
|
+
(call: Array<unknown>) => {
|
|
168
|
+
return (
|
|
169
|
+
call[0] as { query: { projectId: ObjectID } }
|
|
170
|
+
).query.projectId.toString();
|
|
171
|
+
},
|
|
172
|
+
);
|
|
173
|
+
expect(projectIds).toContain(PROJECT_ID.toString());
|
|
174
|
+
expect(projectIds).toContain(OTHER_PROJECT_ID.toString());
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
test("the tokens are re-read with the RAW delete query, as root", async () => {
|
|
178
|
+
await callOnBeforeDelete({
|
|
179
|
+
query: { projectId: PROJECT_ID },
|
|
180
|
+
props: { isRoot: false },
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
expect(findTokens).toHaveBeenCalledTimes(1);
|
|
184
|
+
const arg: {
|
|
185
|
+
query: Record<string, unknown>;
|
|
186
|
+
props: { isRoot: boolean };
|
|
187
|
+
limit: number;
|
|
188
|
+
} = findTokens.mock.calls[0][0] as {
|
|
189
|
+
query: Record<string, unknown>;
|
|
190
|
+
props: { isRoot: boolean };
|
|
191
|
+
limit: number;
|
|
192
|
+
};
|
|
193
|
+
expect(arg.query["projectId"]).toBe(PROJECT_ID);
|
|
194
|
+
expect(arg.props.isRoot).toBe(true);
|
|
195
|
+
expect(arg.limit).toBe(LIMIT_MAX);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
test("a token row missing its project or workspace type is skipped rather than issuing an unscoped delete", async () => {
|
|
199
|
+
findTokens.mockResolvedValue([
|
|
200
|
+
{ id: TOKEN_ID } as unknown as WorkspaceProjectAuthToken,
|
|
201
|
+
] as never);
|
|
202
|
+
|
|
203
|
+
await callOnBeforeDelete({
|
|
204
|
+
query: { _id: TOKEN_ID },
|
|
205
|
+
props: { isRoot: true },
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
/*
|
|
209
|
+
* A deleteBy whose query is { projectId: undefined, workspaceType:
|
|
210
|
+
* undefined } would match every user token in the installation. Skipping
|
|
211
|
+
* the malformed row is the only safe reading.
|
|
212
|
+
*/
|
|
213
|
+
expect(deleteUserTokens).not.toHaveBeenCalled();
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
test("a delete that matches no project tokens cascades nothing", async () => {
|
|
217
|
+
await callOnBeforeDelete({
|
|
218
|
+
query: { _id: TOKEN_ID },
|
|
219
|
+
props: { isRoot: true },
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
expect(deleteUserTokens).not.toHaveBeenCalled();
|
|
223
|
+
});
|
|
224
|
+
});
|