@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,683 @@
|
|
|
1
|
+
import Entities from "../../../../Models/DatabaseModels/Index";
|
|
2
|
+
import PostgresAppInstance from "../../../../Server/Infrastructure/PostgresDatabase";
|
|
3
|
+
import TopologyQueries from "../../../../Server/Utils/Topology/TopologyQueries";
|
|
4
|
+
import ObjectID from "../../../../Types/ObjectID";
|
|
5
|
+
import EntityRelationshipType from "../../../../Types/Telemetry/EntityRelationshipType";
|
|
6
|
+
import EntitySource from "../../../../Types/Telemetry/EntitySource";
|
|
7
|
+
import EntityType from "../../../../Types/Telemetry/EntityType";
|
|
8
|
+
import {
|
|
9
|
+
TopologyApiLimits,
|
|
10
|
+
TopologyEntityResponseJSON,
|
|
11
|
+
TopologyInfrastructureResponseJSON,
|
|
12
|
+
TopologyServiceMapResponseJSON,
|
|
13
|
+
} from "../../../../Types/Topology/TopologyApi";
|
|
14
|
+
import {
|
|
15
|
+
FLAT_TYPES,
|
|
16
|
+
SeededRandom,
|
|
17
|
+
generateEstate,
|
|
18
|
+
} from "./TopologyEstateGenerator";
|
|
19
|
+
import {
|
|
20
|
+
REFERENCE_UNDISCOVERED_RESOURCE_LABEL,
|
|
21
|
+
REFERENCE_UNNAMED_RESOURCE_LABEL,
|
|
22
|
+
ReferenceEntityResponse,
|
|
23
|
+
ReferenceEstate,
|
|
24
|
+
ReferenceInfrastructureResponse,
|
|
25
|
+
ReferenceItem,
|
|
26
|
+
ReferenceRelationship,
|
|
27
|
+
ReferenceServiceMapResponse,
|
|
28
|
+
compareCollateC,
|
|
29
|
+
referenceEntity,
|
|
30
|
+
referenceInfrastructure,
|
|
31
|
+
referenceServiceMap,
|
|
32
|
+
} from "./TopologyReference";
|
|
33
|
+
import { DataSource } from "typeorm";
|
|
34
|
+
|
|
35
|
+
/*
|
|
36
|
+
* Differential test of the Topology API's SQL: random estates are written
|
|
37
|
+
* to Postgres and every answer of TopologyQueries.getServiceMap,
|
|
38
|
+
* getInfrastructure and getEntity must equal the one the reference
|
|
39
|
+
* implementation (TopologyReference, plain TypeScript written from the spec,
|
|
40
|
+
* sharing nothing with the SQL) computes from the same rows — ignoring only
|
|
41
|
+
* the envelope's generatedAt.
|
|
42
|
+
*
|
|
43
|
+
* The estates (TopologyEstateGenerator) are seeded, so a failure names a
|
|
44
|
+
* seed that reproduces it. They cover what the hand-written fixtures of
|
|
45
|
+
* TopologyQueriesPostgres cover one case at a time, all at once and in
|
|
46
|
+
* combination: collections above the inline budget, keys shared by live rows
|
|
47
|
+
* of different types, archived / soft-deleted / other-tenant rows, keys whose
|
|
48
|
+
* order differs between COLLATE "C" and the database collation, containment
|
|
49
|
+
* ties and cycles, dangling and self-referencing edges, stale / boundary /
|
|
50
|
+
* never-seen rows and microsecond timestamps.
|
|
51
|
+
*
|
|
52
|
+
* Opt in with RUN_POSTGRES_TOPOLOGY_TESTS=true, exactly like
|
|
53
|
+
* TopologyQueriesPostgres (whose isolated-schema setup this copies): both
|
|
54
|
+
* tables' structure is cloned into a unique schema that is the connection's
|
|
55
|
+
* whole search_path and dropped afterwards; no real row is read or written.
|
|
56
|
+
*/
|
|
57
|
+
const describePostgres: typeof describe =
|
|
58
|
+
process.env["RUN_POSTGRES_TOPOLOGY_TESTS"] === "true"
|
|
59
|
+
? describe
|
|
60
|
+
: describe.skip;
|
|
61
|
+
|
|
62
|
+
const TABLES: Array<string> = ["InventoryItem", "InventoryItemRelationship"];
|
|
63
|
+
|
|
64
|
+
const ENTITY_KEY_INDEX_DEFINITION: RegExp = /\("projectId", "entityKey"\)$/;
|
|
65
|
+
|
|
66
|
+
const PROJECT_ID: ObjectID = ObjectID.generate();
|
|
67
|
+
const OTHER_PROJECT_ID: ObjectID = ObjectID.generate();
|
|
68
|
+
|
|
69
|
+
/* Already floored, as the router hands it to TopologyQueries. */
|
|
70
|
+
const RANGE_START: Date = new Date("2026-09-20T10:00:00.000Z");
|
|
71
|
+
const DELETED_AT: Date = new Date("2026-09-20T10:05:00.000Z");
|
|
72
|
+
|
|
73
|
+
const ESTATES_PER_VARIANT: number = 30;
|
|
74
|
+
|
|
75
|
+
/* Rows per INSERT: 17 parameters per item stays far below Postgres's 65,535. */
|
|
76
|
+
const INSERT_CHUNK: number = 1000;
|
|
77
|
+
|
|
78
|
+
const INDEX_VARIANTS: Array<[string, boolean]> = [
|
|
79
|
+
["with", true],
|
|
80
|
+
["without", false],
|
|
81
|
+
];
|
|
82
|
+
|
|
83
|
+
type EnvelopeFree<T> = Omit<T, "generatedAt">;
|
|
84
|
+
|
|
85
|
+
function withoutGeneratedAt<T extends { generatedAt: string }>(
|
|
86
|
+
response: T,
|
|
87
|
+
): EnvelopeFree<T> {
|
|
88
|
+
const copy: T = { ...response };
|
|
89
|
+
delete (copy as { generatedAt?: string }).generatedAt;
|
|
90
|
+
return copy;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/*
|
|
94
|
+
* A lastSeenAt with sub-millisecond digits in the database whose floor is
|
|
95
|
+
* the reference's millisecond value: flooring to epoch milliseconds and the
|
|
96
|
+
* activity predicate must both hold at microsecond precision.
|
|
97
|
+
*/
|
|
98
|
+
function withMicroseconds(date: Date, microseconds: number): string {
|
|
99
|
+
return date
|
|
100
|
+
.toISOString()
|
|
101
|
+
.replace("Z", `${String(microseconds).padStart(3, "0")}Z`);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/*
|
|
105
|
+
* A drawer target with more rows than the first view shows: a service that
|
|
106
|
+
* calls, runs on and is called by many things (known and unknown), so the
|
|
107
|
+
* section budgets, "Show more" offsets and label order are all exercised.
|
|
108
|
+
*/
|
|
109
|
+
function addHub(estate: ReferenceEstate, random: SeededRandom): string | null {
|
|
110
|
+
const services: Array<ReferenceItem> = estate.items.filter(
|
|
111
|
+
(item: ReferenceItem): boolean => {
|
|
112
|
+
return (
|
|
113
|
+
item.type === EntityType.Service &&
|
|
114
|
+
item.projectId === PROJECT_ID.toString() &&
|
|
115
|
+
!item.isArchived &&
|
|
116
|
+
!item.deleted
|
|
117
|
+
);
|
|
118
|
+
},
|
|
119
|
+
);
|
|
120
|
+
if (services.length === 0) {
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
const hub: ReferenceItem = random.pick(services);
|
|
124
|
+
const known: Array<ReferenceItem> = estate.items.filter(
|
|
125
|
+
(item: ReferenceItem): boolean => {
|
|
126
|
+
return item.projectId === PROJECT_ID.toString();
|
|
127
|
+
},
|
|
128
|
+
);
|
|
129
|
+
const at: number = RANGE_START.getTime();
|
|
130
|
+
const taken: Set<string> = new Set<string>(
|
|
131
|
+
estate.relationships.map((relationship: ReferenceRelationship): string => {
|
|
132
|
+
return `${relationship.projectId}\u0000${relationship.from}\u0000${relationship.to}\u0000${relationship.type}`;
|
|
133
|
+
}),
|
|
134
|
+
);
|
|
135
|
+
const add: (from: string, to: string, type: string) => void = (
|
|
136
|
+
from: string,
|
|
137
|
+
to: string,
|
|
138
|
+
type: string,
|
|
139
|
+
): void => {
|
|
140
|
+
const identity: string = `${PROJECT_ID.toString()}\u0000${from}\u0000${to}\u0000${type}`;
|
|
141
|
+
if (taken.has(identity)) {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
taken.add(identity);
|
|
145
|
+
estate.relationships.push({
|
|
146
|
+
id: random.uuid(),
|
|
147
|
+
projectId: PROJECT_ID.toString(),
|
|
148
|
+
from,
|
|
149
|
+
to,
|
|
150
|
+
type,
|
|
151
|
+
/* Some share a lastSeenAt, so the scan order falls back to _id. */
|
|
152
|
+
lastSeenAt: new Date(at + random.int(0, 20) * 1000),
|
|
153
|
+
createdAt: new Date(at - random.int(0, 3) * 86_400_000),
|
|
154
|
+
deleted: false,
|
|
155
|
+
/* Ties on callCount (and nulls) push the order down to the label. */
|
|
156
|
+
callCount: random.chance(0.2) ? null : random.int(0, 4) * 10,
|
|
157
|
+
errorCount: random.chance(0.5) ? null : random.int(0, 5),
|
|
158
|
+
avgDurationMs: random.chance(0.5) ? null : random.int(0, 500),
|
|
159
|
+
});
|
|
160
|
+
};
|
|
161
|
+
const other: () => string = (): string => {
|
|
162
|
+
return random.chance(0.6) && known.length > 0
|
|
163
|
+
? random.pick(known).key
|
|
164
|
+
: `hub-ghost-${random.int(0, 999)}`;
|
|
165
|
+
};
|
|
166
|
+
const calls: number = TopologyApiLimits.EntityDependencyRows + 15;
|
|
167
|
+
for (let index: number = 0; index < calls; index++) {
|
|
168
|
+
add(hub.key, other(), EntityRelationshipType.DependsOn);
|
|
169
|
+
}
|
|
170
|
+
for (let index: number = 0; index < 40; index++) {
|
|
171
|
+
add(other(), hub.key, EntityRelationshipType.DependsOn);
|
|
172
|
+
add(
|
|
173
|
+
hub.key,
|
|
174
|
+
other(),
|
|
175
|
+
random.pick([
|
|
176
|
+
EntityRelationshipType.RunsOn,
|
|
177
|
+
EntityRelationshipType.HostedOn,
|
|
178
|
+
]),
|
|
179
|
+
);
|
|
180
|
+
add(
|
|
181
|
+
random.chance(0.5) ? hub.key : other(),
|
|
182
|
+
random.chance(0.5) ? other() : hub.key,
|
|
183
|
+
random.pick([
|
|
184
|
+
EntityRelationshipType.PartOf,
|
|
185
|
+
EntityRelationshipType.MemberOf,
|
|
186
|
+
EntityRelationshipType.InstanceOf,
|
|
187
|
+
]),
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
add(hub.key, hub.key, EntityRelationshipType.DependsOn);
|
|
191
|
+
add(hub.key, hub.key, EntityRelationshipType.RunsOn);
|
|
192
|
+
return hub.key;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
describePostgres.each(INDEX_VARIANTS)(
|
|
196
|
+
"Topology API SQL equals the reference on random estates (%s the entity-key index)",
|
|
197
|
+
(_label: string, withEntityKeyIndex: boolean) => {
|
|
198
|
+
const schema: string = `topology_diff_${ObjectID.generate()
|
|
199
|
+
.toString()
|
|
200
|
+
.replace(/-/g, "")}`;
|
|
201
|
+
let database: DataSource;
|
|
202
|
+
|
|
203
|
+
beforeAll(async () => {
|
|
204
|
+
database = new DataSource({
|
|
205
|
+
type: "postgres",
|
|
206
|
+
host: process.env["TOPOLOGY_TEST_DATABASE_HOST"] || "localhost",
|
|
207
|
+
port: Number(process.env["TOPOLOGY_TEST_DATABASE_PORT"] || "5400"),
|
|
208
|
+
username: process.env["DATABASE_USERNAME"] || "postgres",
|
|
209
|
+
password: process.env["DATABASE_PASSWORD"] || "password",
|
|
210
|
+
database:
|
|
211
|
+
process.env["TOPOLOGY_TEST_DATABASE_NAME"] ||
|
|
212
|
+
process.env["DATABASE_NAME"] ||
|
|
213
|
+
"oneuptimedb",
|
|
214
|
+
entities: Entities,
|
|
215
|
+
schema,
|
|
216
|
+
synchronize: false,
|
|
217
|
+
extra: { options: `-c search_path=${schema}` },
|
|
218
|
+
});
|
|
219
|
+
await database.initialize();
|
|
220
|
+
await database.query(`CREATE SCHEMA "${schema}"`);
|
|
221
|
+
for (const table of TABLES) {
|
|
222
|
+
await database.query(
|
|
223
|
+
`CREATE TABLE "${schema}"."${table}" (LIKE public."${table}" INCLUDING ALL)`,
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
const entityKeyIndexes: Array<{ indexname: string }> = (
|
|
227
|
+
await database.query(
|
|
228
|
+
`SELECT indexname, indexdef FROM pg_indexes WHERE schemaname = $1 AND tablename = 'InventoryItem'`,
|
|
229
|
+
[schema],
|
|
230
|
+
)
|
|
231
|
+
).filter((index: { indexdef: string }): boolean => {
|
|
232
|
+
return ENTITY_KEY_INDEX_DEFINITION.test(index.indexdef);
|
|
233
|
+
});
|
|
234
|
+
if (withEntityKeyIndex && entityKeyIndexes.length === 0) {
|
|
235
|
+
await database.query(
|
|
236
|
+
`CREATE INDEX "topology_diff_entity_key" ON "${schema}"."InventoryItem" ("projectId", "entityKey")`,
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
if (!withEntityKeyIndex) {
|
|
240
|
+
for (const index of entityKeyIndexes) {
|
|
241
|
+
await database.query(`DROP INDEX "${schema}"."${index.indexname}"`);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
expect(
|
|
245
|
+
(await database.query("SELECT current_schema()"))[0].current_schema,
|
|
246
|
+
).toBe(schema);
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
afterAll(async () => {
|
|
250
|
+
if (database?.isInitialized) {
|
|
251
|
+
await database.query(`DROP SCHEMA IF EXISTS "${schema}" CASCADE`);
|
|
252
|
+
await database.destroy();
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
beforeEach(async () => {
|
|
257
|
+
jest.spyOn(PostgresAppInstance, "isConnected").mockReturnValue(true);
|
|
258
|
+
jest
|
|
259
|
+
.spyOn(PostgresAppInstance, "getDataSource")
|
|
260
|
+
.mockReturnValue(database);
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
afterEach(() => {
|
|
264
|
+
jest.restoreAllMocks();
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
/* Inserts rows in multi-row statements of at most INSERT_CHUNK rows. */
|
|
268
|
+
async function insertRows(
|
|
269
|
+
table: string,
|
|
270
|
+
columns: Array<string>,
|
|
271
|
+
rows: Array<Array<unknown>>,
|
|
272
|
+
): Promise<void> {
|
|
273
|
+
for (let start: number = 0; start < rows.length; start += INSERT_CHUNK) {
|
|
274
|
+
const params: Array<unknown> = [];
|
|
275
|
+
const tuples: Array<string> = rows
|
|
276
|
+
.slice(start, start + INSERT_CHUNK)
|
|
277
|
+
.map((row: Array<unknown>): string => {
|
|
278
|
+
return `(${row
|
|
279
|
+
.map((value: unknown): string => {
|
|
280
|
+
params.push(value);
|
|
281
|
+
return `$${params.length}`;
|
|
282
|
+
})
|
|
283
|
+
.join(", ")})`;
|
|
284
|
+
});
|
|
285
|
+
await database.query(
|
|
286
|
+
`INSERT INTO "${schema}"."${table}" (${columns
|
|
287
|
+
.map((column: string): string => {
|
|
288
|
+
return `"${column}"`;
|
|
289
|
+
})
|
|
290
|
+
.join(", ")}) VALUES ${tuples.join(", ")}`,
|
|
291
|
+
params,
|
|
292
|
+
);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/* Replaces both tables' rows with the estate's. */
|
|
297
|
+
async function load(
|
|
298
|
+
estate: ReferenceEstate,
|
|
299
|
+
random: SeededRandom,
|
|
300
|
+
): Promise<void> {
|
|
301
|
+
for (const table of TABLES) {
|
|
302
|
+
await database.query(`DELETE FROM "${schema}"."${table}"`);
|
|
303
|
+
}
|
|
304
|
+
await insertRows(
|
|
305
|
+
"InventoryItem",
|
|
306
|
+
[
|
|
307
|
+
"_id",
|
|
308
|
+
"createdAt",
|
|
309
|
+
"updatedAt",
|
|
310
|
+
"deletedAt",
|
|
311
|
+
"version",
|
|
312
|
+
"projectId",
|
|
313
|
+
"entityType",
|
|
314
|
+
"entityKey",
|
|
315
|
+
"displayName",
|
|
316
|
+
"source",
|
|
317
|
+
"lastSeenAt",
|
|
318
|
+
"firstSeenAt",
|
|
319
|
+
"isArchived",
|
|
320
|
+
"descriptiveAttributes",
|
|
321
|
+
"identifyingAttributes",
|
|
322
|
+
"resourceType",
|
|
323
|
+
"resourceId",
|
|
324
|
+
],
|
|
325
|
+
estate.items.map((item: ReferenceItem): Array<unknown> => {
|
|
326
|
+
return [
|
|
327
|
+
item.id,
|
|
328
|
+
item.createdAt,
|
|
329
|
+
item.createdAt,
|
|
330
|
+
item.deleted ? DELETED_AT : null,
|
|
331
|
+
1,
|
|
332
|
+
item.projectId,
|
|
333
|
+
item.type,
|
|
334
|
+
item.key,
|
|
335
|
+
item.name,
|
|
336
|
+
item.source,
|
|
337
|
+
item.lastSeenAt === null
|
|
338
|
+
? null
|
|
339
|
+
: random.chance(0.3)
|
|
340
|
+
? withMicroseconds(item.lastSeenAt, random.int(1, 999))
|
|
341
|
+
: item.lastSeenAt,
|
|
342
|
+
item.firstSeenAt,
|
|
343
|
+
item.isArchived,
|
|
344
|
+
item.descriptiveAttributes === null
|
|
345
|
+
? null
|
|
346
|
+
: JSON.stringify(item.descriptiveAttributes),
|
|
347
|
+
item.identifyingAttributes === null
|
|
348
|
+
? null
|
|
349
|
+
: JSON.stringify(item.identifyingAttributes),
|
|
350
|
+
item.resourceType,
|
|
351
|
+
item.resourceId,
|
|
352
|
+
];
|
|
353
|
+
}),
|
|
354
|
+
);
|
|
355
|
+
await insertRows(
|
|
356
|
+
"InventoryItemRelationship",
|
|
357
|
+
[
|
|
358
|
+
"_id",
|
|
359
|
+
"createdAt",
|
|
360
|
+
"updatedAt",
|
|
361
|
+
"deletedAt",
|
|
362
|
+
"version",
|
|
363
|
+
"projectId",
|
|
364
|
+
"fromEntityKey",
|
|
365
|
+
"toEntityKey",
|
|
366
|
+
"relationshipType",
|
|
367
|
+
"lastSeenAt",
|
|
368
|
+
"callCount",
|
|
369
|
+
"errorCount",
|
|
370
|
+
"avgDurationMs",
|
|
371
|
+
],
|
|
372
|
+
estate.relationships.map(
|
|
373
|
+
(relationship: ReferenceRelationship): Array<unknown> => {
|
|
374
|
+
return [
|
|
375
|
+
relationship.id,
|
|
376
|
+
relationship.createdAt,
|
|
377
|
+
relationship.createdAt,
|
|
378
|
+
relationship.deleted ? DELETED_AT : null,
|
|
379
|
+
1,
|
|
380
|
+
relationship.projectId,
|
|
381
|
+
relationship.from,
|
|
382
|
+
relationship.to,
|
|
383
|
+
relationship.type,
|
|
384
|
+
relationship.lastSeenAt,
|
|
385
|
+
relationship.callCount,
|
|
386
|
+
relationship.errorCount,
|
|
387
|
+
relationship.avgDurationMs,
|
|
388
|
+
];
|
|
389
|
+
},
|
|
390
|
+
),
|
|
391
|
+
);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/*
|
|
395
|
+
* Drawer labels are the one thing the server orders by the database's
|
|
396
|
+
* default collation; the database itself says what that order is.
|
|
397
|
+
*/
|
|
398
|
+
async function labelOrder(
|
|
399
|
+
estate: ReferenceEstate,
|
|
400
|
+
): Promise<(left: string, right: string) => number> {
|
|
401
|
+
const labels: Set<string> = new Set<string>([
|
|
402
|
+
REFERENCE_UNNAMED_RESOURCE_LABEL,
|
|
403
|
+
REFERENCE_UNDISCOVERED_RESOURCE_LABEL,
|
|
404
|
+
]);
|
|
405
|
+
for (const item of estate.items) {
|
|
406
|
+
if (item.name) {
|
|
407
|
+
labels.add(item.name);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
const rows: Array<{ label: string }> = await database.query(
|
|
411
|
+
`SELECT t."label" AS "label" FROM unnest($1::text[]) AS t("label") ORDER BY t."label"`,
|
|
412
|
+
[Array.from(labels)],
|
|
413
|
+
);
|
|
414
|
+
const rank: Map<string, number> = new Map<string, number>();
|
|
415
|
+
rows.forEach((row: { label: string }, index: number): void => {
|
|
416
|
+
rank.set(row.label, index);
|
|
417
|
+
});
|
|
418
|
+
return (left: string, right: string): number => {
|
|
419
|
+
const a: number | undefined = rank.get(left);
|
|
420
|
+
const b: number | undefined = rank.get(right);
|
|
421
|
+
if (a === undefined || b === undefined) {
|
|
422
|
+
return compareCollateC(left, right);
|
|
423
|
+
}
|
|
424
|
+
return a - b;
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
async function expectServerMatchesReference(
|
|
429
|
+
label: string,
|
|
430
|
+
estate: ReferenceEstate,
|
|
431
|
+
drawerKeys: Array<{ key: string; type: string | null }>,
|
|
432
|
+
): Promise<void> {
|
|
433
|
+
const scope: { projectId: string; rangeStart: Date } = {
|
|
434
|
+
projectId: PROJECT_ID.toString(),
|
|
435
|
+
rangeStart: RANGE_START,
|
|
436
|
+
};
|
|
437
|
+
|
|
438
|
+
const serviceMap: TopologyServiceMapResponseJSON =
|
|
439
|
+
await TopologyQueries.getServiceMap({
|
|
440
|
+
projectId: PROJECT_ID,
|
|
441
|
+
rangeStart: RANGE_START,
|
|
442
|
+
});
|
|
443
|
+
const expectedServiceMap: ReferenceServiceMapResponse =
|
|
444
|
+
referenceServiceMap(estate, scope);
|
|
445
|
+
expect({
|
|
446
|
+
estate: label,
|
|
447
|
+
response: withoutGeneratedAt(serviceMap),
|
|
448
|
+
}).toEqual({ estate: label, response: expectedServiceMap });
|
|
449
|
+
|
|
450
|
+
const infrastructure: TopologyInfrastructureResponseJSON =
|
|
451
|
+
await TopologyQueries.getInfrastructure({
|
|
452
|
+
projectId: PROJECT_ID,
|
|
453
|
+
rangeStart: RANGE_START,
|
|
454
|
+
});
|
|
455
|
+
const expectedInfrastructure: ReferenceInfrastructureResponse =
|
|
456
|
+
referenceInfrastructure(estate, scope);
|
|
457
|
+
expect({
|
|
458
|
+
estate: label,
|
|
459
|
+
response: withoutGeneratedAt(infrastructure),
|
|
460
|
+
}).toEqual({ estate: label, response: expectedInfrastructure });
|
|
461
|
+
|
|
462
|
+
const compareLabels: (left: string, right: string) => number =
|
|
463
|
+
await labelOrder(estate);
|
|
464
|
+
for (const target of drawerKeys) {
|
|
465
|
+
const entity: TopologyEntityResponseJSON =
|
|
466
|
+
await TopologyQueries.getEntity({
|
|
467
|
+
projectId: PROJECT_ID,
|
|
468
|
+
rangeStart: RANGE_START,
|
|
469
|
+
entityKey: target.key,
|
|
470
|
+
entityType: target.type,
|
|
471
|
+
});
|
|
472
|
+
const expectedEntity: ReferenceEntityResponse = referenceEntity(
|
|
473
|
+
estate,
|
|
474
|
+
{ ...scope, entityKey: target.key, entityType: target.type },
|
|
475
|
+
{ compareLabels },
|
|
476
|
+
);
|
|
477
|
+
expect({
|
|
478
|
+
estate: label,
|
|
479
|
+
drawer: target,
|
|
480
|
+
response: withoutGeneratedAt(entity),
|
|
481
|
+
}).toEqual({
|
|
482
|
+
estate: label,
|
|
483
|
+
drawer: target,
|
|
484
|
+
response: expectedEntity,
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
/* A few drawer targets per estate: plain, narrowed, hub, missing. */
|
|
490
|
+
function drawerTargets(
|
|
491
|
+
estate: ReferenceEstate,
|
|
492
|
+
random: SeededRandom,
|
|
493
|
+
hub: string | null,
|
|
494
|
+
): Array<{ key: string; type: string | null }> {
|
|
495
|
+
const projectItems: Array<ReferenceItem> = estate.items.filter(
|
|
496
|
+
(item: ReferenceItem): boolean => {
|
|
497
|
+
return item.projectId === PROJECT_ID.toString();
|
|
498
|
+
},
|
|
499
|
+
);
|
|
500
|
+
const targets: Array<{ key: string; type: string | null }> = [];
|
|
501
|
+
for (
|
|
502
|
+
let index: number = 0;
|
|
503
|
+
index < 4 && projectItems.length > 0;
|
|
504
|
+
index++
|
|
505
|
+
) {
|
|
506
|
+
const item: ReferenceItem = random.pick(projectItems);
|
|
507
|
+
targets.push({
|
|
508
|
+
key: item.key,
|
|
509
|
+
type: random.chance(0.5) ? item.type : null,
|
|
510
|
+
});
|
|
511
|
+
}
|
|
512
|
+
if (hub) {
|
|
513
|
+
targets.push({ key: hub, type: null });
|
|
514
|
+
}
|
|
515
|
+
targets.push({ key: "ghost-never-an-item", type: null });
|
|
516
|
+
targets.push({
|
|
517
|
+
key: `ghost-${random.int(0, 99)}`,
|
|
518
|
+
type: EntityType.Host,
|
|
519
|
+
});
|
|
520
|
+
return targets;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
test("random estates: service map, infrastructure and drawer", async () => {
|
|
524
|
+
let collections: number = 0;
|
|
525
|
+
let duplicates: number = 0;
|
|
526
|
+
let collectedDuplicates: number = 0;
|
|
527
|
+
for (let seed: number = 1; seed <= ESTATES_PER_VARIANT; seed++) {
|
|
528
|
+
const random: SeededRandom = new SeededRandom(7_000 + seed);
|
|
529
|
+
const withCollection: boolean = seed % 3 === 1;
|
|
530
|
+
const estate: ReferenceEstate = generateEstate(random, {
|
|
531
|
+
projectId: PROJECT_ID.toString(),
|
|
532
|
+
otherProjectId: OTHER_PROJECT_ID.toString(),
|
|
533
|
+
rangeStart: RANGE_START,
|
|
534
|
+
size: 70,
|
|
535
|
+
liveDuplicateKeys: seed % 2 === 1,
|
|
536
|
+
collectionType: withCollection
|
|
537
|
+
? FLAT_TYPES[seed % FLAT_TYPES.length]
|
|
538
|
+
: undefined,
|
|
539
|
+
collectionCount: withCollection
|
|
540
|
+
? TopologyApiLimits.InlineFlatItemsPerType + random.int(-2, 60)
|
|
541
|
+
: undefined,
|
|
542
|
+
placementsOntoCollection: true,
|
|
543
|
+
});
|
|
544
|
+
const hub: string | null =
|
|
545
|
+
seed % 3 === 0 ? addHub(estate, random) : null;
|
|
546
|
+
const drawer: Array<{ key: string; type: string | null }> =
|
|
547
|
+
drawerTargets(estate, random, hub);
|
|
548
|
+
|
|
549
|
+
await load(estate, random);
|
|
550
|
+
await expectServerMatchesReference(
|
|
551
|
+
`seed ${7_000 + seed}`,
|
|
552
|
+
estate,
|
|
553
|
+
drawer,
|
|
554
|
+
);
|
|
555
|
+
|
|
556
|
+
const collected: Set<string> = new Set<string>(
|
|
557
|
+
referenceInfrastructure(estate, {
|
|
558
|
+
projectId: PROJECT_ID.toString(),
|
|
559
|
+
rangeStart: RANGE_START,
|
|
560
|
+
}).collections.map((collection: { type: string }): string => {
|
|
561
|
+
return collection.type;
|
|
562
|
+
}),
|
|
563
|
+
);
|
|
564
|
+
collections += collected.size;
|
|
565
|
+
const liveByKey: Map<string, Array<ReferenceItem>> = new Map<
|
|
566
|
+
string,
|
|
567
|
+
Array<ReferenceItem>
|
|
568
|
+
>();
|
|
569
|
+
for (const item of estate.items) {
|
|
570
|
+
if (
|
|
571
|
+
item.projectId === PROJECT_ID.toString() &&
|
|
572
|
+
!item.isArchived &&
|
|
573
|
+
!item.deleted
|
|
574
|
+
) {
|
|
575
|
+
liveByKey.set(item.key, [...(liveByKey.get(item.key) || []), item]);
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
for (const rows of liveByKey.values()) {
|
|
579
|
+
if (rows.length > 1) {
|
|
580
|
+
duplicates++;
|
|
581
|
+
if (
|
|
582
|
+
rows.some((row: ReferenceItem): boolean => {
|
|
583
|
+
return collected.has(row.type);
|
|
584
|
+
})
|
|
585
|
+
) {
|
|
586
|
+
collectedDuplicates++;
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
/*
|
|
592
|
+
* The run covered collections, live duplicate keys, and collection
|
|
593
|
+
* rows that share their key with a live row of another type.
|
|
594
|
+
*/
|
|
595
|
+
expect(collections).toBeGreaterThan(0);
|
|
596
|
+
expect(duplicates).toBeGreaterThan(0);
|
|
597
|
+
expect(collectedDuplicates).toBeGreaterThan(0);
|
|
598
|
+
}, 300_000);
|
|
599
|
+
|
|
600
|
+
test("a container tie between a key beyond the BMP and one in U+E000..U+FFFF is broken by code point", async () => {
|
|
601
|
+
const at: Date = new Date(RANGE_START.getTime() + 60_000);
|
|
602
|
+
const item: (key: string, type: string, id: string) => ReferenceItem = (
|
|
603
|
+
key: string,
|
|
604
|
+
type: string,
|
|
605
|
+
id: string,
|
|
606
|
+
): ReferenceItem => {
|
|
607
|
+
return {
|
|
608
|
+
id,
|
|
609
|
+
projectId: PROJECT_ID.toString(),
|
|
610
|
+
key,
|
|
611
|
+
type,
|
|
612
|
+
name: key,
|
|
613
|
+
source: EntitySource.Discovered,
|
|
614
|
+
lastSeenAt: at,
|
|
615
|
+
firstSeenAt: null,
|
|
616
|
+
createdAt: at,
|
|
617
|
+
isArchived: false,
|
|
618
|
+
deleted: false,
|
|
619
|
+
descriptiveAttributes: null,
|
|
620
|
+
identifyingAttributes: null,
|
|
621
|
+
resourceType: null,
|
|
622
|
+
resourceId: null,
|
|
623
|
+
};
|
|
624
|
+
};
|
|
625
|
+
const partOf: (to: string, id: string) => ReferenceRelationship = (
|
|
626
|
+
to: string,
|
|
627
|
+
id: string,
|
|
628
|
+
): ReferenceRelationship => {
|
|
629
|
+
return {
|
|
630
|
+
id,
|
|
631
|
+
projectId: PROJECT_ID.toString(),
|
|
632
|
+
from: "pod",
|
|
633
|
+
to,
|
|
634
|
+
type: EntityRelationshipType.PartOf,
|
|
635
|
+
lastSeenAt: at,
|
|
636
|
+
createdAt: at,
|
|
637
|
+
deleted: false,
|
|
638
|
+
callCount: null,
|
|
639
|
+
errorCount: null,
|
|
640
|
+
avgDurationMs: null,
|
|
641
|
+
};
|
|
642
|
+
};
|
|
643
|
+
const estate: ReferenceEstate = {
|
|
644
|
+
items: [
|
|
645
|
+
item(
|
|
646
|
+
"pod",
|
|
647
|
+
EntityType.KubernetesPod,
|
|
648
|
+
"00000000-0000-4000-a000-000000000001",
|
|
649
|
+
),
|
|
650
|
+
item(
|
|
651
|
+
"node-�",
|
|
652
|
+
EntityType.KubernetesNode,
|
|
653
|
+
"00000000-0000-4000-a000-000000000002",
|
|
654
|
+
),
|
|
655
|
+
item(
|
|
656
|
+
"node-\u{1F600}",
|
|
657
|
+
EntityType.KubernetesNode,
|
|
658
|
+
"00000000-0000-4000-a000-000000000003",
|
|
659
|
+
),
|
|
660
|
+
],
|
|
661
|
+
relationships: [
|
|
662
|
+
partOf("node-�", "00000000-0000-4000-a000-000000000011"),
|
|
663
|
+
partOf("node-\u{1F600}", "00000000-0000-4000-a000-000000000012"),
|
|
664
|
+
],
|
|
665
|
+
};
|
|
666
|
+
await load(estate, new SeededRandom(1));
|
|
667
|
+
await expectServerMatchesReference("non-BMP tie", estate, [
|
|
668
|
+
{ key: "pod", type: null },
|
|
669
|
+
]);
|
|
670
|
+
const infrastructure: TopologyInfrastructureResponseJSON =
|
|
671
|
+
await TopologyQueries.getInfrastructure({
|
|
672
|
+
projectId: PROJECT_ID,
|
|
673
|
+
rangeStart: RANGE_START,
|
|
674
|
+
});
|
|
675
|
+
const pod: { parent?: number | undefined } | undefined =
|
|
676
|
+
infrastructure.nodes.find((node: { key: string }): boolean => {
|
|
677
|
+
return node.key === "pod";
|
|
678
|
+
});
|
|
679
|
+
/* COLLATE "C" (code points): U+FFFD sorts before U+1F600. */
|
|
680
|
+
expect(infrastructure.nodes[pod!.parent!]!.key).toBe("node-�");
|
|
681
|
+
});
|
|
682
|
+
},
|
|
683
|
+
);
|