@oneuptime/common 14.0.5 → 14.0.7
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/InventoryItem.ts +23 -12
- package/Server/Infrastructure/Postgres/SchemaMigrations/1795200000000-AddInventoryItemProjectEntityKeyIndex.ts +217 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +2 -0
- package/Server/Services/AlertService.ts +32 -36
- package/Server/Services/IncidentAlertService.ts +609 -2
- package/Server/Services/IncidentService.ts +134 -44
- package/Server/Services/InventoryItemService.ts +38 -0
- package/Server/Services/IoTDeviceCredentialService.ts +13 -4
- package/Server/Services/MetricService.ts +1 -1
- package/Server/Services/ScheduledMaintenanceService.ts +50 -46
- package/Server/Services/UserNotificationRuleService.ts +55 -53
- package/Server/Services/WorkspaceNotificationSummaryService.ts +38 -20
- package/Server/Types/Database/QueryHelper.ts +54 -1
- package/Server/Utils/AI/Toolbox/MetricTools.ts +13 -2
- package/Server/Utils/AI/Toolbox/ResourceTools.ts +12 -1
- package/Server/Utils/AffectedResources/LinkedAffectedResources.ts +576 -0
- package/Server/Utils/Alert/AlertStateChangeAuthorization.ts +129 -0
- package/Server/Utils/AnalyticsDatabase/StatementGenerator.ts +1 -1
- package/Server/Utils/Inventory/InventoryOverviewAggregation.ts +111 -0
- package/Server/Utils/Monitor/Criteria/CompareCriteria.ts +33 -0
- package/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.ts +4 -3
- package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +3 -1
- package/Server/Utils/Response.ts +31 -0
- package/Server/Utils/Topology/TopologyConcurrencyLimiter.ts +229 -0
- package/Server/Utils/Topology/TopologyQueries.ts +1350 -0
- package/Server/Utils/Topology/TopologyRequest.ts +463 -0
- package/Server/Utils/Topology/TopologyResponseCache.ts +183 -0
- package/Server/Utils/Topology/TopologySql.ts +1024 -0
- package/Tests/App/Dashboard/AcknowledgeAlertsOnDeclare.test.ts +369 -0
- package/Tests/App/Dashboard/AffectedResourcesSlo.test.tsx +1 -1
- package/Tests/App/Dashboard/AffectedResourcesSummaryStep.test.tsx +510 -123
- package/Tests/App/Dashboard/AlertsTableAffectedResources.test.tsx +490 -0
- package/Tests/App/Dashboard/ChangeAlertStateDeclareIncident.test.tsx +886 -0
- package/Tests/App/Dashboard/CreateReviewStepRoleSummary.test.tsx +1250 -0
- package/Tests/App/Dashboard/CreateReviewStepSelectionSummary.test.tsx +738 -0
- package/Tests/App/Dashboard/DeclareIncidentFromAlert.test.ts +417 -0
- package/Tests/App/Dashboard/EntityDetailPanel.test.tsx +1948 -229
- package/Tests/App/Dashboard/EntityDetailPanelLifecycle.test.tsx +493 -0
- package/Tests/App/Dashboard/EntityDetailPanelNavigation.test.tsx +439 -0
- package/Tests/App/Dashboard/EventOverviewPages.test.tsx +292 -1
- package/Tests/App/Dashboard/EventStatusPanel.test.tsx +798 -2
- package/Tests/App/Dashboard/FetchStateAndSeverityElements.test.tsx +669 -0
- package/Tests/App/Dashboard/FetchUsers.test.tsx +230 -0
- package/Tests/App/Dashboard/IncidentCreateAcknowledgeAlerts.test.tsx +1611 -0
- package/Tests/App/Dashboard/InfrastructureExplorer.test.tsx +1365 -57
- package/Tests/App/Dashboard/InfrastructureGraph.test.tsx +964 -23
- package/Tests/App/Dashboard/InventoryRelationships.test.tsx +885 -0
- package/Tests/App/Dashboard/NetworkTopologyExplorerSearch.test.tsx +1267 -0
- package/Tests/App/Dashboard/ResourceOverviewTileTooltips.test.tsx +4 -1
- package/Tests/App/Dashboard/RumOverviewPage.test.tsx +127 -0
- package/Tests/App/Dashboard/ServiceMapDatabaseLink.test.tsx +201 -15
- package/Tests/App/Dashboard/ServiceMapGraph.test.tsx +508 -36
- package/Tests/App/Dashboard/SiteSearchBox.test.tsx +2110 -0
- package/Tests/App/Dashboard/TelemetryMetricsSignals.test.ts +168 -8
- package/Tests/App/Dashboard/TopologyDataLifecycle.test.tsx +583 -0
- package/Tests/App/Dashboard/TopologyDataLoading.test.tsx +982 -218
- package/Tests/App/Dashboard/TopologyPageNavigation.test.tsx +818 -114
- package/Tests/Server/Infrastructure/Postgres/RenameInventoryItemMigration.test.ts +90 -21
- package/Tests/Server/Services/DomainService.test.ts +525 -0
- package/Tests/Server/Services/IncidentAlertAcknowledgeOnDeclare.test.ts +2630 -0
- package/Tests/Server/Services/IncidentAlertPostgres.test.ts +1625 -1
- package/Tests/Server/Services/IncidentDeclareAcknowledgeAlerts.test.ts +2189 -0
- package/Tests/Server/Services/InventoryItemServiceOverviewCounts.test.ts +345 -0
- package/Tests/Server/Services/InventoryOverviewPostgres.test.ts +254 -0
- package/Tests/Server/Services/IoTDeviceCredentialService.test.ts +819 -0
- package/Tests/Server/Services/LinkedAffectedResourcesCreatedFeed.test.ts +420 -0
- package/Tests/Server/Services/MetricServiceAggregate.test.ts +12 -0
- package/Tests/Server/Services/SavedViewDefaults.test.ts +325 -0
- package/Tests/Server/Services/SloAffectedResourceCreatedFeed.test.ts +92 -36
- package/Tests/Server/Services/StatusPageCertificateService.test.ts +169 -0
- package/Tests/Server/Services/TeamComplianceSettingService.test.ts +191 -0
- package/Tests/Server/Types/Database/QueryHelperSearchAllWords.test.ts +651 -0
- package/Tests/Server/Utils/AI/AlertWriteTools.test.ts +256 -0
- package/Tests/Server/Utils/AI/IncidentWriteTools.test.ts +482 -0
- package/Tests/Server/Utils/AffectedResources/LinkedAffectedResources.test.ts +858 -0
- package/Tests/Server/Utils/Alert/AlertStateChangeAuthorization.test.ts +1172 -0
- package/Tests/Server/Utils/Monitor/Criteria/CompareCriteria.test.ts +54 -0
- package/Tests/Server/Utils/Monitor/MetricDecimalThreshold.test.ts +417 -0
- package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorAffectedResourceEdgeCases.test.ts +96 -0
- package/Tests/Server/Utils/Topology/InventoryItemEntityKeyIndexMigration.test.ts +386 -0
- package/Tests/Server/Utils/Topology/InventoryItemEntityKeyIndexMigrationPostgres.test.ts +430 -0
- package/Tests/Server/Utils/Topology/TopologyActivityParityPostgres.test.ts +279 -0
- package/Tests/Server/Utils/Topology/TopologyConcurrencyLimiter.test.ts +534 -0
- package/Tests/Server/Utils/Topology/TopologyEstateGenerator.ts +624 -0
- package/Tests/Server/Utils/Topology/TopologyJsonResponse.test.ts +90 -0
- package/Tests/Server/Utils/Topology/TopologyQueries.test.ts +1634 -0
- package/Tests/Server/Utils/Topology/TopologyQueriesDifferentialPostgres.test.ts +683 -0
- package/Tests/Server/Utils/Topology/TopologyQueriesPostgres.test.ts +2624 -0
- package/Tests/Server/Utils/Topology/TopologyReference.ts +1018 -0
- package/Tests/Server/Utils/Topology/TopologyRequest.test.ts +643 -0
- package/Tests/Server/Utils/Topology/TopologyResponseCache.test.ts +305 -0
- package/Tests/Server/Utils/Topology/TopologySql.test.ts +973 -0
- package/Tests/Types/BaseDatabase/AggregationType.test.ts +5 -1
- package/Tests/Types/Dashboard/KubernetesDashboardTemplate.test.ts +184 -0
- package/Tests/Types/Dashboard/KubernetesDashboardTemplates.test.ts +107 -0
- package/Tests/Types/Monitor/KubernetesAlertTemplates.test.ts +8 -14
- package/Tests/Types/Monitor/KubernetesMetricCatalog.test.ts +4 -3
- package/Tests/Types/Monitor/Utils/KubernetesAgentEmittedMetrics.ts +27 -6
- package/Tests/Types/Topology/TopologyTypeRules.test.ts +176 -0
- package/Tests/UI/Rum/ChunkLoader.test.ts +76 -0
- package/Tests/UI/Rum/ReplayRailDetail.test.tsx +53 -0
- package/Tests/UI/Rum/ReplaySignals.test.ts +54 -0
- package/Tests/Utils/Dashboard/KubernetesClusterCpuTileRepair.test.ts +370 -0
- package/Tests/Utils/Incident/IncidentFromAlerts.test.ts +302 -0
- package/Tests/Utils/Rules/MonitorTypeRuleCriteria.test.ts +181 -0
- package/Types/BaseDatabase/AggregationType.ts +7 -0
- package/Types/Dashboard/DashboardTemplates.ts +20 -7
- package/Types/Incident/IncidentAlertLink.ts +19 -0
- package/Types/Rum/SessionReplayCustomEvents.ts +29 -3
- package/Types/Rum/WebVitals.ts +13 -0
- package/Types/Telemetry/InventoryLiveness.ts +26 -0
- package/Types/Topology/TopologyApi.ts +428 -0
- package/Types/Topology/TopologyTypeRules.ts +191 -0
- package/UI/Components/Input/Input.tsx +11 -0
- package/Utils/Dashboard/KubernetesClusterCpuTileRepair.ts +147 -0
- package/Utils/Incident/IncidentFromAlerts.ts +78 -0
- package/build/dist/Models/DatabaseModels/InventoryItem.js +20 -11
- package/build/dist/Models/DatabaseModels/InventoryItem.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795200000000-AddInventoryItemProjectEntityKeyIndex.js +165 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795200000000-AddInventoryItemProjectEntityKeyIndex.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/AlertService.js +21 -33
- package/build/dist/Server/Services/AlertService.js.map +1 -1
- package/build/dist/Server/Services/IncidentAlertService.js +457 -3
- package/build/dist/Server/Services/IncidentAlertService.js.map +1 -1
- package/build/dist/Server/Services/IncidentService.js +96 -42
- package/build/dist/Server/Services/IncidentService.js.map +1 -1
- package/build/dist/Server/Services/InventoryItemService.js +31 -0
- package/build/dist/Server/Services/InventoryItemService.js.map +1 -1
- package/build/dist/Server/Services/IoTDeviceCredentialService.js +12 -5
- package/build/dist/Server/Services/IoTDeviceCredentialService.js.map +1 -1
- package/build/dist/Server/Services/MetricService.js +1 -1
- package/build/dist/Server/Services/ScheduledMaintenanceService.js +37 -40
- package/build/dist/Server/Services/ScheduledMaintenanceService.js.map +1 -1
- package/build/dist/Server/Services/UserNotificationRuleService.js +55 -51
- package/build/dist/Server/Services/UserNotificationRuleService.js.map +1 -1
- package/build/dist/Server/Services/WorkspaceNotificationSummaryService.js +42 -25
- package/build/dist/Server/Services/WorkspaceNotificationSummaryService.js.map +1 -1
- package/build/dist/Server/Types/Database/QueryHelper.js +47 -1
- package/build/dist/Server/Types/Database/QueryHelper.js.map +1 -1
- package/build/dist/Server/Utils/AI/Toolbox/MetricTools.js +13 -2
- package/build/dist/Server/Utils/AI/Toolbox/MetricTools.js.map +1 -1
- package/build/dist/Server/Utils/AI/Toolbox/ResourceTools.js +12 -1
- package/build/dist/Server/Utils/AI/Toolbox/ResourceTools.js.map +1 -1
- package/build/dist/Server/Utils/AffectedResources/LinkedAffectedResources.js +405 -0
- package/build/dist/Server/Utils/AffectedResources/LinkedAffectedResources.js.map +1 -0
- package/build/dist/Server/Utils/Alert/AlertStateChangeAuthorization.js +112 -0
- package/build/dist/Server/Utils/Alert/AlertStateChangeAuthorization.js.map +1 -0
- package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js +1 -1
- package/build/dist/Server/Utils/Inventory/InventoryOverviewAggregation.js +81 -0
- package/build/dist/Server/Utils/Inventory/InventoryOverviewAggregation.js.map +1 -0
- package/build/dist/Server/Utils/Monitor/Criteria/CompareCriteria.js +31 -0
- package/build/dist/Server/Utils/Monitor/Criteria/CompareCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.js +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
- package/build/dist/Server/Utils/Response.js +30 -0
- package/build/dist/Server/Utils/Response.js.map +1 -1
- package/build/dist/Server/Utils/Topology/TopologyConcurrencyLimiter.js +178 -0
- package/build/dist/Server/Utils/Topology/TopologyConcurrencyLimiter.js.map +1 -0
- package/build/dist/Server/Utils/Topology/TopologyQueries.js +884 -0
- package/build/dist/Server/Utils/Topology/TopologyQueries.js.map +1 -0
- package/build/dist/Server/Utils/Topology/TopologyRequest.js +275 -0
- package/build/dist/Server/Utils/Topology/TopologyRequest.js.map +1 -0
- package/build/dist/Server/Utils/Topology/TopologyResponseCache.js +146 -0
- package/build/dist/Server/Utils/Topology/TopologyResponseCache.js.map +1 -0
- package/build/dist/Server/Utils/Topology/TopologySql.js +688 -0
- package/build/dist/Server/Utils/Topology/TopologySql.js.map +1 -0
- package/build/dist/Types/BaseDatabase/AggregationType.js +7 -0
- package/build/dist/Types/BaseDatabase/AggregationType.js.map +1 -1
- package/build/dist/Types/Dashboard/DashboardTemplates.js +20 -7
- package/build/dist/Types/Dashboard/DashboardTemplates.js.map +1 -1
- package/build/dist/Types/Incident/IncidentAlertLink.js +17 -0
- package/build/dist/Types/Incident/IncidentAlertLink.js.map +1 -1
- package/build/dist/Types/Rum/SessionReplayCustomEvents.js.map +1 -1
- package/build/dist/Types/Rum/WebVitals.js +12 -0
- package/build/dist/Types/Rum/WebVitals.js.map +1 -1
- package/build/dist/Types/Telemetry/InventoryLiveness.js +23 -0
- package/build/dist/Types/Telemetry/InventoryLiveness.js.map +1 -1
- package/build/dist/Types/Topology/TopologyApi.js +55 -0
- package/build/dist/Types/Topology/TopologyApi.js.map +1 -0
- package/build/dist/Types/Topology/TopologyTypeRules.js +165 -0
- package/build/dist/Types/Topology/TopologyTypeRules.js.map +1 -0
- package/build/dist/UI/Components/Input/Input.js +1 -1
- package/build/dist/UI/Components/Input/Input.js.map +1 -1
- package/build/dist/Utils/Dashboard/KubernetesClusterCpuTileRepair.js +110 -0
- package/build/dist/Utils/Dashboard/KubernetesClusterCpuTileRepair.js.map +1 -0
- package/build/dist/Utils/Incident/IncidentFromAlerts.js +38 -0
- package/build/dist/Utils/Incident/IncidentFromAlerts.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,643 @@
|
|
|
1
|
+
import TopologyRequest, {
|
|
2
|
+
MAP_RANGE_START_MAX_AGE_MS,
|
|
3
|
+
MAX_CURSOR_NAME_LENGTH,
|
|
4
|
+
MAX_ENTITY_KEY_LENGTH,
|
|
5
|
+
RANGE_START_MAX_FUTURE_SKEW_MS,
|
|
6
|
+
TopologyCollectionRequest,
|
|
7
|
+
TopologyCollectionSearchRequest,
|
|
8
|
+
TopologyEntityAllTimeConnectionsRequest,
|
|
9
|
+
TopologyEntityAllTimeRequest,
|
|
10
|
+
TopologyEntityConnectionsRequest,
|
|
11
|
+
TopologyEntityRequest,
|
|
12
|
+
floorToMinute,
|
|
13
|
+
rowsForSection,
|
|
14
|
+
} from "../../../../Server/Utils/Topology/TopologyRequest";
|
|
15
|
+
import BadDataException from "../../../../Types/Exception/BadDataException";
|
|
16
|
+
import { JSONObject } from "../../../../Types/JSON";
|
|
17
|
+
import EntityType from "../../../../Types/Telemetry/EntityType";
|
|
18
|
+
import { TopologyApiLimits } from "../../../../Types/Topology/TopologyApi";
|
|
19
|
+
import { describe, expect, test } from "@jest/globals";
|
|
20
|
+
|
|
21
|
+
/*
|
|
22
|
+
* Request bodies are parsed strictly before a connection is taken: every
|
|
23
|
+
* value the SQL binds comes out typed, bounded and normalized, and anything
|
|
24
|
+
* else is a 400 with a message that names the field.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
const NOW: Date = new Date("2026-09-26T12:00:30.000Z");
|
|
28
|
+
const RANGE_START: string = "2026-09-26T11:00:45.678Z";
|
|
29
|
+
const FLOORED: Date = new Date("2026-09-26T11:00:00.000Z");
|
|
30
|
+
|
|
31
|
+
function expectBadData(parse: () => unknown, message: RegExp): void {
|
|
32
|
+
let caught: unknown = null;
|
|
33
|
+
try {
|
|
34
|
+
parse();
|
|
35
|
+
} catch (error) {
|
|
36
|
+
caught = error;
|
|
37
|
+
}
|
|
38
|
+
expect(caught).toBeInstanceOf(BadDataException);
|
|
39
|
+
expect((caught as Error).message).toMatch(message);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
describe("TopologyRequest", () => {
|
|
43
|
+
describe("rangeStart", () => {
|
|
44
|
+
test("is floored to the minute", () => {
|
|
45
|
+
expect(
|
|
46
|
+
TopologyRequest.parseMapRequest({ rangeStart: RANGE_START }, NOW)
|
|
47
|
+
.rangeStart,
|
|
48
|
+
).toEqual(FLOORED);
|
|
49
|
+
expect(floorToMinute(new Date("2026-09-26T11:00:59.999Z"))).toEqual(
|
|
50
|
+
FLOORED,
|
|
51
|
+
);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test.each([
|
|
55
|
+
["with an offset", "2026-09-26T13:00:45+02:00"],
|
|
56
|
+
["without seconds", "2026-09-26T11:00Z"],
|
|
57
|
+
["with nanoseconds", "2026-09-26T11:00:45.123456789Z"],
|
|
58
|
+
])("accepts ISO 8601 %s", (_label: string, value: string) => {
|
|
59
|
+
expect(
|
|
60
|
+
TopologyRequest.parseMapRequest({ rangeStart: value }, NOW).rangeStart,
|
|
61
|
+
).toEqual(FLOORED);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test("accepts a start up to five minutes ahead of this clock", () => {
|
|
65
|
+
const ahead: string = new Date(
|
|
66
|
+
NOW.getTime() + RANGE_START_MAX_FUTURE_SKEW_MS,
|
|
67
|
+
).toISOString();
|
|
68
|
+
expect(() => {
|
|
69
|
+
return TopologyRequest.parseMapRequest({ rangeStart: ahead }, NOW);
|
|
70
|
+
}).not.toThrow();
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test.each([
|
|
74
|
+
["missing", {}, /rangeStart must be an ISO 8601 date-time/],
|
|
75
|
+
["a number", { rangeStart: 1790000000000 }, /ISO 8601/],
|
|
76
|
+
["a date without time", { rangeStart: "2026-09-26" }, /ISO 8601/],
|
|
77
|
+
["without a zone", { rangeStart: "2026-09-26T11:00:00" }, /ISO 8601/],
|
|
78
|
+
["free text", { rangeStart: "yesterday" }, /ISO 8601/],
|
|
79
|
+
[
|
|
80
|
+
"too long",
|
|
81
|
+
{ rangeStart: `${RANGE_START}${"0".repeat(64)}` },
|
|
82
|
+
/ISO 8601/,
|
|
83
|
+
],
|
|
84
|
+
[
|
|
85
|
+
"an impossible date",
|
|
86
|
+
{ rangeStart: "2026-02-31T25:61:00Z" },
|
|
87
|
+
/not a valid date/,
|
|
88
|
+
],
|
|
89
|
+
[
|
|
90
|
+
"more than five minutes ahead",
|
|
91
|
+
{ rangeStart: "2026-09-26T12:06:00.000Z" },
|
|
92
|
+
/cannot be in the future/,
|
|
93
|
+
],
|
|
94
|
+
[
|
|
95
|
+
"before 1970",
|
|
96
|
+
{ rangeStart: "1969-12-31T23:59:00Z" },
|
|
97
|
+
/too far in the past/,
|
|
98
|
+
],
|
|
99
|
+
[
|
|
100
|
+
"year zero",
|
|
101
|
+
{ rangeStart: "0000-01-01T00:00:00Z" },
|
|
102
|
+
/too far in the past/,
|
|
103
|
+
],
|
|
104
|
+
])(
|
|
105
|
+
"rejects rangeStart %s",
|
|
106
|
+
(_label: string, body: JSONObject, message: RegExp) => {
|
|
107
|
+
expectBadData(() => {
|
|
108
|
+
return TopologyRequest.parseMapRequest(body, NOW);
|
|
109
|
+
}, message);
|
|
110
|
+
},
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
test.each([
|
|
114
|
+
["null", null],
|
|
115
|
+
["an array", [RANGE_START]],
|
|
116
|
+
["a string", RANGE_START],
|
|
117
|
+
])("rejects a body that is %s", (_label: string, body: unknown) => {
|
|
118
|
+
expectBadData(() => {
|
|
119
|
+
return TopologyRequest.parseMapRequest(body, NOW);
|
|
120
|
+
}, /body must be a JSON object/);
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
describe("the maps' range start is clamped to at most 400 days ago", () => {
|
|
125
|
+
test("the bound is the contract's", () => {
|
|
126
|
+
expect(TopologyApiLimits.MaxMapRangeStartAgeDays).toBe(400);
|
|
127
|
+
expect(MAP_RANGE_START_MAX_AGE_MS).toBe(400 * 24 * 60 * 60 * 1000);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test("the oldest whole minute inside 400 days is kept as sent", () => {
|
|
131
|
+
const oldest: Date = floorToMinute(
|
|
132
|
+
new Date(NOW.getTime() - MAP_RANGE_START_MAX_AGE_MS + 60_000),
|
|
133
|
+
);
|
|
134
|
+
expect(
|
|
135
|
+
TopologyRequest.parseMapRequest(
|
|
136
|
+
{ rangeStart: oldest.toISOString() },
|
|
137
|
+
NOW,
|
|
138
|
+
).rangeStart,
|
|
139
|
+
).toEqual(oldest);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
/*
|
|
143
|
+
* Relationships are pruned after 30 days, so an older start draws the
|
|
144
|
+
* same map; clamping (rather than refusing) keeps an old custom range
|
|
145
|
+
* working while every such request shares one cache entry.
|
|
146
|
+
*/
|
|
147
|
+
test.each([
|
|
148
|
+
["a minute past the bound", MAP_RANGE_START_MAX_AGE_MS + 60_000],
|
|
149
|
+
["two years", 2 * 365 * 24 * 60 * 60 * 1000],
|
|
150
|
+
["the epoch", NOW.getTime()],
|
|
151
|
+
])(
|
|
152
|
+
"%s before now is clamped to the oldest honoured minute",
|
|
153
|
+
(_label: string, ageMs: number) => {
|
|
154
|
+
const clamped: Date = TopologyRequest.parseMapRequest(
|
|
155
|
+
{ rangeStart: new Date(NOW.getTime() - ageMs).toISOString() },
|
|
156
|
+
NOW,
|
|
157
|
+
).rangeStart;
|
|
158
|
+
expect(clamped.getTime() % 60_000).toBe(0);
|
|
159
|
+
expect(clamped.getTime()).toBeGreaterThanOrEqual(
|
|
160
|
+
NOW.getTime() - MAP_RANGE_START_MAX_AGE_MS,
|
|
161
|
+
);
|
|
162
|
+
expect(clamped.getTime()).toBeLessThan(
|
|
163
|
+
NOW.getTime() - MAP_RANGE_START_MAX_AGE_MS + 60_000,
|
|
164
|
+
);
|
|
165
|
+
},
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
test("every over-old start shares one clamped value (one cache entry)", () => {
|
|
169
|
+
const starts: Array<number> = [401, 500, 5000].map(
|
|
170
|
+
(days: number): number => {
|
|
171
|
+
return TopologyRequest.parseMapRequest(
|
|
172
|
+
{
|
|
173
|
+
rangeStart: new Date(
|
|
174
|
+
NOW.getTime() - days * 24 * 60 * 60 * 1000,
|
|
175
|
+
).toISOString(),
|
|
176
|
+
},
|
|
177
|
+
NOW,
|
|
178
|
+
).rangeStart.getTime();
|
|
179
|
+
},
|
|
180
|
+
);
|
|
181
|
+
expect(new Set<number>(starts).size).toBe(1);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
test("the drawer and collections are not bound by it (their start comes from a map)", () => {
|
|
185
|
+
const old: string = new Date(
|
|
186
|
+
NOW.getTime() - MAP_RANGE_START_MAX_AGE_MS - 86_400_000,
|
|
187
|
+
).toISOString();
|
|
188
|
+
expect(() => {
|
|
189
|
+
return TopologyRequest.parseEntityRequest(
|
|
190
|
+
{ rangeStart: old, entityKey: "k" },
|
|
191
|
+
NOW,
|
|
192
|
+
);
|
|
193
|
+
}).not.toThrow();
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
describe("fresh (an explicit refresh of a map)", () => {
|
|
198
|
+
test("absent means a cached map may answer", () => {
|
|
199
|
+
expect(
|
|
200
|
+
TopologyRequest.parseMapRequest({ rangeStart: RANGE_START }, NOW),
|
|
201
|
+
).toEqual({ rangeStart: FLOORED, fresh: false });
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
test.each([true, false])(
|
|
205
|
+
"a boolean %p is taken as given",
|
|
206
|
+
(fresh: boolean) => {
|
|
207
|
+
expect(
|
|
208
|
+
TopologyRequest.parseMapRequest(
|
|
209
|
+
{ rangeStart: RANGE_START, fresh },
|
|
210
|
+
NOW,
|
|
211
|
+
),
|
|
212
|
+
).toEqual({ rangeStart: FLOORED, fresh });
|
|
213
|
+
},
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
test.each([
|
|
217
|
+
["a string", "true"],
|
|
218
|
+
["a number", 1],
|
|
219
|
+
["null", null],
|
|
220
|
+
["an object", {}],
|
|
221
|
+
])("rejects %s", (_label: string, fresh: unknown) => {
|
|
222
|
+
expectBadData(() => {
|
|
223
|
+
return TopologyRequest.parseMapRequest(
|
|
224
|
+
{ rangeStart: RANGE_START, fresh },
|
|
225
|
+
NOW,
|
|
226
|
+
);
|
|
227
|
+
}, /fresh must be a boolean/);
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
describe("collection page", () => {
|
|
232
|
+
const valid: JSONObject = {
|
|
233
|
+
rangeStart: RANGE_START,
|
|
234
|
+
entityType: EntityType.NetworkDevice,
|
|
235
|
+
includeInactive: false,
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
test("defaults: no terms, no cursor, the default page size", () => {
|
|
239
|
+
const request: TopologyCollectionRequest =
|
|
240
|
+
TopologyRequest.parseCollectionRequest(valid, NOW);
|
|
241
|
+
expect(request).toEqual({
|
|
242
|
+
rangeStart: FLOORED,
|
|
243
|
+
entityType: EntityType.NetworkDevice,
|
|
244
|
+
includeInactive: false,
|
|
245
|
+
nameTerms: [],
|
|
246
|
+
cursor: null,
|
|
247
|
+
limit: TopologyApiLimits.CollectionPageSizeDefault,
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
test("terms are trimmed and lowercased; the cursor may carry an empty name", () => {
|
|
252
|
+
const request: TopologyCollectionRequest =
|
|
253
|
+
TopologyRequest.parseCollectionRequest(
|
|
254
|
+
{
|
|
255
|
+
...valid,
|
|
256
|
+
nameTerms: [" Core ", "SWITCH"],
|
|
257
|
+
cursor: { name: "", key: "k-1" },
|
|
258
|
+
limit: TopologyApiLimits.CollectionPageSizeMax,
|
|
259
|
+
},
|
|
260
|
+
NOW,
|
|
261
|
+
);
|
|
262
|
+
expect(request.nameTerms).toEqual(["core", "switch"]);
|
|
263
|
+
expect(request.cursor).toEqual({ name: "", key: "k-1" });
|
|
264
|
+
expect(request.limit).toBe(TopologyApiLimits.CollectionPageSizeMax);
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
test.each([
|
|
268
|
+
[
|
|
269
|
+
"a structural type",
|
|
270
|
+
{ entityType: EntityType.KubernetesPod },
|
|
271
|
+
/not a flat infrastructure type/,
|
|
272
|
+
],
|
|
273
|
+
[
|
|
274
|
+
"a service",
|
|
275
|
+
{ entityType: EntityType.Service },
|
|
276
|
+
/not a flat infrastructure type/,
|
|
277
|
+
],
|
|
278
|
+
[
|
|
279
|
+
"an application type",
|
|
280
|
+
{ entityType: EntityType.Database },
|
|
281
|
+
/not a flat infrastructure type/,
|
|
282
|
+
],
|
|
283
|
+
["an empty type", { entityType: "" }, /entityType cannot be empty/],
|
|
284
|
+
["a non-string type", { entityType: 7 }, /entityType must be a string/],
|
|
285
|
+
[
|
|
286
|
+
"includeInactive missing",
|
|
287
|
+
{ includeInactive: undefined },
|
|
288
|
+
/includeInactive must be a boolean/,
|
|
289
|
+
],
|
|
290
|
+
[
|
|
291
|
+
"includeInactive as a string",
|
|
292
|
+
{ includeInactive: "true" },
|
|
293
|
+
/includeInactive must be a boolean/,
|
|
294
|
+
],
|
|
295
|
+
[
|
|
296
|
+
"terms not an array",
|
|
297
|
+
{ nameTerms: "core" },
|
|
298
|
+
/nameTerms must be an array of strings/,
|
|
299
|
+
],
|
|
300
|
+
[
|
|
301
|
+
"a non-string term",
|
|
302
|
+
{ nameTerms: ["core", 5] },
|
|
303
|
+
/nameTerms must be an array of strings/,
|
|
304
|
+
],
|
|
305
|
+
["a blank term", { nameTerms: [" "] }, /cannot contain an empty term/],
|
|
306
|
+
[
|
|
307
|
+
"too many terms",
|
|
308
|
+
{
|
|
309
|
+
nameTerms: new Array(TopologyApiLimits.MaxSearchTerms + 1).fill("a"),
|
|
310
|
+
},
|
|
311
|
+
/cannot have more than/,
|
|
312
|
+
],
|
|
313
|
+
[
|
|
314
|
+
"a term too long",
|
|
315
|
+
{ nameTerms: ["a".repeat(TopologyApiLimits.MaxSearchTermLength + 1)] },
|
|
316
|
+
/terms cannot be longer than/,
|
|
317
|
+
],
|
|
318
|
+
[
|
|
319
|
+
"a cursor that is not an object",
|
|
320
|
+
{ cursor: "k-1" },
|
|
321
|
+
/cursor must be a JSON object/,
|
|
322
|
+
],
|
|
323
|
+
[
|
|
324
|
+
"a cursor without a key",
|
|
325
|
+
{ cursor: { name: "a" } },
|
|
326
|
+
/cursor.key must be a string/,
|
|
327
|
+
],
|
|
328
|
+
[
|
|
329
|
+
"a cursor with an empty key",
|
|
330
|
+
{ cursor: { name: "a", key: "" } },
|
|
331
|
+
/cursor.key cannot be empty/,
|
|
332
|
+
],
|
|
333
|
+
[
|
|
334
|
+
"a cursor name too long",
|
|
335
|
+
{ cursor: { name: "a".repeat(MAX_CURSOR_NAME_LENGTH + 1), key: "k" } },
|
|
336
|
+
/cursor.name cannot be longer/,
|
|
337
|
+
],
|
|
338
|
+
["a zero limit", { limit: 0 }, /limit must be an integer from 1/],
|
|
339
|
+
["a fractional limit", { limit: 1.5 }, /limit must be an integer/],
|
|
340
|
+
[
|
|
341
|
+
"a limit above the maximum",
|
|
342
|
+
{ limit: TopologyApiLimits.CollectionPageSizeMax + 1 },
|
|
343
|
+
/limit must be an integer/,
|
|
344
|
+
],
|
|
345
|
+
])("rejects %s", (_label: string, change: JSONObject, message: RegExp) => {
|
|
346
|
+
expectBadData(() => {
|
|
347
|
+
return TopologyRequest.parseCollectionRequest(
|
|
348
|
+
{ ...valid, ...change },
|
|
349
|
+
NOW,
|
|
350
|
+
);
|
|
351
|
+
}, message);
|
|
352
|
+
});
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
describe("collection search", () => {
|
|
356
|
+
test("parses each type's terms", () => {
|
|
357
|
+
const request: TopologyCollectionSearchRequest =
|
|
358
|
+
TopologyRequest.parseCollectionSearchRequest(
|
|
359
|
+
{
|
|
360
|
+
rangeStart: RANGE_START,
|
|
361
|
+
includeInactive: true,
|
|
362
|
+
types: [
|
|
363
|
+
{ entityType: EntityType.NetworkDevice, nameTerms: ["Core"] },
|
|
364
|
+
{ entityType: EntityType.IoTDevice, nameTerms: [] },
|
|
365
|
+
],
|
|
366
|
+
},
|
|
367
|
+
NOW,
|
|
368
|
+
);
|
|
369
|
+
expect(request).toEqual({
|
|
370
|
+
rangeStart: FLOORED,
|
|
371
|
+
includeInactive: true,
|
|
372
|
+
types: [
|
|
373
|
+
{ entityType: EntityType.NetworkDevice, nameTerms: ["core"] },
|
|
374
|
+
{ entityType: EntityType.IoTDevice, nameTerms: [] },
|
|
375
|
+
],
|
|
376
|
+
});
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
test.each([
|
|
380
|
+
["types missing", { types: undefined }, /types must be an array/],
|
|
381
|
+
[
|
|
382
|
+
"too many types",
|
|
383
|
+
{
|
|
384
|
+
types: new Array(TopologyApiLimits.MaxCollectionSearchTypes + 1).fill(
|
|
385
|
+
{
|
|
386
|
+
entityType: EntityType.NetworkDevice,
|
|
387
|
+
nameTerms: [],
|
|
388
|
+
},
|
|
389
|
+
),
|
|
390
|
+
},
|
|
391
|
+
/types cannot have more than/,
|
|
392
|
+
],
|
|
393
|
+
[
|
|
394
|
+
"a type listed twice",
|
|
395
|
+
{
|
|
396
|
+
types: [
|
|
397
|
+
{ entityType: EntityType.NetworkDevice, nameTerms: [] },
|
|
398
|
+
{ entityType: EntityType.NetworkDevice, nameTerms: ["a"] },
|
|
399
|
+
],
|
|
400
|
+
},
|
|
401
|
+
/more than once/,
|
|
402
|
+
],
|
|
403
|
+
[
|
|
404
|
+
"an entry that is not an object",
|
|
405
|
+
{ types: ["network.device"] },
|
|
406
|
+
/types\[0\] must be a JSON object/,
|
|
407
|
+
],
|
|
408
|
+
[
|
|
409
|
+
"a structural type",
|
|
410
|
+
{ types: [{ entityType: EntityType.Host, nameTerms: [] }] },
|
|
411
|
+
/types\[0\].entityType "host" is not a flat infrastructure type/,
|
|
412
|
+
],
|
|
413
|
+
[
|
|
414
|
+
"terms missing",
|
|
415
|
+
{ types: [{ entityType: EntityType.NetworkDevice }] },
|
|
416
|
+
/types\[0\].nameTerms must be an array of strings/,
|
|
417
|
+
],
|
|
418
|
+
])("rejects %s", (_label: string, change: JSONObject, message: RegExp) => {
|
|
419
|
+
expectBadData(() => {
|
|
420
|
+
return TopologyRequest.parseCollectionSearchRequest(
|
|
421
|
+
{ rangeStart: RANGE_START, includeInactive: true, ...change },
|
|
422
|
+
NOW,
|
|
423
|
+
);
|
|
424
|
+
}, message);
|
|
425
|
+
});
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
describe("entity and connections", () => {
|
|
429
|
+
test("the type is optional", () => {
|
|
430
|
+
for (const entityType of [undefined, null, ""]) {
|
|
431
|
+
const request: TopologyEntityRequest =
|
|
432
|
+
TopologyRequest.parseEntityRequest(
|
|
433
|
+
{ rangeStart: RANGE_START, entityKey: "svc-a", entityType },
|
|
434
|
+
NOW,
|
|
435
|
+
);
|
|
436
|
+
expect(request).toEqual({
|
|
437
|
+
rangeStart: FLOORED,
|
|
438
|
+
entityKey: "svc-a",
|
|
439
|
+
entityType: null,
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
expect(
|
|
443
|
+
TopologyRequest.parseEntityRequest(
|
|
444
|
+
{
|
|
445
|
+
rangeStart: RANGE_START,
|
|
446
|
+
entityKey: "svc-a",
|
|
447
|
+
entityType: "service",
|
|
448
|
+
},
|
|
449
|
+
NOW,
|
|
450
|
+
).entityType,
|
|
451
|
+
).toBe("service");
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
test.each([
|
|
455
|
+
["no key", {}, /entityKey must be a string/],
|
|
456
|
+
["an empty key", { entityKey: "" }, /entityKey cannot be empty/],
|
|
457
|
+
[
|
|
458
|
+
"a key too long",
|
|
459
|
+
{ entityKey: "k".repeat(MAX_ENTITY_KEY_LENGTH + 1) },
|
|
460
|
+
/entityKey cannot be longer/,
|
|
461
|
+
],
|
|
462
|
+
[
|
|
463
|
+
"a non-string type",
|
|
464
|
+
{ entityKey: "k", entityType: 3 },
|
|
465
|
+
/entityType must be a string/,
|
|
466
|
+
],
|
|
467
|
+
])("rejects %s", (_label: string, change: JSONObject, message: RegExp) => {
|
|
468
|
+
expectBadData(() => {
|
|
469
|
+
return TopologyRequest.parseEntityRequest(
|
|
470
|
+
{ rangeStart: RANGE_START, ...change },
|
|
471
|
+
NOW,
|
|
472
|
+
);
|
|
473
|
+
}, message);
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
test("a page defaults to the section's own row count", () => {
|
|
477
|
+
const calls: TopologyEntityConnectionsRequest =
|
|
478
|
+
TopologyRequest.parseEntityConnectionsRequest(
|
|
479
|
+
{
|
|
480
|
+
rangeStart: RANGE_START,
|
|
481
|
+
entityKey: "svc-a",
|
|
482
|
+
section: "calls",
|
|
483
|
+
offset: 100,
|
|
484
|
+
},
|
|
485
|
+
NOW,
|
|
486
|
+
);
|
|
487
|
+
expect(calls).toEqual({
|
|
488
|
+
rangeStart: FLOORED,
|
|
489
|
+
entityKey: "svc-a",
|
|
490
|
+
entityType: null,
|
|
491
|
+
section: "calls",
|
|
492
|
+
offset: 100,
|
|
493
|
+
limit: TopologyApiLimits.EntityDependencyRows,
|
|
494
|
+
});
|
|
495
|
+
expect(rowsForSection("calledBy")).toBe(
|
|
496
|
+
TopologyApiLimits.EntityDependencyRows,
|
|
497
|
+
);
|
|
498
|
+
expect(rowsForSection("runsOn")).toBe(TopologyApiLimits.EntityOtherRows);
|
|
499
|
+
expect(rowsForSection("related")).toBe(TopologyApiLimits.EntityOtherRows);
|
|
500
|
+
});
|
|
501
|
+
|
|
502
|
+
test.each([
|
|
503
|
+
[
|
|
504
|
+
"an unknown section",
|
|
505
|
+
{ section: "neighbours" },
|
|
506
|
+
/section must be one of calls, calledBy, runsOn, related/,
|
|
507
|
+
],
|
|
508
|
+
["no section", { section: undefined }, /section must be one of/],
|
|
509
|
+
["no offset", { offset: undefined }, /offset is required/],
|
|
510
|
+
["a negative offset", { offset: -1 }, /offset must be an integer from 0/],
|
|
511
|
+
[
|
|
512
|
+
"an offset past the scan limit",
|
|
513
|
+
{ offset: TopologyApiLimits.EntityConnectionScanLimit + 1 },
|
|
514
|
+
/offset must be an integer/,
|
|
515
|
+
],
|
|
516
|
+
[
|
|
517
|
+
"a limit above the page maximum",
|
|
518
|
+
{ limit: TopologyApiLimits.EntityConnectionsPageSizeMax + 1 },
|
|
519
|
+
/limit must be an integer from 1/,
|
|
520
|
+
],
|
|
521
|
+
])("rejects %s", (_label: string, change: JSONObject, message: RegExp) => {
|
|
522
|
+
expectBadData(() => {
|
|
523
|
+
return TopologyRequest.parseEntityConnectionsRequest(
|
|
524
|
+
{
|
|
525
|
+
rangeStart: RANGE_START,
|
|
526
|
+
entityKey: "svc-a",
|
|
527
|
+
section: "related",
|
|
528
|
+
offset: 0,
|
|
529
|
+
...change,
|
|
530
|
+
},
|
|
531
|
+
NOW,
|
|
532
|
+
);
|
|
533
|
+
}, message);
|
|
534
|
+
});
|
|
535
|
+
});
|
|
536
|
+
|
|
537
|
+
/*
|
|
538
|
+
* The inventory item page reads all time: the same entity and page fields
|
|
539
|
+
* as the drawer, with no range start to require, floor or clamp.
|
|
540
|
+
*/
|
|
541
|
+
describe("entity and connections, all time", () => {
|
|
542
|
+
test("needs no range start, and ignores one sent anyway", () => {
|
|
543
|
+
const withoutRange: TopologyEntityAllTimeRequest =
|
|
544
|
+
TopologyRequest.parseEntityAllTimeRequest({ entityKey: "ns-a" });
|
|
545
|
+
expect(withoutRange).toEqual({ entityKey: "ns-a", entityType: null });
|
|
546
|
+
|
|
547
|
+
for (const rangeStart of [RANGE_START, "not a date", 42]) {
|
|
548
|
+
expect(
|
|
549
|
+
TopologyRequest.parseEntityAllTimeRequest({
|
|
550
|
+
rangeStart,
|
|
551
|
+
entityKey: "ns-a",
|
|
552
|
+
entityType: EntityType.KubernetesNamespace,
|
|
553
|
+
}),
|
|
554
|
+
).toEqual({
|
|
555
|
+
entityKey: "ns-a",
|
|
556
|
+
entityType: EntityType.KubernetesNamespace,
|
|
557
|
+
});
|
|
558
|
+
}
|
|
559
|
+
});
|
|
560
|
+
|
|
561
|
+
test("a page defaults to the section's own row count", () => {
|
|
562
|
+
const page: TopologyEntityAllTimeConnectionsRequest =
|
|
563
|
+
TopologyRequest.parseEntityAllTimeConnectionsRequest({
|
|
564
|
+
entityKey: "ns-a",
|
|
565
|
+
section: "related",
|
|
566
|
+
offset: 25,
|
|
567
|
+
});
|
|
568
|
+
expect(page).toEqual({
|
|
569
|
+
entityKey: "ns-a",
|
|
570
|
+
entityType: null,
|
|
571
|
+
section: "related",
|
|
572
|
+
offset: 25,
|
|
573
|
+
limit: TopologyApiLimits.EntityOtherRows,
|
|
574
|
+
});
|
|
575
|
+
expect(
|
|
576
|
+
TopologyRequest.parseEntityAllTimeConnectionsRequest({
|
|
577
|
+
entityKey: "svc-a",
|
|
578
|
+
section: "calls",
|
|
579
|
+
offset: 0,
|
|
580
|
+
limit: TopologyApiLimits.EntityConnectionsPageSizeMax,
|
|
581
|
+
}).limit,
|
|
582
|
+
).toBe(TopologyApiLimits.EntityConnectionsPageSizeMax);
|
|
583
|
+
});
|
|
584
|
+
|
|
585
|
+
test.each([
|
|
586
|
+
["not an object", [], /body must be a JSON object/],
|
|
587
|
+
["no key", {}, /entityKey must be a string/],
|
|
588
|
+
["an empty key", { entityKey: "" }, /entityKey cannot be empty/],
|
|
589
|
+
[
|
|
590
|
+
"a key too long",
|
|
591
|
+
{ entityKey: "k".repeat(MAX_ENTITY_KEY_LENGTH + 1) },
|
|
592
|
+
/entityKey cannot be longer/,
|
|
593
|
+
],
|
|
594
|
+
[
|
|
595
|
+
"a non-string type",
|
|
596
|
+
{ entityKey: "k", entityType: 3 },
|
|
597
|
+
/entityType must be a string/,
|
|
598
|
+
],
|
|
599
|
+
])(
|
|
600
|
+
"the entity rejects %s",
|
|
601
|
+
(_label: string, body: unknown, message: RegExp) => {
|
|
602
|
+
expectBadData(() => {
|
|
603
|
+
return TopologyRequest.parseEntityAllTimeRequest(body);
|
|
604
|
+
}, message);
|
|
605
|
+
},
|
|
606
|
+
);
|
|
607
|
+
|
|
608
|
+
test.each([
|
|
609
|
+
["no key", { entityKey: undefined }, /entityKey must be a string/],
|
|
610
|
+
[
|
|
611
|
+
"an unknown section",
|
|
612
|
+
{ section: "neighbours" },
|
|
613
|
+
/section must be one of calls, calledBy, runsOn, related/,
|
|
614
|
+
],
|
|
615
|
+
["no offset", { offset: undefined }, /offset is required/],
|
|
616
|
+
["a negative offset", { offset: -1 }, /offset must be an integer from 0/],
|
|
617
|
+
[
|
|
618
|
+
"an offset past the scan limit",
|
|
619
|
+
{ offset: TopologyApiLimits.EntityConnectionScanLimit + 1 },
|
|
620
|
+
/offset must be an integer/,
|
|
621
|
+
],
|
|
622
|
+
["a fractional offset", { offset: 2.5 }, /offset must be an integer/],
|
|
623
|
+
[
|
|
624
|
+
"a limit above the page maximum",
|
|
625
|
+
{ limit: TopologyApiLimits.EntityConnectionsPageSizeMax + 1 },
|
|
626
|
+
/limit must be an integer from 1/,
|
|
627
|
+
],
|
|
628
|
+
["a zero limit", { limit: 0 }, /limit must be an integer from 1/],
|
|
629
|
+
])(
|
|
630
|
+
"a page rejects %s",
|
|
631
|
+
(_label: string, change: JSONObject, message: RegExp) => {
|
|
632
|
+
expectBadData(() => {
|
|
633
|
+
return TopologyRequest.parseEntityAllTimeConnectionsRequest({
|
|
634
|
+
entityKey: "svc-a",
|
|
635
|
+
section: "related",
|
|
636
|
+
offset: 0,
|
|
637
|
+
...change,
|
|
638
|
+
});
|
|
639
|
+
}, message);
|
|
640
|
+
},
|
|
641
|
+
);
|
|
642
|
+
});
|
|
643
|
+
});
|