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