@oneuptime/common 12.0.31 → 12.0.32
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/AlertState.ts +0 -2
- package/Models/DatabaseModels/Domain.ts +1 -1
- package/Models/DatabaseModels/Incident.ts +1 -1
- package/Models/DatabaseModels/IncidentTemplate.ts +1 -1
- package/Models/DatabaseModels/Index.ts +2 -0
- package/Models/DatabaseModels/MetricType.ts +0 -2
- package/Models/DatabaseModels/MonitorStatusTimeline.ts +0 -2
- package/Models/DatabaseModels/MonitorTest.ts +0 -2
- package/Models/DatabaseModels/NetworkDevice.ts +121 -2
- package/Models/DatabaseModels/NetworkDeviceOidTemplate.ts +3 -1
- package/Models/DatabaseModels/NetworkDeviceRole.ts +648 -0
- package/Models/DatabaseModels/ScheduledMaintenance.ts +1 -1
- package/Models/DatabaseModels/ScheduledMaintenanceTemplate.ts +1 -1
- package/Models/DatabaseModels/StatusPageAnnouncement.ts +0 -2
- package/Models/DatabaseModels/StatusPageAnnouncementTemplate.ts +0 -2
- package/Models/DatabaseModels/StatusPagePrivateUser.ts +0 -2
- package/Models/DatabaseModels/StatusPageResource.ts +0 -2
- package/Models/DatabaseModels/StatusPageSubscriber.ts +0 -2
- package/Server/Infrastructure/Postgres/SchemaMigrations/1790800000000-AddNetworkDeviceRoleTable.ts +72 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +2 -0
- package/Server/Services/DatabaseService.ts +39 -9
- package/Server/Services/Index.ts +2 -0
- package/Server/Services/NetworkDeviceRoleService.ts +90 -0
- package/Server/Services/ProjectService.ts +85 -0
- package/Server/Services/UserNotificationSettingService.ts +0 -6
- package/Tests/App/Dashboard/AddNeighborToMonitoringModal.test.tsx +31 -1
- package/Tests/App/Dashboard/DashboardChartWidgetZoom.test.tsx +35 -8
- package/Tests/App/Dashboard/DashboardCommandPalette.test.tsx +107 -3
- package/Tests/App/Dashboard/DashboardWideChartZoom.test.tsx +401 -0
- package/Tests/App/Dashboard/MonitorTemplateColumnAndFacet.test.tsx +318 -0
- package/Tests/App/Dashboard/NetworkDeviceBulkDelete.test.tsx +357 -0
- package/Tests/App/Dashboard/NetworkSideMenu.test.tsx +590 -0
- package/Tests/App/Dashboard/SideMenuHarness.tsx +11 -3
- package/Tests/Models/DatabaseModels/ModelRegistryInvariants.test.ts +115 -41
- package/Tests/Server/Infrastructure/Postgres/NetworkDeviceRoleTableMigration.test.ts +476 -0
- package/Tests/Server/Services/DatabaseServiceGenerateSlug.test.ts +215 -0
- package/Tests/Server/Services/NetworkDeviceRoleModel.test.ts +672 -0
- package/Tests/Server/Services/NetworkDeviceRuleEngines.test.ts +1 -1
- package/Tests/Server/Services/ProjectServiceCreateSubscriptionStarted.test.ts +67 -4
- package/Tests/Server/Services/ScheduledMaintenanceSubscriberEndTime.test.ts +369 -0
- package/Tests/Server/Services/UserNotificationSettingMonitorDefaults.test.ts +248 -0
- package/Tests/Server/Services/UserNotificationSettingWorkspaceChannels.test.ts +43 -2
- package/Tests/Server/Utils/BasicCron.test.ts +283 -0
- package/Tests/Server/Utils/IncomingCallPhoneNumber.test.ts +174 -0
- package/Tests/Types/NetworkDevice/DefaultNetworkDeviceRole.test.ts +417 -0
- package/Tests/UI/Components/Charts/ChartClickDoubleClickDisambiguation.test.tsx +221 -0
- package/Tests/UI/Components/Charts/ChartDoubleClickReset.test.tsx +220 -0
- package/Tests/UI/Components/ModelTable/BaseModelTableBulkDelete.test.tsx +751 -0
- package/Tests/UI/Components/ModelTable/BaseModelTableColumnCustomization.test.tsx +475 -43
- package/Tests/UI/Components/ModelTable/ColumnPreference.test.ts +89 -0
- package/Tests/UI/Components/NavBarCommandKGating.test.tsx +103 -8
- package/Tests/UI/Components/SideMenuItem.test.tsx +30 -0
- package/Tests/UI/Utils/TableColumnsToCsv.test.ts +30 -0
- package/Tests/UI/Utils/UseDashboardTimeRangeZoom.test.tsx +187 -0
- package/Tests/Utils/Dashboard/DashboardResourceListSharedArgs.test.ts +302 -0
- package/Tests/Utils/Dashboard/DashboardTimeRangeZoom.test.ts +242 -0
- package/Tests/Utils/Monitor/NetworkDeviceRoleCatalog.test.ts +981 -0
- package/Tests/Utils/Monitor/NetworkTopologyRoleCatalog.test.ts +865 -0
- package/Tests/Utils/NetworkDevice/DeviceRoleKeyUtil.test.ts +538 -0
- package/Tests/Utils/Slo/SloEvaluation.test.ts +171 -0
- package/Tests/Utils/Slug.test.ts +61 -1
- package/Tests/jest.setup.ts +19 -0
- package/Types/Monitor/SnmpMonitor/NetworkTopology.ts +61 -0
- package/Types/NetworkDevice/DefaultNetworkDeviceRole.ts +145 -0
- package/Types/Permission.ts +45 -0
- package/UI/Components/Charts/Area/AreaChart.tsx +7 -0
- package/UI/Components/Charts/Bar/BarChart.tsx +6 -0
- package/UI/Components/Charts/ChartGroup/ChartGroup.tsx +9 -1
- package/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.tsx +97 -35
- package/UI/Components/Charts/ChartLibrary/BarChart/BarChart.tsx +14 -0
- package/UI/Components/Charts/ChartLibrary/LineChart/LineChart.tsx +97 -35
- package/UI/Components/Charts/ChartLibrary/Utils/DoubleClick.ts +13 -0
- package/UI/Components/Charts/Line/LineChart.tsx +7 -0
- package/UI/Components/ModelTable/BaseModelTable.tsx +50 -15
- package/UI/Components/Navbar/NavBar.tsx +62 -13
- package/UI/Components/Navbar/NavBarMenuModal.tsx +7 -2
- package/UI/Components/SideMenu/CountModelSideMenuItem.tsx +2 -0
- package/UI/Components/SideMenu/SideMenu.tsx +12 -2
- package/UI/Components/SideMenu/SideMenuItem.tsx +4 -1
- package/UI/Components/SideMenu/SideMenuSection.tsx +14 -2
- package/UI/Utils/UseDashboardTimeRangeZoom.ts +77 -0
- package/Utils/Dashboard/DashboardTimeRangeZoom.ts +118 -0
- package/Utils/Monitor/NetworkDeviceRoleCatalog.ts +232 -0
- package/Utils/Monitor/NetworkDeviceRoleUtil.ts +2 -1
- package/Utils/Monitor/NetworkTopologyUtil.ts +116 -33
- package/Utils/NetworkDevice/DeviceRoleKeyUtil.ts +137 -0
- package/Utils/Rules/RulePatternMatchUtil.ts +1 -1
- package/Utils/Slug.ts +34 -1
- package/build/dist/Models/DatabaseModels/AlertState.js +0 -2
- package/build/dist/Models/DatabaseModels/AlertState.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Domain.js +1 -1
- package/build/dist/Models/DatabaseModels/Domain.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Incident.js +1 -1
- package/build/dist/Models/DatabaseModels/Incident.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentTemplate.js +1 -1
- package/build/dist/Models/DatabaseModels/IncidentTemplate.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Index.js +2 -0
- package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
- package/build/dist/Models/DatabaseModels/MetricType.js +0 -2
- package/build/dist/Models/DatabaseModels/MetricType.js.map +1 -1
- package/build/dist/Models/DatabaseModels/MonitorStatusTimeline.js +0 -2
- package/build/dist/Models/DatabaseModels/MonitorStatusTimeline.js.map +1 -1
- package/build/dist/Models/DatabaseModels/MonitorTest.js +0 -2
- package/build/dist/Models/DatabaseModels/MonitorTest.js.map +1 -1
- package/build/dist/Models/DatabaseModels/NetworkDevice.js +124 -3
- package/build/dist/Models/DatabaseModels/NetworkDevice.js.map +1 -1
- package/build/dist/Models/DatabaseModels/NetworkDeviceOidTemplate.js +3 -1
- package/build/dist/Models/DatabaseModels/NetworkDeviceOidTemplate.js.map +1 -1
- package/build/dist/Models/DatabaseModels/NetworkDeviceRole.js +677 -0
- package/build/dist/Models/DatabaseModels/NetworkDeviceRole.js.map +1 -0
- package/build/dist/Models/DatabaseModels/ScheduledMaintenance.js +1 -1
- package/build/dist/Models/DatabaseModels/ScheduledMaintenance.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ScheduledMaintenanceTemplate.js +1 -1
- package/build/dist/Models/DatabaseModels/ScheduledMaintenanceTemplate.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPageAnnouncement.js +0 -2
- package/build/dist/Models/DatabaseModels/StatusPageAnnouncement.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPageAnnouncementTemplate.js +0 -2
- package/build/dist/Models/DatabaseModels/StatusPageAnnouncementTemplate.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPagePrivateUser.js +0 -2
- package/build/dist/Models/DatabaseModels/StatusPagePrivateUser.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPageResource.js +0 -2
- package/build/dist/Models/DatabaseModels/StatusPageResource.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPageSubscriber.js +0 -2
- package/build/dist/Models/DatabaseModels/StatusPageSubscriber.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790800000000-AddNetworkDeviceRoleTable.js +39 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790800000000-AddNetworkDeviceRoleTable.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +2 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Services/DatabaseService.js +31 -5
- package/build/dist/Server/Services/DatabaseService.js.map +1 -1
- package/build/dist/Server/Services/Index.js +2 -0
- package/build/dist/Server/Services/Index.js.map +1 -1
- package/build/dist/Server/Services/NetworkDeviceRoleService.js +70 -0
- package/build/dist/Server/Services/NetworkDeviceRoleService.js.map +1 -0
- package/build/dist/Server/Services/ProjectService.js +65 -0
- package/build/dist/Server/Services/ProjectService.js.map +1 -1
- package/build/dist/Server/Services/UserNotificationSettingService.js +0 -1
- package/build/dist/Server/Services/UserNotificationSettingService.js.map +1 -1
- package/build/dist/Types/NetworkDevice/DefaultNetworkDeviceRole.js +105 -0
- package/build/dist/Types/NetworkDevice/DefaultNetworkDeviceRole.js.map +1 -0
- package/build/dist/Types/Permission.js +40 -0
- package/build/dist/Types/Permission.js.map +1 -1
- package/build/dist/UI/Components/Charts/Area/AreaChart.js +1 -1
- package/build/dist/UI/Components/Charts/Area/AreaChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/Bar/BarChart.js +1 -1
- package/build/dist/UI/Components/Charts/Bar/BarChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js +6 -1
- package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.js +79 -31
- package/build/dist/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartLibrary/BarChart/BarChart.js +7 -1
- package/build/dist/UI/Components/Charts/ChartLibrary/BarChart/BarChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartLibrary/LineChart/LineChart.js +79 -31
- package/build/dist/UI/Components/Charts/ChartLibrary/LineChart/LineChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartLibrary/Utils/DoubleClick.js +14 -0
- package/build/dist/UI/Components/Charts/ChartLibrary/Utils/DoubleClick.js.map +1 -0
- package/build/dist/UI/Components/Charts/Line/LineChart.js +1 -1
- package/build/dist/UI/Components/Charts/Line/LineChart.js.map +1 -1
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js +39 -13
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
- package/build/dist/UI/Components/Navbar/NavBar.js +44 -9
- package/build/dist/UI/Components/Navbar/NavBar.js.map +1 -1
- package/build/dist/UI/Components/Navbar/NavBarMenuModal.js +2 -2
- package/build/dist/UI/Components/Navbar/NavBarMenuModal.js.map +1 -1
- package/build/dist/UI/Components/SideMenu/CountModelSideMenuItem.js +1 -1
- package/build/dist/UI/Components/SideMenu/CountModelSideMenuItem.js.map +1 -1
- package/build/dist/UI/Components/SideMenu/SideMenu.js +9 -6
- package/build/dist/UI/Components/SideMenu/SideMenu.js.map +1 -1
- package/build/dist/UI/Components/SideMenu/SideMenuItem.js +1 -1
- package/build/dist/UI/Components/SideMenu/SideMenuItem.js.map +1 -1
- package/build/dist/UI/Components/SideMenu/SideMenuSection.js +7 -2
- package/build/dist/UI/Components/SideMenu/SideMenuSection.js.map +1 -1
- package/build/dist/UI/Utils/UseDashboardTimeRangeZoom.js +42 -0
- package/build/dist/UI/Utils/UseDashboardTimeRangeZoom.js.map +1 -0
- package/build/dist/Utils/Dashboard/DashboardTimeRangeZoom.js +74 -0
- package/build/dist/Utils/Dashboard/DashboardTimeRangeZoom.js.map +1 -0
- package/build/dist/Utils/Monitor/NetworkDeviceRoleCatalog.js +126 -0
- package/build/dist/Utils/Monitor/NetworkDeviceRoleCatalog.js.map +1 -0
- package/build/dist/Utils/Monitor/NetworkDeviceRoleUtil.js +2 -1
- package/build/dist/Utils/Monitor/NetworkDeviceRoleUtil.js.map +1 -1
- package/build/dist/Utils/Monitor/NetworkTopologyUtil.js +67 -32
- package/build/dist/Utils/Monitor/NetworkTopologyUtil.js.map +1 -1
- package/build/dist/Utils/NetworkDevice/DeviceRoleKeyUtil.js +114 -0
- package/build/dist/Utils/NetworkDevice/DeviceRoleKeyUtil.js.map +1 -0
- package/build/dist/Utils/Rules/RulePatternMatchUtil.js +1 -1
- package/build/dist/Utils/Slug.js +31 -1
- package/build/dist/Utils/Slug.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
1
3
|
import ProjectService from "../../../Server/Services/ProjectService";
|
|
2
4
|
import BillingService from "../../../Server/Services/BillingService";
|
|
3
5
|
import MarketingEventUtil from "../../../Server/Utils/Marketing/MarketingEventUtil";
|
|
@@ -8,7 +10,7 @@ import { MarketingEvent } from "../../../Types/Marketing/MarketingEvent";
|
|
|
8
10
|
import { resolveEmittedMarketingEvent } from "../Utils/Marketing/EmittedMarketingEvent";
|
|
9
11
|
import { JSONObject } from "../../../Types/JSON";
|
|
10
12
|
import ObjectID from "../../../Types/ObjectID";
|
|
11
|
-
import { describe, expect, it, afterEach } from "@jest/globals";
|
|
13
|
+
import { describe, expect, it, afterEach, test } from "@jest/globals";
|
|
12
14
|
|
|
13
15
|
/*
|
|
14
16
|
* A project created directly on a paid plan never passes through changePlan,
|
|
@@ -121,9 +123,11 @@ describe("ProjectService project creation - subscription_started", () => {
|
|
|
121
123
|
}
|
|
122
124
|
|
|
123
125
|
/*
|
|
124
|
-
* The
|
|
125
|
-
*
|
|
126
|
-
*
|
|
126
|
+
* The default-row seeders run after the conversion is captured and each one
|
|
127
|
+
* writes to a different table. None of them are what is under test, so they
|
|
128
|
+
* are stubbed out wholesale. This list must name every seeder in
|
|
129
|
+
* ProjectService.onCreateSuccess: one left off reaches a real repository and
|
|
130
|
+
* throws "Database not connected", failing the whole suite.
|
|
127
131
|
*/
|
|
128
132
|
const DEFAULT_SEEDERS: Array<string> = [
|
|
129
133
|
"addDefaultIncidentSeverity",
|
|
@@ -135,6 +139,7 @@ describe("ProjectService project creation - subscription_started", () => {
|
|
|
135
139
|
"addDefaultAlertState",
|
|
136
140
|
"addDefaultIncidentRoles",
|
|
137
141
|
"addDefaultNetworkSiteTypes",
|
|
142
|
+
"addDefaultNetworkDeviceRoles",
|
|
138
143
|
];
|
|
139
144
|
|
|
140
145
|
function setup(data?: {
|
|
@@ -516,4 +521,62 @@ describe("ProjectService project creation - subscription_started", () => {
|
|
|
516
521
|
expect(emitOrder).toBeGreaterThan(writeOrder);
|
|
517
522
|
});
|
|
518
523
|
});
|
|
524
|
+
|
|
525
|
+
/*
|
|
526
|
+
* The list above is a hand-maintained mirror of the seeders
|
|
527
|
+
* onCreateSuccess runs, and nothing about adding a tenth seeder to the
|
|
528
|
+
* service makes this file fail to compile. When addDefaultNetworkDeviceRoles
|
|
529
|
+
* was added and this list was not updated, the unstubbed seeder reached a
|
|
530
|
+
* real repository and every test in this suite failed with "Database not
|
|
531
|
+
* connected" — on master, not in review.
|
|
532
|
+
*
|
|
533
|
+
* So the mirror is checked against its source. Read as text because
|
|
534
|
+
* onCreateSuccess is protected and the seeder batch is a statement inside
|
|
535
|
+
* it: there is nothing to enumerate at runtime without actually creating a
|
|
536
|
+
* project, which is the thing this suite stubs out.
|
|
537
|
+
*/
|
|
538
|
+
describe("the stub list stays in step with the service", () => {
|
|
539
|
+
const PROJECT_SERVICE_SOURCE: string = fs.readFileSync(
|
|
540
|
+
path.join(__dirname, "../../../Server/Services/ProjectService.ts"),
|
|
541
|
+
"utf8",
|
|
542
|
+
);
|
|
543
|
+
|
|
544
|
+
function seedersInCreateHook(): Array<string> {
|
|
545
|
+
const start: number = PROJECT_SERVICE_SOURCE.indexOf(
|
|
546
|
+
"await Promise.all([",
|
|
547
|
+
);
|
|
548
|
+
expect(start).toBeGreaterThan(-1);
|
|
549
|
+
|
|
550
|
+
const block: string = PROJECT_SERVICE_SOURCE.slice(
|
|
551
|
+
start,
|
|
552
|
+
PROJECT_SERVICE_SOURCE.indexOf("]);", start),
|
|
553
|
+
);
|
|
554
|
+
|
|
555
|
+
return Array.from(
|
|
556
|
+
block.matchAll(/this\.(addDefault\w+)\(createdItem\)/g),
|
|
557
|
+
(match: RegExpMatchArray): string => {
|
|
558
|
+
return match[1]!;
|
|
559
|
+
},
|
|
560
|
+
);
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
test("every seeder the create hook runs is stubbed here", () => {
|
|
564
|
+
const inHook: Array<string> = seedersInCreateHook();
|
|
565
|
+
|
|
566
|
+
expect(inHook.length).toBeGreaterThan(0);
|
|
567
|
+
expect(DEFAULT_SEEDERS.slice().sort()).toEqual(inHook.slice().sort());
|
|
568
|
+
});
|
|
569
|
+
|
|
570
|
+
/*
|
|
571
|
+
* A stub for a seeder that no longer exists is dead weight, and
|
|
572
|
+
* jest.spyOn would throw on the missing method rather than skip it.
|
|
573
|
+
*/
|
|
574
|
+
test("every seeder stubbed here is a real method on the service", () => {
|
|
575
|
+
for (const seeder of DEFAULT_SEEDERS) {
|
|
576
|
+
expect(
|
|
577
|
+
typeof (ProjectService as unknown as Record<string, unknown>)[seeder],
|
|
578
|
+
).toBe("function");
|
|
579
|
+
}
|
|
580
|
+
});
|
|
581
|
+
});
|
|
519
582
|
});
|
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
import ScheduledMaintenance from "../../../Models/DatabaseModels/ScheduledMaintenance";
|
|
2
|
+
import ProjectCallSMSConfig from "../../../Models/DatabaseModels/ProjectCallSMSConfig";
|
|
3
|
+
import ProjectSmtpConfig from "../../../Models/DatabaseModels/ProjectSmtpConfig";
|
|
4
|
+
import StatusPage from "../../../Models/DatabaseModels/StatusPage";
|
|
5
|
+
import StatusPageSubscriber from "../../../Models/DatabaseModels/StatusPageSubscriber";
|
|
6
|
+
import StatusPageSubscriberNotificationTemplate from "../../../Models/DatabaseModels/StatusPageSubscriberNotificationTemplate";
|
|
7
|
+
import DatabaseConfig from "../../../Server/DatabaseConfig";
|
|
8
|
+
import MailService from "../../../Server/Services/MailService";
|
|
9
|
+
import ProjectCallSMSConfigService from "../../../Server/Services/ProjectCallSMSConfigService";
|
|
10
|
+
import ProjectSmtpConfigService from "../../../Server/Services/ProjectSmtpConfigService";
|
|
11
|
+
import ScheduledMaintenanceService from "../../../Server/Services/ScheduledMaintenanceService";
|
|
12
|
+
import SmsService from "../../../Server/Services/SmsService";
|
|
13
|
+
import StatusPageResourceService from "../../../Server/Services/StatusPageResourceService";
|
|
14
|
+
import StatusPageService from "../../../Server/Services/StatusPageService";
|
|
15
|
+
import StatusPageSubscriberNotificationTemplateService from "../../../Server/Services/StatusPageSubscriberNotificationTemplateService";
|
|
16
|
+
import StatusPageSubscriberService from "../../../Server/Services/StatusPageSubscriberService";
|
|
17
|
+
import Markdown from "../../../Server/Types/Markdown";
|
|
18
|
+
import SlackUtil from "../../../Server/Utils/Workspace/Slack/Slack";
|
|
19
|
+
import StatusPageSubscriberWebhookUtil from "../../../Server/Utils/StatusPageSubscriberWebhook";
|
|
20
|
+
import Hostname from "../../../Types/API/Hostname";
|
|
21
|
+
import HTTPResponse from "../../../Types/API/HTTPResponse";
|
|
22
|
+
import Protocol from "../../../Types/API/Protocol";
|
|
23
|
+
import URL from "../../../Types/API/URL";
|
|
24
|
+
import OneUptimeDate from "../../../Types/Date";
|
|
25
|
+
import { JSONObject } from "../../../Types/JSON";
|
|
26
|
+
import Email from "../../../Types/Email";
|
|
27
|
+
import Phone from "../../../Types/Phone";
|
|
28
|
+
import ObjectID from "../../../Types/ObjectID";
|
|
29
|
+
import StatusPageSubscriberNotificationMethod from "../../../Types/StatusPage/StatusPageSubscriberNotificationMethod";
|
|
30
|
+
import {
|
|
31
|
+
afterEach,
|
|
32
|
+
beforeEach,
|
|
33
|
+
describe,
|
|
34
|
+
expect,
|
|
35
|
+
jest,
|
|
36
|
+
test,
|
|
37
|
+
} from "@jest/globals";
|
|
38
|
+
|
|
39
|
+
/*
|
|
40
|
+
* The other half of GitHub issue #3545.
|
|
41
|
+
*
|
|
42
|
+
* The worker jobs did not select `endsAt`, so this service received events
|
|
43
|
+
* with the end time missing and rendered `{{scheduledEndTime}}` as an empty
|
|
44
|
+
* string — while `{{scheduledStartTime}}` rendered fine, which is what made
|
|
45
|
+
* the bug look like a template problem rather than a query problem.
|
|
46
|
+
*
|
|
47
|
+
* The worker suites pin that `endsAt` now arrives. This suite pins the
|
|
48
|
+
* consequence: given an event that carries `endsAt`, every channel that
|
|
49
|
+
* advertises `{{scheduledEndTime}}` to template authors — custom email, SMS
|
|
50
|
+
* and Slack templates, plus the subscriber webhook payload — actually renders
|
|
51
|
+
* the end time; and given an event without one, they degrade to an empty
|
|
52
|
+
* string rather than printing "undefined" at a subscriber.
|
|
53
|
+
*
|
|
54
|
+
* Everything the service talks to is a `jest.spyOn`: no database, no mail
|
|
55
|
+
* server, no Slack. What is under test is this service's own rendering, driven
|
|
56
|
+
* through the real `compileTemplate`.
|
|
57
|
+
*/
|
|
58
|
+
|
|
59
|
+
const PROJECT_ID: ObjectID = new ObjectID(
|
|
60
|
+
"11111111-1111-4111-8111-111111111111",
|
|
61
|
+
);
|
|
62
|
+
const STATUS_PAGE_ID: ObjectID = new ObjectID(
|
|
63
|
+
"22222222-2222-4222-8222-222222222222",
|
|
64
|
+
);
|
|
65
|
+
const SUBSCRIBER_ID: ObjectID = new ObjectID(
|
|
66
|
+
"33333333-3333-4333-8333-333333333333",
|
|
67
|
+
);
|
|
68
|
+
const EVENT_ID: ObjectID = new ObjectID("44444444-4444-4444-8444-444444444444");
|
|
69
|
+
|
|
70
|
+
const STARTS_AT: Date = new Date("2024-03-04T06:08:00.000Z");
|
|
71
|
+
const ENDS_AT: Date = new Date("2024-03-04T08:00:00.000Z");
|
|
72
|
+
|
|
73
|
+
const TEMPLATE_BODY: string =
|
|
74
|
+
"Start: {{scheduledStartTime}} | End: {{scheduledEndTime}}";
|
|
75
|
+
|
|
76
|
+
// What the template author expects to see rendered, in the service's own format.
|
|
77
|
+
const EXPECTED_END: string =
|
|
78
|
+
OneUptimeDate.getDateAsUserFriendlyFormattedString(ENDS_AT);
|
|
79
|
+
const EXPECTED_START: string =
|
|
80
|
+
OneUptimeDate.getDateAsUserFriendlyFormattedString(STARTS_AT);
|
|
81
|
+
|
|
82
|
+
// Every outbound channel here resolves an HTTPResponse rather than throwing.
|
|
83
|
+
function accepted(): HTTPResponse<JSONObject> {
|
|
84
|
+
return new HTTPResponse<JSONObject>(200, {}, {});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function scheduledEvent(endsAt?: Date): ScheduledMaintenance {
|
|
88
|
+
const event: ScheduledMaintenance = new ScheduledMaintenance();
|
|
89
|
+
|
|
90
|
+
event._id = EVENT_ID.toString();
|
|
91
|
+
event.projectId = PROJECT_ID;
|
|
92
|
+
event.title = "Quarterly database failover drill";
|
|
93
|
+
event.description = "Failing over the primary.";
|
|
94
|
+
event.startsAt = STARTS_AT;
|
|
95
|
+
|
|
96
|
+
if (endsAt) {
|
|
97
|
+
event.endsAt = endsAt;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const statusPage: StatusPage = new StatusPage();
|
|
101
|
+
statusPage._id = STATUS_PAGE_ID.toString();
|
|
102
|
+
event.statusPages = [statusPage];
|
|
103
|
+
|
|
104
|
+
return event;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function statusPageWithCustomProviders(): StatusPage {
|
|
108
|
+
const statusPage: StatusPage = new StatusPage();
|
|
109
|
+
|
|
110
|
+
statusPage._id = STATUS_PAGE_ID.toString();
|
|
111
|
+
statusPage.projectId = PROJECT_ID;
|
|
112
|
+
statusPage.name = "Acme Status";
|
|
113
|
+
statusPage.pageTitle = "Acme Status";
|
|
114
|
+
statusPage.isPublicStatusPage = true;
|
|
115
|
+
statusPage.showScheduledMaintenanceEventsOnStatusPage = true;
|
|
116
|
+
statusPage.allowSubscribersToChooseResources = false;
|
|
117
|
+
statusPage.allowSubscribersToChooseEventTypes = false;
|
|
118
|
+
statusPage.subscriberTimezones = [];
|
|
119
|
+
|
|
120
|
+
/*
|
|
121
|
+
* Custom email and SMS templates are only honoured when the status page
|
|
122
|
+
* brings its own SMTP/Twilio credentials, so both configs are required for
|
|
123
|
+
* this suite to exercise the custom-template path at all.
|
|
124
|
+
*/
|
|
125
|
+
const smtpConfig: ProjectSmtpConfig = new ProjectSmtpConfig();
|
|
126
|
+
smtpConfig._id = "55555555-5555-4555-8555-555555555555";
|
|
127
|
+
statusPage.smtpConfig = smtpConfig;
|
|
128
|
+
|
|
129
|
+
const callSmsConfig: ProjectCallSMSConfig = new ProjectCallSMSConfig();
|
|
130
|
+
callSmsConfig._id = "66666666-6666-4666-8666-666666666666";
|
|
131
|
+
statusPage.callSmsConfig = callSmsConfig;
|
|
132
|
+
|
|
133
|
+
return statusPage;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function subscriberOnEveryChannel(): StatusPageSubscriber {
|
|
137
|
+
const subscriber: StatusPageSubscriber = new StatusPageSubscriber();
|
|
138
|
+
|
|
139
|
+
subscriber._id = SUBSCRIBER_ID.toString();
|
|
140
|
+
subscriber.statusPageId = STATUS_PAGE_ID;
|
|
141
|
+
subscriber.isUnsubscribed = false;
|
|
142
|
+
subscriber.isSubscribedToAllResources = true;
|
|
143
|
+
subscriber.isSubscribedToAllEventTypes = true;
|
|
144
|
+
subscriber.subscriberEmail = new Email("subscriber@example.com");
|
|
145
|
+
subscriber.subscriberPhone = new Phone("+15558675309");
|
|
146
|
+
subscriber.slackIncomingWebhookUrl = URL.fromString(
|
|
147
|
+
"https://hooks.slack.com/services/T/B/X",
|
|
148
|
+
);
|
|
149
|
+
subscriber.subscriberWebhook = URL.fromString(
|
|
150
|
+
"https://hooks.example.com/subscriber",
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
return subscriber;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function customTemplate(
|
|
157
|
+
method: StatusPageSubscriberNotificationMethod,
|
|
158
|
+
): StatusPageSubscriberNotificationTemplate {
|
|
159
|
+
const template: StatusPageSubscriberNotificationTemplate =
|
|
160
|
+
new StatusPageSubscriberNotificationTemplate();
|
|
161
|
+
|
|
162
|
+
template._id = `template-${method}`;
|
|
163
|
+
template.templateBody = TEMPLATE_BODY;
|
|
164
|
+
template.emailSubject = "Window ends {{scheduledEndTime}}";
|
|
165
|
+
template.notificationMethod = method;
|
|
166
|
+
|
|
167
|
+
return template;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// The rendered body of the one custom email this run produced.
|
|
171
|
+
function sentEmailBody(): string {
|
|
172
|
+
const call: Array<unknown> = (MailService.sendMail as unknown as jest.Mock)
|
|
173
|
+
.mock.calls[0]!;
|
|
174
|
+
|
|
175
|
+
return String((call[0] as { vars: { body: string } }).vars.body);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function sentEmailSubject(): string {
|
|
179
|
+
const call: Array<unknown> = (MailService.sendMail as unknown as jest.Mock)
|
|
180
|
+
.mock.calls[0]!;
|
|
181
|
+
|
|
182
|
+
return String((call[0] as { subject: string }).subject);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function sentSmsMessage(): string {
|
|
186
|
+
const call: Array<unknown> = (SmsService.sendSms as unknown as jest.Mock).mock
|
|
187
|
+
.calls[0]!;
|
|
188
|
+
|
|
189
|
+
return String((call[0] as { message: string }).message);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function sentSlackMessage(): string {
|
|
193
|
+
return String(
|
|
194
|
+
(SlackUtil.convertMarkdownToSlackRichText as unknown as jest.Mock).mock
|
|
195
|
+
.calls[0]![0],
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function sentWebhookPayload(): Record<string, string> {
|
|
200
|
+
const call: Array<unknown> = (
|
|
201
|
+
StatusPageSubscriberWebhookUtil.sendWebhookNotification as unknown as jest.Mock
|
|
202
|
+
).mock.calls[0]!;
|
|
203
|
+
|
|
204
|
+
return (call[0] as { payload: { data: Record<string, string> } }).payload
|
|
205
|
+
.data;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
describe("scheduled maintenance subscriber notifications: {{scheduledEndTime}}", () => {
|
|
209
|
+
beforeEach(() => {
|
|
210
|
+
jest
|
|
211
|
+
.spyOn(DatabaseConfig, "getHost")
|
|
212
|
+
.mockResolvedValue(new Hostname("oneuptime.com"));
|
|
213
|
+
jest
|
|
214
|
+
.spyOn(DatabaseConfig, "getHttpProtocol")
|
|
215
|
+
.mockResolvedValue(Protocol.HTTPS);
|
|
216
|
+
|
|
217
|
+
jest
|
|
218
|
+
.spyOn(StatusPageResourceService, "findByMonitors")
|
|
219
|
+
.mockResolvedValue([]);
|
|
220
|
+
|
|
221
|
+
jest
|
|
222
|
+
.spyOn(StatusPageSubscriberService, "getStatusPagesToSendNotification")
|
|
223
|
+
.mockResolvedValue([statusPageWithCustomProviders()]);
|
|
224
|
+
|
|
225
|
+
jest
|
|
226
|
+
.spyOn(StatusPageSubscriberService, "getSubscribersByStatusPage")
|
|
227
|
+
.mockResolvedValue([subscriberOnEveryChannel()]);
|
|
228
|
+
|
|
229
|
+
jest
|
|
230
|
+
.spyOn(StatusPageService, "getStatusPageURL")
|
|
231
|
+
.mockResolvedValue("https://status.example.com");
|
|
232
|
+
|
|
233
|
+
jest
|
|
234
|
+
.spyOn(
|
|
235
|
+
StatusPageSubscriberNotificationTemplateService,
|
|
236
|
+
"getTemplateForStatusPage",
|
|
237
|
+
)
|
|
238
|
+
.mockImplementation(
|
|
239
|
+
async (data: {
|
|
240
|
+
notificationMethod: StatusPageSubscriberNotificationMethod;
|
|
241
|
+
}): Promise<StatusPageSubscriberNotificationTemplate | null> => {
|
|
242
|
+
return customTemplate(data.notificationMethod);
|
|
243
|
+
},
|
|
244
|
+
);
|
|
245
|
+
|
|
246
|
+
jest
|
|
247
|
+
.spyOn(ProjectSmtpConfigService, "toEmailServer")
|
|
248
|
+
.mockReturnValue(undefined);
|
|
249
|
+
jest
|
|
250
|
+
.spyOn(ProjectCallSMSConfigService, "toTwilioConfig")
|
|
251
|
+
.mockReturnValue(undefined);
|
|
252
|
+
|
|
253
|
+
jest
|
|
254
|
+
.spyOn(Markdown, "convertToHTML")
|
|
255
|
+
.mockImplementation(async (markdown: string): Promise<string> => {
|
|
256
|
+
return markdown;
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
jest.spyOn(MailService, "sendMail").mockResolvedValue(accepted());
|
|
260
|
+
jest.spyOn(SmsService, "sendSms").mockResolvedValue(accepted());
|
|
261
|
+
jest
|
|
262
|
+
.spyOn(SlackUtil, "sendMessageToChannelViaIncomingWebhook")
|
|
263
|
+
.mockResolvedValue(undefined as never);
|
|
264
|
+
jest
|
|
265
|
+
.spyOn(SlackUtil, "convertMarkdownToSlackRichText")
|
|
266
|
+
.mockImplementation((markdown: string): string => {
|
|
267
|
+
return markdown;
|
|
268
|
+
});
|
|
269
|
+
jest
|
|
270
|
+
.spyOn(StatusPageSubscriberWebhookUtil, "sendWebhookNotification")
|
|
271
|
+
.mockResolvedValue(accepted());
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
afterEach(() => {
|
|
275
|
+
jest.restoreAllMocks();
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
test("renders the end time in a custom email body", async () => {
|
|
279
|
+
await ScheduledMaintenanceService.notififySubscribersOnEventScheduled([
|
|
280
|
+
scheduledEvent(ENDS_AT),
|
|
281
|
+
]);
|
|
282
|
+
|
|
283
|
+
/*
|
|
284
|
+
* Guards the fixture itself: an empty EXPECTED_END would make every
|
|
285
|
+
* assertion in this file pass on the very bug it exists to catch.
|
|
286
|
+
*/
|
|
287
|
+
expect(EXPECTED_END).not.toBe("");
|
|
288
|
+
|
|
289
|
+
expect(MailService.sendMail).toHaveBeenCalledTimes(1);
|
|
290
|
+
expect(sentEmailBody()).toBe(
|
|
291
|
+
`Start: ${EXPECTED_START} | End: ${EXPECTED_END}`,
|
|
292
|
+
);
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
test("renders the end time in a custom email subject", async () => {
|
|
296
|
+
await ScheduledMaintenanceService.notififySubscribersOnEventScheduled([
|
|
297
|
+
scheduledEvent(ENDS_AT),
|
|
298
|
+
]);
|
|
299
|
+
|
|
300
|
+
expect(sentEmailSubject()).toBe(`Window ends ${EXPECTED_END}`);
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
test("renders the end time in a custom SMS template", async () => {
|
|
304
|
+
await ScheduledMaintenanceService.notififySubscribersOnEventScheduled([
|
|
305
|
+
scheduledEvent(ENDS_AT),
|
|
306
|
+
]);
|
|
307
|
+
|
|
308
|
+
expect(SmsService.sendSms).toHaveBeenCalledTimes(1);
|
|
309
|
+
expect(sentSmsMessage()).toBe(
|
|
310
|
+
`Start: ${EXPECTED_START} | End: ${EXPECTED_END}`,
|
|
311
|
+
);
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
test("renders the end time in a custom Slack template", async () => {
|
|
315
|
+
await ScheduledMaintenanceService.notififySubscribersOnEventScheduled([
|
|
316
|
+
scheduledEvent(ENDS_AT),
|
|
317
|
+
]);
|
|
318
|
+
|
|
319
|
+
expect(sentSlackMessage()).toBe(
|
|
320
|
+
`Start: ${EXPECTED_START} | End: ${EXPECTED_END}`,
|
|
321
|
+
);
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
test("sends the end time in the subscriber webhook payload", async () => {
|
|
325
|
+
await ScheduledMaintenanceService.notififySubscribersOnEventScheduled([
|
|
326
|
+
scheduledEvent(ENDS_AT),
|
|
327
|
+
]);
|
|
328
|
+
|
|
329
|
+
expect(sentWebhookPayload()["scheduledEndTime"]).toBe(EXPECTED_END);
|
|
330
|
+
expect(sentWebhookPayload()["scheduledStartTime"]).toBe(EXPECTED_START);
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
test("renders an empty end time — never 'undefined' — for an open-ended event", async () => {
|
|
334
|
+
/*
|
|
335
|
+
* An event with no end time is legitimate. The subscriber should see a
|
|
336
|
+
* blank, not the string "undefined": this is the behaviour the bug made
|
|
337
|
+
* universal, and it has to survive as the genuine no-end-time case.
|
|
338
|
+
*/
|
|
339
|
+
await ScheduledMaintenanceService.notififySubscribersOnEventScheduled([
|
|
340
|
+
scheduledEvent(undefined),
|
|
341
|
+
]);
|
|
342
|
+
|
|
343
|
+
expect(sentEmailBody()).toBe(`Start: ${EXPECTED_START} | End: `);
|
|
344
|
+
expect(sentSmsMessage()).toBe(`Start: ${EXPECTED_START} | End: `);
|
|
345
|
+
expect(sentWebhookPayload()["scheduledEndTime"]).toBe("");
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
test("renders each event's own end time when several are notified at once", async () => {
|
|
349
|
+
const laterEnd: Date = new Date("2024-03-05T09:30:00.000Z");
|
|
350
|
+
|
|
351
|
+
await ScheduledMaintenanceService.notififySubscribersOnEventScheduled([
|
|
352
|
+
scheduledEvent(ENDS_AT),
|
|
353
|
+
scheduledEvent(laterEnd),
|
|
354
|
+
]);
|
|
355
|
+
|
|
356
|
+
const bodies: Array<string> = (
|
|
357
|
+
MailService.sendMail as unknown as jest.Mock
|
|
358
|
+
).mock.calls.map((call: Array<unknown>) => {
|
|
359
|
+
return String((call[0] as { vars: { body: string } }).vars.body);
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
expect(bodies).toEqual([
|
|
363
|
+
`Start: ${EXPECTED_START} | End: ${EXPECTED_END}`,
|
|
364
|
+
`Start: ${EXPECTED_START} | End: ${OneUptimeDate.getDateAsUserFriendlyFormattedString(
|
|
365
|
+
laterEnd,
|
|
366
|
+
)}`,
|
|
367
|
+
]);
|
|
368
|
+
});
|
|
369
|
+
});
|