@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
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, jest, test } from "@jest/globals";
|
|
2
|
+
import ObjectID from "../../../Types/ObjectID";
|
|
3
|
+
import API from "../../../Utils/API";
|
|
4
|
+
import ClusterKeyAuthorization from "../../../Server/Middleware/ClusterKeyAuthorization";
|
|
5
|
+
import logger from "../../../Server/Utils/Logger";
|
|
6
|
+
import releaseIncomingCallPhoneNumber from "../../../Server/Utils/IncomingCallPhoneNumber";
|
|
7
|
+
import { JSONObject } from "../../../Types/JSON";
|
|
8
|
+
|
|
9
|
+
/*
|
|
10
|
+
* Releasing a provisioned incoming-call number when its config is deleted.
|
|
11
|
+
*
|
|
12
|
+
* The Common layer cannot reach the call provider itself, so this hands the
|
|
13
|
+
* release to the notification app's internal endpoint. It runs during cleanup,
|
|
14
|
+
* behind a delete the user has already asked for, and its whole contract is
|
|
15
|
+
* that it FAILS OPEN: the provider being unreachable, the notification app
|
|
16
|
+
* being mid-restart, or the endpoint answering 500 must all leave the parent
|
|
17
|
+
* delete to finish. A throw escaping here would leave the user's config
|
|
18
|
+
* undeletable for as long as the provider is unwell.
|
|
19
|
+
*
|
|
20
|
+
* The endpoint is internal and authenticated by cluster key rather than by a
|
|
21
|
+
* user, so the header is not optional decoration — a request without it is
|
|
22
|
+
* rejected and the number silently stays provisioned, which is a bill the
|
|
23
|
+
* project keeps paying for something it deleted.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
jest.mock("../../../Utils/API", () => {
|
|
27
|
+
return { __esModule: true, default: { post: jest.fn() } };
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
jest.mock("../../../Server/Middleware/ClusterKeyAuthorization", () => {
|
|
31
|
+
return {
|
|
32
|
+
__esModule: true,
|
|
33
|
+
default: { getClusterKeyHeaders: jest.fn() },
|
|
34
|
+
};
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
jest.mock("../../../Server/Utils/Logger", () => {
|
|
38
|
+
return {
|
|
39
|
+
__esModule: true,
|
|
40
|
+
default: {
|
|
41
|
+
debug: jest.fn(),
|
|
42
|
+
info: jest.fn(),
|
|
43
|
+
warn: jest.fn(),
|
|
44
|
+
error: jest.fn(),
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
const api: { post: jest.Mock } = API as unknown as { post: jest.Mock };
|
|
50
|
+
|
|
51
|
+
const clusterKey: { getClusterKeyHeaders: jest.Mock } =
|
|
52
|
+
ClusterKeyAuthorization as unknown as {
|
|
53
|
+
getClusterKeyHeaders: jest.Mock;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const mockedLogger: { error: jest.Mock } = logger as unknown as {
|
|
57
|
+
error: jest.Mock;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const CONFIG_ID: ObjectID = ObjectID.generate();
|
|
61
|
+
const PROVIDER_NUMBER_ID: string = "PN_provider_abc123";
|
|
62
|
+
|
|
63
|
+
function callArgs(): JSONObject {
|
|
64
|
+
return api.post.mock.calls[0]![0] as JSONObject;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function release(): Promise<void> {
|
|
68
|
+
return releaseIncomingCallPhoneNumber({
|
|
69
|
+
projectCallSMSConfigId: CONFIG_ID,
|
|
70
|
+
callProviderPhoneNumberId: PROVIDER_NUMBER_ID,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
describe("releaseIncomingCallPhoneNumber", () => {
|
|
75
|
+
beforeEach(() => {
|
|
76
|
+
jest.clearAllMocks();
|
|
77
|
+
api.post.mockResolvedValue(undefined as never);
|
|
78
|
+
clusterKey.getClusterKeyHeaders.mockReturnValue({
|
|
79
|
+
clusterkey: "test-cluster-key",
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
describe("the request it makes", () => {
|
|
84
|
+
test("posts to the notification app's internal release endpoint", async () => {
|
|
85
|
+
await release();
|
|
86
|
+
|
|
87
|
+
expect(api.post).toHaveBeenCalledTimes(1);
|
|
88
|
+
expect(callArgs()["url"]!.toString()).toContain(
|
|
89
|
+
"/api/notification/phone-number/internal/release",
|
|
90
|
+
);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
/*
|
|
94
|
+
* Both ids are needed: the provider's own id says which number to hand
|
|
95
|
+
* back, and the config id is what the endpoint authorises the release
|
|
96
|
+
* against. Dropping either silently leaves the number provisioned.
|
|
97
|
+
*/
|
|
98
|
+
test("names both the config and the provider's number", async () => {
|
|
99
|
+
await release();
|
|
100
|
+
|
|
101
|
+
const data: JSONObject = callArgs()["data"] as JSONObject;
|
|
102
|
+
|
|
103
|
+
expect(data["projectCallSMSConfigId"]).toBe(CONFIG_ID.toString());
|
|
104
|
+
expect(data["callProviderPhoneNumberId"]).toBe(PROVIDER_NUMBER_ID);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
/*
|
|
108
|
+
* The id is sent as its string form, not as an ObjectID that would
|
|
109
|
+
* serialize to whatever JSON.stringify makes of the instance.
|
|
110
|
+
*/
|
|
111
|
+
test("the config id is sent as a plain string", async () => {
|
|
112
|
+
await release();
|
|
113
|
+
|
|
114
|
+
expect(
|
|
115
|
+
typeof (callArgs()["data"] as JSONObject)["projectCallSMSConfigId"],
|
|
116
|
+
).toBe("string");
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
/*
|
|
120
|
+
* An internal endpoint with no user behind it. Without the cluster key the
|
|
121
|
+
* request is rejected and the number stays provisioned - and because this
|
|
122
|
+
* function swallows failures, that rejection would be invisible.
|
|
123
|
+
*/
|
|
124
|
+
test("carries the cluster key the internal endpoint authenticates on", async () => {
|
|
125
|
+
await release();
|
|
126
|
+
|
|
127
|
+
expect(clusterKey.getClusterKeyHeaders).toHaveBeenCalled();
|
|
128
|
+
expect((callArgs()["headers"] as JSONObject)["clusterkey"]).toBe(
|
|
129
|
+
"test-cluster-key",
|
|
130
|
+
);
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
describe("it fails open, so cleanup never blocks the delete", () => {
|
|
135
|
+
/*
|
|
136
|
+
* The parent delete has already been asked for. A provider or network
|
|
137
|
+
* problem during cleanup must not resurrect the row the user deleted.
|
|
138
|
+
*/
|
|
139
|
+
test("a rejected request resolves rather than throwing", async () => {
|
|
140
|
+
api.post.mockRejectedValue(new Error("provider unreachable") as never);
|
|
141
|
+
|
|
142
|
+
await expect(release()).resolves.toBeUndefined();
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
test("the failure is logged rather than swallowed silently", async () => {
|
|
146
|
+
api.post.mockRejectedValue(new Error("provider unreachable") as never);
|
|
147
|
+
|
|
148
|
+
await release();
|
|
149
|
+
|
|
150
|
+
expect(mockedLogger.error).toHaveBeenCalled();
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
/*
|
|
154
|
+
* A synchronous throw while the request is still being assembled - a
|
|
155
|
+
* missing cluster key secret, say - is the same kind of cleanup failure
|
|
156
|
+
* and must not escape either.
|
|
157
|
+
*/
|
|
158
|
+
test("a throw while building the request is caught too", async () => {
|
|
159
|
+
clusterKey.getClusterKeyHeaders.mockImplementation((): never => {
|
|
160
|
+
throw new Error("no cluster key configured");
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
await expect(release()).resolves.toBeUndefined();
|
|
164
|
+
expect(mockedLogger.error).toHaveBeenCalled();
|
|
165
|
+
expect(api.post).not.toHaveBeenCalled();
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
test("a successful release logs no error", async () => {
|
|
169
|
+
await release();
|
|
170
|
+
|
|
171
|
+
expect(mockedLogger.error).not.toHaveBeenCalled();
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
});
|
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
import { describe, expect, test } from "@jest/globals";
|
|
2
|
+
import {
|
|
3
|
+
NetworkTopologyDeviceRole,
|
|
4
|
+
NetworkTopologyNode,
|
|
5
|
+
NetworkTopologyNodeShape,
|
|
6
|
+
} from "../../../Types/Monitor/SnmpMonitor/NetworkTopology";
|
|
7
|
+
import DEFAULT_NETWORK_DEVICE_ROLES, {
|
|
8
|
+
DefaultNetworkDeviceRole,
|
|
9
|
+
} from "../../../Types/NetworkDevice/DefaultNetworkDeviceRole";
|
|
10
|
+
import {
|
|
11
|
+
DEVICE_ROLES_IN_LEGEND_ORDER,
|
|
12
|
+
DEVICE_ROLE_LABELS,
|
|
13
|
+
} from "../../../Utils/Monitor/NetworkDeviceRoleUtil";
|
|
14
|
+
import { shapeForNode } from "../../../../App/FeatureSet/Dashboard/src/Components/NetworkDevice/TopologyNodeShape";
|
|
15
|
+
|
|
16
|
+
/*
|
|
17
|
+
* DEFAULT_NETWORK_DEVICE_ROLES is what every project starts life with, and
|
|
18
|
+
* the promise it makes is a conservative one: a project seeded from it must
|
|
19
|
+
* behave EXACTLY as the product behaved when roles were a hardcoded union.
|
|
20
|
+
* Same eleven roles, same labels, same silhouettes, same tiering, same
|
|
21
|
+
* SNMP-versus-monitor default.
|
|
22
|
+
*
|
|
23
|
+
* That promise is only checkable by comparing the seed list against the three
|
|
24
|
+
* modules it replaced — the labels in NetworkDeviceRoleUtil, the SHAPE_BY_ROLE
|
|
25
|
+
* map in the renderer, the CORE_DEVICE_ROLES set in the layout and the
|
|
26
|
+
* MONITOR_BACKED_ROLES set in the adopt flow. So that is what these tests do:
|
|
27
|
+
* every one of them is a cross-check against the old source of truth, not a
|
|
28
|
+
* restatement of the new one.
|
|
29
|
+
*
|
|
30
|
+
* The shape check imports the renderer directly (SHAPE_BY_ROLE itself is not
|
|
31
|
+
* exported, but shapeForNode is, and it is the function the map actually
|
|
32
|
+
* calls). Common's jest maps "Common/*" back to this package and several
|
|
33
|
+
* Common tests already import App/FeatureSet/Dashboard sources this way, so a
|
|
34
|
+
* genuine comparison is available and is worth far more than a second copy of
|
|
35
|
+
* the table. The literal table below is kept anyway, so a reader can see the
|
|
36
|
+
* eleven answers without following the import.
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
type MakeDeviceNodeFunction = (
|
|
40
|
+
role: NetworkTopologyDeviceRole,
|
|
41
|
+
) => NetworkTopologyNode;
|
|
42
|
+
|
|
43
|
+
/*
|
|
44
|
+
* A node as an OLD payload carried it: the classified role and nothing else.
|
|
45
|
+
* Deliberately no roleShape — that is the per-project stamp, and stamping it
|
|
46
|
+
* would make shapeForNode echo the value back instead of consulting the
|
|
47
|
+
* built-in map this test is comparing against.
|
|
48
|
+
*/
|
|
49
|
+
const makeDeviceNode: MakeDeviceNodeFunction = (
|
|
50
|
+
role: NetworkTopologyDeviceRole,
|
|
51
|
+
): NetworkTopologyNode => {
|
|
52
|
+
return {
|
|
53
|
+
id: `device-${role}`,
|
|
54
|
+
name: role,
|
|
55
|
+
isManaged: true,
|
|
56
|
+
status: "up",
|
|
57
|
+
kind: "device",
|
|
58
|
+
role: role,
|
|
59
|
+
};
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
describe("DEFAULT_NETWORK_DEVICE_ROLES — what a fresh project is given", () => {
|
|
63
|
+
test("seeds exactly eleven roles", () => {
|
|
64
|
+
expect(DEFAULT_NETWORK_DEVICE_ROLES).toHaveLength(11);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
/*
|
|
68
|
+
* "unknown" is the classifier saying it has no answer, not a job a box
|
|
69
|
+
* does. Offering it as a row an operator could assign would turn "I don't
|
|
70
|
+
* know yet" into "never classify this device again", which is a different
|
|
71
|
+
* and much worse statement.
|
|
72
|
+
*/
|
|
73
|
+
test("does not seed 'unknown', which is an absence of an answer rather than a role", () => {
|
|
74
|
+
const keys: Array<string> = DEFAULT_NETWORK_DEVICE_ROLES.map(
|
|
75
|
+
(role: DefaultNetworkDeviceRole): string => {
|
|
76
|
+
return role.key;
|
|
77
|
+
},
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
expect(keys).not.toContain("unknown");
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test("every key is one of the built-in roles the SNMP classifier can return", () => {
|
|
84
|
+
/*
|
|
85
|
+
* The classifier is evidence-driven and speaks the fixed union; it cannot
|
|
86
|
+
* invent a project's custom role. A seeded key outside that union would be
|
|
87
|
+
* a row nothing could ever match.
|
|
88
|
+
*/
|
|
89
|
+
for (const role of DEFAULT_NETWORK_DEVICE_ROLES) {
|
|
90
|
+
expect(DEVICE_ROLES_IN_LEGEND_ORDER).toContain(role.key);
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
describe("DEFAULT_NETWORK_DEVICE_ROLES — order", () => {
|
|
96
|
+
/*
|
|
97
|
+
* The seeded `order` column is this array's index, and the legend reads in
|
|
98
|
+
* DEVICE_ROLES_IN_LEGEND_ORDER. If the two ever diverge, a fresh project's
|
|
99
|
+
* settings page and its map legend list the same eleven roles in two
|
|
100
|
+
* different sequences — the sort of inconsistency nobody files a bug for and
|
|
101
|
+
* everybody trips over.
|
|
102
|
+
*/
|
|
103
|
+
test("the keys are DEVICE_ROLES_IN_LEGEND_ORDER, in that exact order", () => {
|
|
104
|
+
const keys: Array<NetworkTopologyDeviceRole> =
|
|
105
|
+
DEFAULT_NETWORK_DEVICE_ROLES.map(
|
|
106
|
+
(role: DefaultNetworkDeviceRole): NetworkTopologyDeviceRole => {
|
|
107
|
+
return role.key;
|
|
108
|
+
},
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
expect(keys).toEqual(Array.from(DEVICE_ROLES_IN_LEGEND_ORDER));
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
test("the two lists are the same SET as well as the same sequence", () => {
|
|
115
|
+
// Guards the case where both lists are edited but only one is reordered.
|
|
116
|
+
const seeded: Set<string> = new Set<string>(
|
|
117
|
+
DEFAULT_NETWORK_DEVICE_ROLES.map(
|
|
118
|
+
(role: DefaultNetworkDeviceRole): string => {
|
|
119
|
+
return role.key;
|
|
120
|
+
},
|
|
121
|
+
),
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
expect(seeded.size).toBe(DEVICE_ROLES_IN_LEGEND_ORDER.length);
|
|
125
|
+
for (const key of DEVICE_ROLES_IN_LEGEND_ORDER) {
|
|
126
|
+
expect(seeded.has(key)).toBe(true);
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
describe("DEFAULT_NETWORK_DEVICE_ROLES — names reproduce the built-in labels", () => {
|
|
132
|
+
/*
|
|
133
|
+
* The name is what the legend, the detail panel and the accessible label
|
|
134
|
+
* show once a project is seeded, because the topology builder stamps the
|
|
135
|
+
* configured label onto every node. If a seeded name drifted from
|
|
136
|
+
* DEVICE_ROLE_LABELS, a freshly created project's map would be relabelled
|
|
137
|
+
* relative to an old one for no reason the operator asked for.
|
|
138
|
+
*/
|
|
139
|
+
test("every default's name is the built-in label for its key", () => {
|
|
140
|
+
for (const role of DEFAULT_NETWORK_DEVICE_ROLES) {
|
|
141
|
+
expect(role.name).toBe(DEVICE_ROLE_LABELS[role.key]);
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
test("the eleven names are exactly the eleven non-unknown labels", () => {
|
|
146
|
+
const names: Array<string> = DEFAULT_NETWORK_DEVICE_ROLES.map(
|
|
147
|
+
(role: DefaultNetworkDeviceRole): string => {
|
|
148
|
+
return role.name;
|
|
149
|
+
},
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
expect(names).toEqual([
|
|
153
|
+
"Router",
|
|
154
|
+
"Switch",
|
|
155
|
+
"Firewall",
|
|
156
|
+
"Wireless AP",
|
|
157
|
+
"Load balancer",
|
|
158
|
+
"Server",
|
|
159
|
+
"Storage",
|
|
160
|
+
"Printer",
|
|
161
|
+
"Camera",
|
|
162
|
+
"IP phone",
|
|
163
|
+
"Host",
|
|
164
|
+
]);
|
|
165
|
+
expect(names).not.toContain(DEVICE_ROLE_LABELS.unknown);
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
describe("DEFAULT_NETWORK_DEVICE_ROLES — shapes reproduce the renderer's map", () => {
|
|
170
|
+
/*
|
|
171
|
+
* Mirrors the renderer's private SHAPE_BY_ROLE. Written out so the eleven
|
|
172
|
+
* silhouettes are readable here; the test below is the one that proves they
|
|
173
|
+
* still agree with the renderer.
|
|
174
|
+
*/
|
|
175
|
+
const EXPECTED_SHAPE_BY_KEY: Readonly<
|
|
176
|
+
Record<string, NetworkTopologyNodeShape>
|
|
177
|
+
> = {
|
|
178
|
+
router: "circle",
|
|
179
|
+
switch: "rounded-square",
|
|
180
|
+
firewall: "diamond",
|
|
181
|
+
wirelessAccessPoint: "triangle",
|
|
182
|
+
loadBalancer: "hexagon",
|
|
183
|
+
server: "tower",
|
|
184
|
+
storage: "cylinder",
|
|
185
|
+
// The four leaf roles deliberately share the endpoint rect.
|
|
186
|
+
printer: "rect",
|
|
187
|
+
camera: "rect",
|
|
188
|
+
phone: "rect",
|
|
189
|
+
host: "rect",
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
test("every default's topologyShape is what the renderer would draw for that role", () => {
|
|
193
|
+
/*
|
|
194
|
+
* The real cross-check, and the regression this whole file exists to
|
|
195
|
+
* prevent: a project seeded from these defaults must draw the map it drew
|
|
196
|
+
* before roles were configurable. shapeForNode on a node carrying only a
|
|
197
|
+
* classified role is exactly the pre-feature code path.
|
|
198
|
+
*/
|
|
199
|
+
for (const role of DEFAULT_NETWORK_DEVICE_ROLES) {
|
|
200
|
+
expect(role.topologyShape).toBe(shapeForNode(makeDeviceNode(role.key)));
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
test("and it is the silhouette the table above names", () => {
|
|
205
|
+
for (const role of DEFAULT_NETWORK_DEVICE_ROLES) {
|
|
206
|
+
expect(role.topologyShape).toBe(EXPECTED_SHAPE_BY_KEY[role.key]);
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
test("the seven infrastructure roles are seven distinct silhouettes", () => {
|
|
211
|
+
// A map on which a router, a switch and a firewall look alike is unreadable.
|
|
212
|
+
const infrastructureShapes: Array<NetworkTopologyNodeShape> =
|
|
213
|
+
DEFAULT_NETWORK_DEVICE_ROLES.filter(
|
|
214
|
+
(role: DefaultNetworkDeviceRole): boolean => {
|
|
215
|
+
return role.topologyShape !== "rect";
|
|
216
|
+
},
|
|
217
|
+
).map((role: DefaultNetworkDeviceRole): NetworkTopologyNodeShape => {
|
|
218
|
+
return role.topologyShape;
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
expect(infrastructureShapes).toHaveLength(7);
|
|
222
|
+
expect(new Set<string>(infrastructureShapes).size).toBe(7);
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
test("every seeded shape is one the renderer has geometry for", () => {
|
|
226
|
+
/*
|
|
227
|
+
* topologyShape is a free-text column and the catalogue drops any value
|
|
228
|
+
* the renderer cannot draw. A seeded value it dropped would leave the
|
|
229
|
+
* node falling back to a circle, silently.
|
|
230
|
+
*/
|
|
231
|
+
const drawableShapes: ReadonlyArray<NetworkTopologyNodeShape> = [
|
|
232
|
+
"circle",
|
|
233
|
+
"rounded-square",
|
|
234
|
+
"diamond",
|
|
235
|
+
"triangle",
|
|
236
|
+
"hexagon",
|
|
237
|
+
"tower",
|
|
238
|
+
"cylinder",
|
|
239
|
+
"rect",
|
|
240
|
+
];
|
|
241
|
+
|
|
242
|
+
for (const role of DEFAULT_NETWORK_DEVICE_ROLES) {
|
|
243
|
+
expect(drawableShapes).toContain(role.topologyShape);
|
|
244
|
+
}
|
|
245
|
+
});
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
describe("DEFAULT_NETWORK_DEVICE_ROLES — isCoreLayer reproduces CORE_DEVICE_ROLES", () => {
|
|
249
|
+
/*
|
|
250
|
+
* Mirrors the private CORE_DEVICE_ROLES set in TopologyLayout. Note what is
|
|
251
|
+
* NOT in it: "switch". A switch is infrastructure, but it hangs off a
|
|
252
|
+
* router, and the top tier is for the devices that hang off nothing.
|
|
253
|
+
*
|
|
254
|
+
* tierForNode now reads node.isCoreLayerRole BEFORE node.role, so these
|
|
255
|
+
* eleven booleans are what decides the tier on every seeded project. Get one
|
|
256
|
+
* wrong and the parent-child tree roots itself on the wrong device.
|
|
257
|
+
*/
|
|
258
|
+
const CORE_KEYS: ReadonlyArray<string> = [
|
|
259
|
+
"router",
|
|
260
|
+
"firewall",
|
|
261
|
+
"loadBalancer",
|
|
262
|
+
];
|
|
263
|
+
|
|
264
|
+
test("exactly router, firewall and load balancer sit at the core", () => {
|
|
265
|
+
const coreKeys: Array<string> = DEFAULT_NETWORK_DEVICE_ROLES.filter(
|
|
266
|
+
(role: DefaultNetworkDeviceRole): boolean => {
|
|
267
|
+
return role.isCoreLayer;
|
|
268
|
+
},
|
|
269
|
+
).map((role: DefaultNetworkDeviceRole): string => {
|
|
270
|
+
return role.key;
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
expect(coreKeys).toEqual(CORE_KEYS);
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
test("every other default is explicitly not core, never left undefined", () => {
|
|
277
|
+
/*
|
|
278
|
+
* The flag has to be a real boolean on every row: tierForNode branches on
|
|
279
|
+
* `isCoreLayerRole !== undefined`, so an absent value would silently hand
|
|
280
|
+
* the decision back to the FDB heuristic the role was meant to overrule.
|
|
281
|
+
*/
|
|
282
|
+
for (const role of DEFAULT_NETWORK_DEVICE_ROLES) {
|
|
283
|
+
expect(typeof role.isCoreLayer).toBe("boolean");
|
|
284
|
+
expect(role.isCoreLayer).toBe(CORE_KEYS.includes(role.key));
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
test("a switch is not core, and neither is an access point", () => {
|
|
289
|
+
// Called out because both are tempting to promote and both would be wrong.
|
|
290
|
+
const byKey: Map<string, DefaultNetworkDeviceRole> = new Map<
|
|
291
|
+
string,
|
|
292
|
+
DefaultNetworkDeviceRole
|
|
293
|
+
>(
|
|
294
|
+
DEFAULT_NETWORK_DEVICE_ROLES.map(
|
|
295
|
+
(
|
|
296
|
+
role: DefaultNetworkDeviceRole,
|
|
297
|
+
): [string, DefaultNetworkDeviceRole] => {
|
|
298
|
+
return [role.key, role];
|
|
299
|
+
},
|
|
300
|
+
),
|
|
301
|
+
);
|
|
302
|
+
|
|
303
|
+
expect(byKey.get("switch")?.isCoreLayer).toBe(false);
|
|
304
|
+
expect(byKey.get("wirelessAccessPoint")?.isCoreLayer).toBe(false);
|
|
305
|
+
});
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
describe("DEFAULT_NETWORK_DEVICE_ROLES — isSnmpWalkable reproduces MONITOR_BACKED_ROLES", () => {
|
|
309
|
+
/*
|
|
310
|
+
* Mirrors the private MONITOR_BACKED_ROLES set in AdoptNeighborUtil, which
|
|
311
|
+
* lists the roles that are never SNMP-walkable in practice. Adopting a
|
|
312
|
+
* neighbour with one of them defaults the new device to a monitor rather
|
|
313
|
+
* than to SNMP — defaulting a desk phone to SNMP would queue a walk it can
|
|
314
|
+
* only fail and leave the operator reading "pending" forever.
|
|
315
|
+
*
|
|
316
|
+
* Everything else, including an unclassified peer, defaults to SNMP: an
|
|
317
|
+
* unidentified box on a switch uplink is far more often a switch nobody has
|
|
318
|
+
* added yet than it is a kiosk.
|
|
319
|
+
*/
|
|
320
|
+
const MONITOR_BACKED_KEYS: ReadonlyArray<string> = [
|
|
321
|
+
"printer",
|
|
322
|
+
"camera",
|
|
323
|
+
"phone",
|
|
324
|
+
"host",
|
|
325
|
+
];
|
|
326
|
+
|
|
327
|
+
test("exactly printer, camera, phone and host are not walkable", () => {
|
|
328
|
+
const notWalkable: Array<string> = DEFAULT_NETWORK_DEVICE_ROLES.filter(
|
|
329
|
+
(role: DefaultNetworkDeviceRole): boolean => {
|
|
330
|
+
return !role.isSnmpWalkable;
|
|
331
|
+
},
|
|
332
|
+
).map((role: DefaultNetworkDeviceRole): string => {
|
|
333
|
+
return role.key;
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
expect(notWalkable).toEqual(MONITOR_BACKED_KEYS);
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
test("every other default is walkable, and the flag is always a real boolean", () => {
|
|
340
|
+
/*
|
|
341
|
+
* buildNeighborAdoptionDraft branches on
|
|
342
|
+
* `isSnmpWalkableRole !== undefined`, so an absent value falls back to the
|
|
343
|
+
* built-in set rather than being read as "not walkable".
|
|
344
|
+
*/
|
|
345
|
+
for (const role of DEFAULT_NETWORK_DEVICE_ROLES) {
|
|
346
|
+
expect(typeof role.isSnmpWalkable).toBe("boolean");
|
|
347
|
+
expect(role.isSnmpWalkable).toBe(!MONITOR_BACKED_KEYS.includes(role.key));
|
|
348
|
+
}
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
test("the four non-walkable roles are exactly the four drawn as a leaf rect", () => {
|
|
352
|
+
/*
|
|
353
|
+
* Not a coincidence worth breaking: "something plugged into an access
|
|
354
|
+
* port" is the same set of boxes as "nothing worth walking". If the two
|
|
355
|
+
* ever diverge, one of the two lists was edited without the other.
|
|
356
|
+
*/
|
|
357
|
+
const rectKeys: Array<string> = DEFAULT_NETWORK_DEVICE_ROLES.filter(
|
|
358
|
+
(role: DefaultNetworkDeviceRole): boolean => {
|
|
359
|
+
return role.topologyShape === "rect";
|
|
360
|
+
},
|
|
361
|
+
).map((role: DefaultNetworkDeviceRole): string => {
|
|
362
|
+
return role.key;
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
expect(rectKeys).toEqual(MONITOR_BACKED_KEYS);
|
|
366
|
+
});
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
describe("DEFAULT_NETWORK_DEVICE_ROLES — the rows are storable as they stand", () => {
|
|
370
|
+
test("keys are unique", () => {
|
|
371
|
+
// The column is unique per project; a duplicate would fail the seed.
|
|
372
|
+
const keys: Array<string> = DEFAULT_NETWORK_DEVICE_ROLES.map(
|
|
373
|
+
(role: DefaultNetworkDeviceRole): string => {
|
|
374
|
+
return role.key;
|
|
375
|
+
},
|
|
376
|
+
);
|
|
377
|
+
|
|
378
|
+
expect(new Set<string>(keys).size).toBe(keys.length);
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
test("names are unique", () => {
|
|
382
|
+
/*
|
|
383
|
+
* Also a unique column, and the seeder skips a default whose NAME already
|
|
384
|
+
* exists — two defaults sharing a name would silently seed only one.
|
|
385
|
+
*/
|
|
386
|
+
const names: Array<string> = DEFAULT_NETWORK_DEVICE_ROLES.map(
|
|
387
|
+
(role: DefaultNetworkDeviceRole): string => {
|
|
388
|
+
return role.name;
|
|
389
|
+
},
|
|
390
|
+
);
|
|
391
|
+
|
|
392
|
+
expect(new Set<string>(names).size).toBe(names.length);
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
test("every default has a name and a description a human can read", () => {
|
|
396
|
+
/*
|
|
397
|
+
* The description is the only explanation the settings page offers for
|
|
398
|
+
* what a role means, and a blank one there reads as a broken page.
|
|
399
|
+
*/
|
|
400
|
+
for (const role of DEFAULT_NETWORK_DEVICE_ROLES) {
|
|
401
|
+
expect(role.name.trim()).not.toBe("");
|
|
402
|
+
expect(role.name).toBe(role.name.trim());
|
|
403
|
+
expect(role.description.trim()).not.toBe("");
|
|
404
|
+
expect(role.description.length).toBeGreaterThan(10);
|
|
405
|
+
}
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
test("descriptions are distinct, so no two roles explain themselves the same way", () => {
|
|
409
|
+
const descriptions: Array<string> = DEFAULT_NETWORK_DEVICE_ROLES.map(
|
|
410
|
+
(role: DefaultNetworkDeviceRole): string => {
|
|
411
|
+
return role.description;
|
|
412
|
+
},
|
|
413
|
+
);
|
|
414
|
+
|
|
415
|
+
expect(new Set<string>(descriptions).size).toBe(descriptions.length);
|
|
416
|
+
});
|
|
417
|
+
});
|