@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,624 @@
|
|
|
1
|
+
import { JSONValue } from "../../../../Types/JSON";
|
|
2
|
+
import EntityRelationshipType from "../../../../Types/Telemetry/EntityRelationshipType";
|
|
3
|
+
import EntitySource from "../../../../Types/Telemetry/EntitySource";
|
|
4
|
+
import EntityType from "../../../../Types/Telemetry/EntityType";
|
|
5
|
+
import {
|
|
6
|
+
CONTAINER_SPECIFICITY,
|
|
7
|
+
NESTABLE_CHILD_TYPES,
|
|
8
|
+
} from "../../../../Types/Topology/TopologyTypeRules";
|
|
9
|
+
import {
|
|
10
|
+
ReferenceEstate,
|
|
11
|
+
ReferenceItem,
|
|
12
|
+
ReferenceRelationship,
|
|
13
|
+
} from "./TopologyReference";
|
|
14
|
+
|
|
15
|
+
/*
|
|
16
|
+
* Seeded random inventory estates for the Topology differential tests.
|
|
17
|
+
*
|
|
18
|
+
* An estate mixes everything the Topology API has to get right: structural
|
|
19
|
+
* resources that nest (clusters, namespaces, deployments, pods, nodes, hosts,
|
|
20
|
+
* containers, VMware, Proxmox, Docker Swarm), services and what they call,
|
|
21
|
+
* flat types (network / IoT devices, cloud resources, appliances, types this
|
|
22
|
+
* build does not know), containment with competing candidates and cycles,
|
|
23
|
+
* placements, dangling and self-referencing edges, rows that did not report
|
|
24
|
+
* in range (stale, exactly at the boundary, a millisecond before it, never),
|
|
25
|
+
* manual / inventory / blank sources whose old lastSeenAt means nothing,
|
|
26
|
+
* archived, soft-deleted and other-project rows, and ties on createdAt.
|
|
27
|
+
*
|
|
28
|
+
* Rows respect the database's unique indexes — (projectId, entityType,
|
|
29
|
+
* entityKey) on items and (projectId, from, to, type) on relationships,
|
|
30
|
+
* deleted and archived rows included — so every estate can also be written
|
|
31
|
+
* to Postgres.
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
/* mulberry32: small, fast and good enough to explore an input space. */
|
|
35
|
+
export class SeededRandom {
|
|
36
|
+
private state: number;
|
|
37
|
+
|
|
38
|
+
public constructor(seed: number) {
|
|
39
|
+
this.state = seed >>> 0;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
public next(): number {
|
|
43
|
+
this.state = (this.state + 0x6d2b79f5) >>> 0;
|
|
44
|
+
let t: number = this.state;
|
|
45
|
+
t = Math.imul(t ^ (t >>> 15), t | 1);
|
|
46
|
+
t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
|
|
47
|
+
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/* Inclusive on both ends. */
|
|
51
|
+
public int(min: number, max: number): number {
|
|
52
|
+
return min + Math.floor(this.next() * (max - min + 1));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
public chance(probability: number): boolean {
|
|
56
|
+
return this.next() < probability;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
public pick<T>(list: ReadonlyArray<T>): T {
|
|
60
|
+
return list[Math.floor(this.next() * list.length)]!;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
public uuid(): string {
|
|
64
|
+
let hex: string = "";
|
|
65
|
+
for (let index: number = 0; index < 32; index++) {
|
|
66
|
+
hex += Math.floor(this.next() * 16).toString(16);
|
|
67
|
+
}
|
|
68
|
+
return (
|
|
69
|
+
`${hex.substring(0, 8)}-${hex.substring(8, 12)}-4${hex.substring(13, 16)}-` +
|
|
70
|
+
`a${hex.substring(17, 20)}-${hex.substring(20, 32)}`
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export interface EstateOptions {
|
|
76
|
+
projectId: string;
|
|
77
|
+
otherProjectId: string;
|
|
78
|
+
/* Minute-aligned. */
|
|
79
|
+
rangeStart: Date;
|
|
80
|
+
/* Roughly how many items (before collections). */
|
|
81
|
+
size: number;
|
|
82
|
+
/*
|
|
83
|
+
* Let two LIVE rows of different types share a key (unsupported but must
|
|
84
|
+
* be deterministic). Off for the Dashboard parity test: the old client did
|
|
85
|
+
* not dedupe keys, so parity is only defined without them.
|
|
86
|
+
*/
|
|
87
|
+
liveDuplicateKeys: boolean;
|
|
88
|
+
/* One flat type filled with `collectionCount` extra items. */
|
|
89
|
+
collectionType?: string | undefined;
|
|
90
|
+
collectionCount?: number | undefined;
|
|
91
|
+
/* Whether services may be placed on the collection's items. */
|
|
92
|
+
placementsOntoCollection?: boolean | undefined;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const STRUCTURAL_TYPES: ReadonlyArray<string> = [
|
|
96
|
+
EntityType.KubernetesCluster,
|
|
97
|
+
EntityType.KubernetesNamespace,
|
|
98
|
+
EntityType.KubernetesNamespace,
|
|
99
|
+
EntityType.KubernetesDeployment,
|
|
100
|
+
EntityType.KubernetesDeployment,
|
|
101
|
+
EntityType.KubernetesNode,
|
|
102
|
+
EntityType.KubernetesNode,
|
|
103
|
+
EntityType.KubernetesPod,
|
|
104
|
+
EntityType.KubernetesPod,
|
|
105
|
+
EntityType.KubernetesPod,
|
|
106
|
+
EntityType.KubernetesPod,
|
|
107
|
+
EntityType.Host,
|
|
108
|
+
EntityType.Host,
|
|
109
|
+
EntityType.Container,
|
|
110
|
+
EntityType.Container,
|
|
111
|
+
EntityType.VMwareVCenter,
|
|
112
|
+
EntityType.VMwareCluster,
|
|
113
|
+
EntityType.VMwareHost,
|
|
114
|
+
EntityType.VMwareVirtualMachine,
|
|
115
|
+
EntityType.VMwareDatastore,
|
|
116
|
+
EntityType.ProxmoxCluster,
|
|
117
|
+
EntityType.ProxmoxNode,
|
|
118
|
+
EntityType.ProxmoxGuest,
|
|
119
|
+
EntityType.DockerSwarmCluster,
|
|
120
|
+
EntityType.DockerSwarmNode,
|
|
121
|
+
EntityType.DockerSwarmService,
|
|
122
|
+
EntityType.DockerSwarmTask,
|
|
123
|
+
EntityType.CephCluster,
|
|
124
|
+
];
|
|
125
|
+
|
|
126
|
+
const APPLICATION_TYPES: ReadonlyArray<string> = [
|
|
127
|
+
EntityType.Service,
|
|
128
|
+
EntityType.Service,
|
|
129
|
+
EntityType.Service,
|
|
130
|
+
EntityType.Service,
|
|
131
|
+
EntityType.ServiceInstance,
|
|
132
|
+
EntityType.Database,
|
|
133
|
+
EntityType.Database,
|
|
134
|
+
EntityType.RemoteService,
|
|
135
|
+
EntityType.RemoteService,
|
|
136
|
+
EntityType.Process,
|
|
137
|
+
EntityType.TelemetrySdk,
|
|
138
|
+
];
|
|
139
|
+
|
|
140
|
+
export const FLAT_TYPES: ReadonlyArray<string> = [
|
|
141
|
+
EntityType.NetworkDevice,
|
|
142
|
+
EntityType.IoTDevice,
|
|
143
|
+
EntityType.CloudResource,
|
|
144
|
+
EntityType.Appliance,
|
|
145
|
+
EntityType.DockerHost,
|
|
146
|
+
EntityType.ServerlessFunction,
|
|
147
|
+
EntityType.ExternalService,
|
|
148
|
+
EntityType.ExternalDatabase,
|
|
149
|
+
/* A type this build does not know: infrastructure, flat. */
|
|
150
|
+
"custom.gadget",
|
|
151
|
+
];
|
|
152
|
+
|
|
153
|
+
const NESTING_TYPES: ReadonlyArray<string> = [
|
|
154
|
+
EntityRelationshipType.PartOf,
|
|
155
|
+
EntityRelationshipType.RunsOn,
|
|
156
|
+
EntityRelationshipType.MemberOf,
|
|
157
|
+
];
|
|
158
|
+
|
|
159
|
+
const OTHER_RELATIONSHIP_TYPES: ReadonlyArray<string> = [
|
|
160
|
+
EntityRelationshipType.HostedOn,
|
|
161
|
+
EntityRelationshipType.InstanceOf,
|
|
162
|
+
EntityRelationshipType.DependsOn,
|
|
163
|
+
"custom.link",
|
|
164
|
+
];
|
|
165
|
+
|
|
166
|
+
const PLACEMENT_TYPES: ReadonlyArray<string> = [
|
|
167
|
+
EntityRelationshipType.RunsOn,
|
|
168
|
+
EntityRelationshipType.HostedOn,
|
|
169
|
+
];
|
|
170
|
+
|
|
171
|
+
/*
|
|
172
|
+
* Key characters chosen to disagree between COLLATE "C" and a linguistic
|
|
173
|
+
* collation (case, punctuation, accents) — all in the Basic Multilingual
|
|
174
|
+
* Plane, where code-unit and code-point order agree.
|
|
175
|
+
*/
|
|
176
|
+
const KEY_ALPHABET: ReadonlyArray<string> = [
|
|
177
|
+
"a",
|
|
178
|
+
"b",
|
|
179
|
+
"B",
|
|
180
|
+
"z",
|
|
181
|
+
"Z",
|
|
182
|
+
"_",
|
|
183
|
+
"-",
|
|
184
|
+
".",
|
|
185
|
+
"0",
|
|
186
|
+
"9",
|
|
187
|
+
"é",
|
|
188
|
+
"É",
|
|
189
|
+
"ß",
|
|
190
|
+
];
|
|
191
|
+
|
|
192
|
+
/* Names that group as replicas, sort interestingly, or are missing. */
|
|
193
|
+
const NAMES: ReadonlyArray<string | null> = [
|
|
194
|
+
null,
|
|
195
|
+
"",
|
|
196
|
+
"web-1",
|
|
197
|
+
"web-2",
|
|
198
|
+
"web-3",
|
|
199
|
+
"api-7d9f8b6c5-x2k4p",
|
|
200
|
+
"api-7d9f8b6c5-b7zqk",
|
|
201
|
+
"api-7d9f8b6c5-q9w8r",
|
|
202
|
+
"worker-12",
|
|
203
|
+
"worker-13",
|
|
204
|
+
"Zeta",
|
|
205
|
+
"alpha",
|
|
206
|
+
"Beta",
|
|
207
|
+
"émile",
|
|
208
|
+
"db primary",
|
|
209
|
+
"node 1",
|
|
210
|
+
"NODE-2",
|
|
211
|
+
"checkout",
|
|
212
|
+
"payments",
|
|
213
|
+
];
|
|
214
|
+
|
|
215
|
+
/* Attribute bags: detail keys with strings, blanks, non-strings, non-objects. */
|
|
216
|
+
const BAGS: ReadonlyArray<JSONValue | null> = [
|
|
217
|
+
null,
|
|
218
|
+
{},
|
|
219
|
+
{ "telemetry.sdk.language": "nodejs" },
|
|
220
|
+
{ "telemetry.sdk.language": "python", "service.version": "1.2.3" },
|
|
221
|
+
{ "db.system.name": "postgresql" },
|
|
222
|
+
{ "db.system.name": " " },
|
|
223
|
+
{ "db.system.name": 5 },
|
|
224
|
+
{ "db.system.name": { nested: "x" } },
|
|
225
|
+
{ "network.protocol.name": "grpc", "messaging.system": "kafka" },
|
|
226
|
+
{ "messaging.system": ["kafka"] },
|
|
227
|
+
{ "telemetry.sdk.language": "", "db.system.name": "Redis" },
|
|
228
|
+
{ "telemetry.sdk.language": true },
|
|
229
|
+
["telemetry.sdk.language"],
|
|
230
|
+
"nodejs",
|
|
231
|
+
7,
|
|
232
|
+
];
|
|
233
|
+
|
|
234
|
+
const MINUTE_MS: number = 60 * 1000;
|
|
235
|
+
const DAY_MS: number = 24 * 60 * MINUTE_MS;
|
|
236
|
+
|
|
237
|
+
/* A few creation instants, so createdAt ties (broken by _id) are common. */
|
|
238
|
+
const CREATED_BASE: number = Date.UTC(2026, 7, 1, 0, 0, 0);
|
|
239
|
+
|
|
240
|
+
function createdAt(random: SeededRandom): Date {
|
|
241
|
+
return new Date(CREATED_BASE + random.int(0, 3) * DAY_MS);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function randomKey(random: SeededRandom): string {
|
|
245
|
+
const length: number = random.int(1, 4);
|
|
246
|
+
let key: string = "";
|
|
247
|
+
for (let index: number = 0; index < length; index++) {
|
|
248
|
+
key += random.pick(KEY_ALPHABET);
|
|
249
|
+
}
|
|
250
|
+
return key;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/* In range, on the boundary, a millisecond before it, stale, or never. */
|
|
254
|
+
function itemLastSeen(random: SeededRandom, rangeStart: Date): Date | null {
|
|
255
|
+
const roll: number = random.next();
|
|
256
|
+
const start: number = rangeStart.getTime();
|
|
257
|
+
if (roll < 0.5) {
|
|
258
|
+
return new Date(start + random.int(0, 3 * 60 * MINUTE_MS));
|
|
259
|
+
}
|
|
260
|
+
if (roll < 0.56) {
|
|
261
|
+
return new Date(start);
|
|
262
|
+
}
|
|
263
|
+
if (roll < 0.62) {
|
|
264
|
+
return new Date(start - 1);
|
|
265
|
+
}
|
|
266
|
+
if (roll < 0.9) {
|
|
267
|
+
return new Date(start - random.int(1, 30 * 24 * 60) * MINUTE_MS);
|
|
268
|
+
}
|
|
269
|
+
return null;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function relationshipLastSeen(
|
|
273
|
+
random: SeededRandom,
|
|
274
|
+
rangeStart: Date,
|
|
275
|
+
): Date | null {
|
|
276
|
+
const roll: number = random.next();
|
|
277
|
+
const start: number = rangeStart.getTime();
|
|
278
|
+
if (roll < 0.7) {
|
|
279
|
+
return new Date(start + random.int(0, 3 * 60 * MINUTE_MS));
|
|
280
|
+
}
|
|
281
|
+
if (roll < 0.75) {
|
|
282
|
+
return new Date(start);
|
|
283
|
+
}
|
|
284
|
+
if (roll < 0.8) {
|
|
285
|
+
return new Date(start - 1);
|
|
286
|
+
}
|
|
287
|
+
if (roll < 0.93) {
|
|
288
|
+
return new Date(start - random.int(1, 7 * 24 * 60) * MINUTE_MS);
|
|
289
|
+
}
|
|
290
|
+
return null;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function source(random: SeededRandom): string {
|
|
294
|
+
const roll: number = random.next();
|
|
295
|
+
if (roll < 0.7) {
|
|
296
|
+
return EntitySource.Discovered;
|
|
297
|
+
}
|
|
298
|
+
if (roll < 0.8) {
|
|
299
|
+
return EntitySource.Manual;
|
|
300
|
+
}
|
|
301
|
+
if (roll < 0.9) {
|
|
302
|
+
return EntitySource.Inventory;
|
|
303
|
+
}
|
|
304
|
+
return "";
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function isNestable(type: string): boolean {
|
|
308
|
+
return NESTABLE_CHILD_TYPES.has(type as EntityType);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function isContainer(type: string): boolean {
|
|
312
|
+
return CONTAINER_SPECIFICITY[type as EntityType] !== undefined;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
class EstateBuilder {
|
|
316
|
+
public items: Array<ReferenceItem> = [];
|
|
317
|
+
public relationships: Array<ReferenceRelationship> = [];
|
|
318
|
+
private itemIdentities: Set<string> = new Set<string>();
|
|
319
|
+
private relationshipIdentities: Set<string> = new Set<string>();
|
|
320
|
+
|
|
321
|
+
public constructor(
|
|
322
|
+
private random: SeededRandom,
|
|
323
|
+
private options: EstateOptions,
|
|
324
|
+
) {}
|
|
325
|
+
|
|
326
|
+
/* Adds an item unless its (project, type, key) is taken. */
|
|
327
|
+
public addItem(data: {
|
|
328
|
+
key: string;
|
|
329
|
+
type: string;
|
|
330
|
+
projectId?: string | undefined;
|
|
331
|
+
isArchived?: boolean | undefined;
|
|
332
|
+
deleted?: boolean | undefined;
|
|
333
|
+
name?: string | null | undefined;
|
|
334
|
+
}): ReferenceItem | null {
|
|
335
|
+
const projectId: string = data.projectId || this.options.projectId;
|
|
336
|
+
const identity: string = `${projectId}\u0000${data.type}\u0000${data.key}`;
|
|
337
|
+
if (this.itemIdentities.has(identity)) {
|
|
338
|
+
return null;
|
|
339
|
+
}
|
|
340
|
+
this.itemIdentities.add(identity);
|
|
341
|
+
const random: SeededRandom = this.random;
|
|
342
|
+
const item: ReferenceItem = {
|
|
343
|
+
id: random.uuid(),
|
|
344
|
+
projectId,
|
|
345
|
+
key: data.key,
|
|
346
|
+
type: data.type,
|
|
347
|
+
name:
|
|
348
|
+
data.name !== undefined
|
|
349
|
+
? data.name
|
|
350
|
+
: random.chance(0.3)
|
|
351
|
+
? `${random.pick(NAMES) ?? "n"}-${randomKey(random)}`
|
|
352
|
+
: random.pick(NAMES),
|
|
353
|
+
source: source(random),
|
|
354
|
+
lastSeenAt: itemLastSeen(random, this.options.rangeStart),
|
|
355
|
+
firstSeenAt: random.chance(0.8)
|
|
356
|
+
? new Date(CREATED_BASE + random.int(0, 1000) * MINUTE_MS)
|
|
357
|
+
: null,
|
|
358
|
+
createdAt: createdAt(random),
|
|
359
|
+
isArchived: Boolean(data.isArchived),
|
|
360
|
+
deleted: Boolean(data.deleted),
|
|
361
|
+
descriptiveAttributes: random.pick(BAGS),
|
|
362
|
+
identifyingAttributes: random.pick(BAGS),
|
|
363
|
+
resourceType: random.chance(0.3) ? "Monitor" : null,
|
|
364
|
+
resourceId: random.chance(0.3) ? random.uuid() : null,
|
|
365
|
+
};
|
|
366
|
+
this.items.push(item);
|
|
367
|
+
return item;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/* Adds a relationship unless its (project, from, to, type) is taken. */
|
|
371
|
+
public addRelationship(data: {
|
|
372
|
+
from: string;
|
|
373
|
+
to: string;
|
|
374
|
+
type: string;
|
|
375
|
+
projectId?: string | undefined;
|
|
376
|
+
deleted?: boolean | undefined;
|
|
377
|
+
lastSeenAt?: Date | null | undefined;
|
|
378
|
+
}): void {
|
|
379
|
+
const projectId: string = data.projectId || this.options.projectId;
|
|
380
|
+
const identity: string = `${projectId}\u0000${data.from}\u0000${data.to}\u0000${data.type}`;
|
|
381
|
+
if (this.relationshipIdentities.has(identity)) {
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
this.relationshipIdentities.add(identity);
|
|
385
|
+
const random: SeededRandom = this.random;
|
|
386
|
+
const callCount: number | null = random.chance(0.15)
|
|
387
|
+
? null
|
|
388
|
+
: random.chance(0.1)
|
|
389
|
+
? 0
|
|
390
|
+
: random.int(1, 5000);
|
|
391
|
+
this.relationships.push({
|
|
392
|
+
id: random.uuid(),
|
|
393
|
+
projectId,
|
|
394
|
+
from: data.from,
|
|
395
|
+
to: data.to,
|
|
396
|
+
type: data.type,
|
|
397
|
+
lastSeenAt:
|
|
398
|
+
data.lastSeenAt !== undefined
|
|
399
|
+
? data.lastSeenAt
|
|
400
|
+
: relationshipLastSeen(random, this.options.rangeStart),
|
|
401
|
+
createdAt: createdAt(random),
|
|
402
|
+
deleted: Boolean(data.deleted),
|
|
403
|
+
callCount,
|
|
404
|
+
errorCount: random.chance(0.2)
|
|
405
|
+
? null
|
|
406
|
+
: random.int(
|
|
407
|
+
0,
|
|
408
|
+
Math.max(0, callCount || 0) + (random.chance(0.05) ? 5 : 0),
|
|
409
|
+
),
|
|
410
|
+
avgDurationMs: random.chance(0.2) ? null : random.int(0, 2000),
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
export function generateEstate(
|
|
416
|
+
random: SeededRandom,
|
|
417
|
+
options: EstateOptions,
|
|
418
|
+
): ReferenceEstate {
|
|
419
|
+
const builder: EstateBuilder = new EstateBuilder(random, options);
|
|
420
|
+
const liveKeys: Set<string> = new Set<string>();
|
|
421
|
+
const count: number = random.int(1, Math.max(2, options.size));
|
|
422
|
+
|
|
423
|
+
const freshKey: () => string = (): string => {
|
|
424
|
+
for (let attempt: number = 0; attempt < 50; attempt++) {
|
|
425
|
+
const key: string = randomKey(random);
|
|
426
|
+
if (!liveKeys.has(key)) {
|
|
427
|
+
return key;
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
return `${randomKey(random)}${random.uuid()}`;
|
|
431
|
+
};
|
|
432
|
+
|
|
433
|
+
for (let index: number = 0; index < count; index++) {
|
|
434
|
+
const roll: number = random.next();
|
|
435
|
+
const type: string =
|
|
436
|
+
roll < 0.55
|
|
437
|
+
? random.pick(STRUCTURAL_TYPES)
|
|
438
|
+
: roll < 0.85
|
|
439
|
+
? random.pick(APPLICATION_TYPES)
|
|
440
|
+
: random.pick(FLAT_TYPES);
|
|
441
|
+
const liveItems: Array<ReferenceItem> = builder.items.filter(
|
|
442
|
+
(item: ReferenceItem): boolean => {
|
|
443
|
+
return (
|
|
444
|
+
item.projectId === options.projectId &&
|
|
445
|
+
!item.isArchived &&
|
|
446
|
+
!item.deleted
|
|
447
|
+
);
|
|
448
|
+
},
|
|
449
|
+
);
|
|
450
|
+
|
|
451
|
+
/* Rows that must never show: archived, soft-deleted, another tenant. */
|
|
452
|
+
const hidden: number = random.next();
|
|
453
|
+
if (hidden < 0.12) {
|
|
454
|
+
/* Reusing a live key (with another type) proves they are filtered. */
|
|
455
|
+
const key: string =
|
|
456
|
+
liveItems.length > 0 && random.chance(0.5)
|
|
457
|
+
? random.pick(liveItems).key
|
|
458
|
+
: freshKey();
|
|
459
|
+
if (hidden < 0.05) {
|
|
460
|
+
builder.addItem({ key, type, isArchived: true });
|
|
461
|
+
} else if (hidden < 0.09) {
|
|
462
|
+
builder.addItem({ key, type, deleted: true });
|
|
463
|
+
} else {
|
|
464
|
+
builder.addItem({ key, type, projectId: options.otherProjectId });
|
|
465
|
+
}
|
|
466
|
+
continue;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
if (
|
|
470
|
+
options.liveDuplicateKeys &&
|
|
471
|
+
liveItems.length > 0 &&
|
|
472
|
+
random.chance(0.1)
|
|
473
|
+
) {
|
|
474
|
+
const twin: ReferenceItem = random.pick(liveItems);
|
|
475
|
+
builder.addItem({ key: twin.key, type });
|
|
476
|
+
continue;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
const key: string = freshKey();
|
|
480
|
+
if (builder.addItem({ key, type })) {
|
|
481
|
+
liveKeys.add(key);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
if (options.collectionType && options.collectionCount) {
|
|
486
|
+
const others: Array<ReferenceItem> = builder.items.filter(
|
|
487
|
+
(item: ReferenceItem): boolean => {
|
|
488
|
+
return (
|
|
489
|
+
item.projectId === options.projectId &&
|
|
490
|
+
!item.isArchived &&
|
|
491
|
+
!item.deleted
|
|
492
|
+
);
|
|
493
|
+
},
|
|
494
|
+
);
|
|
495
|
+
for (let index: number = 0; index < options.collectionCount; index++) {
|
|
496
|
+
/*
|
|
497
|
+
* With duplicates allowed, a few collection items share a key with a
|
|
498
|
+
* live row of another type, winning or losing it by createdAt / _id.
|
|
499
|
+
*/
|
|
500
|
+
const key: string =
|
|
501
|
+
options.liveDuplicateKeys && others.length > 0 && random.chance(0.01)
|
|
502
|
+
? random.pick(others).key
|
|
503
|
+
: `coll-${index}-${randomKey(random)}`;
|
|
504
|
+
builder.addItem({
|
|
505
|
+
key,
|
|
506
|
+
type: options.collectionType,
|
|
507
|
+
name: random.chance(0.9) ? `device ${random.int(0, 99999)}` : null,
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
const all: Array<ReferenceItem> = builder.items.filter(
|
|
513
|
+
(item: ReferenceItem): boolean => {
|
|
514
|
+
return item.projectId === options.projectId;
|
|
515
|
+
},
|
|
516
|
+
);
|
|
517
|
+
/* Everything but the collection's bulk items. */
|
|
518
|
+
const regular: Array<ReferenceItem> = all.filter(
|
|
519
|
+
(item: ReferenceItem): boolean => {
|
|
520
|
+
return item.type !== options.collectionType;
|
|
521
|
+
},
|
|
522
|
+
);
|
|
523
|
+
if (all.length === 0) {
|
|
524
|
+
return { items: builder.items, relationships: [] };
|
|
525
|
+
}
|
|
526
|
+
const nestable: Array<ReferenceItem> = regular.filter(
|
|
527
|
+
(item: ReferenceItem): boolean => {
|
|
528
|
+
return isNestable(item.type);
|
|
529
|
+
},
|
|
530
|
+
);
|
|
531
|
+
const containers: Array<ReferenceItem> = regular.filter(
|
|
532
|
+
(item: ReferenceItem): boolean => {
|
|
533
|
+
return isContainer(item.type);
|
|
534
|
+
},
|
|
535
|
+
);
|
|
536
|
+
const services: Array<ReferenceItem> = all.filter(
|
|
537
|
+
(item: ReferenceItem): boolean => {
|
|
538
|
+
return item.type === EntityType.Service;
|
|
539
|
+
},
|
|
540
|
+
);
|
|
541
|
+
const placementTargets: Array<ReferenceItem> =
|
|
542
|
+
options.placementsOntoCollection === false ? regular : all;
|
|
543
|
+
|
|
544
|
+
const anyKey: (pool: Array<ReferenceItem>) => string = (
|
|
545
|
+
pool: Array<ReferenceItem>,
|
|
546
|
+
): string => {
|
|
547
|
+
/* Sometimes an end nothing in the inventory carries. */
|
|
548
|
+
if (pool.length === 0 || random.chance(0.08)) {
|
|
549
|
+
return `ghost-${randomKey(random)}`;
|
|
550
|
+
}
|
|
551
|
+
return random.pick(pool).key;
|
|
552
|
+
};
|
|
553
|
+
|
|
554
|
+
const relationshipCount: number = random.int(0, 3 * count + 2);
|
|
555
|
+
for (let index: number = 0; index < relationshipCount; index++) {
|
|
556
|
+
const roll: number = random.next();
|
|
557
|
+
let from: string;
|
|
558
|
+
let to: string;
|
|
559
|
+
let type: string;
|
|
560
|
+
if (roll < 0.45) {
|
|
561
|
+
from = anyKey(
|
|
562
|
+
nestable.length > 0 && random.chance(0.9) ? nestable : regular,
|
|
563
|
+
);
|
|
564
|
+
to = anyKey(
|
|
565
|
+
containers.length > 0 && random.chance(0.9) ? containers : regular,
|
|
566
|
+
);
|
|
567
|
+
type = random.chance(0.85)
|
|
568
|
+
? random.pick(NESTING_TYPES)
|
|
569
|
+
: random.pick(OTHER_RELATIONSHIP_TYPES);
|
|
570
|
+
} else if (roll < 0.65) {
|
|
571
|
+
from = anyKey(services.length > 0 && random.chance(0.9) ? services : all);
|
|
572
|
+
to = anyKey(placementTargets);
|
|
573
|
+
type = random.pick(PLACEMENT_TYPES);
|
|
574
|
+
} else if (roll < 0.9) {
|
|
575
|
+
from = anyKey(
|
|
576
|
+
services.length > 0 && random.chance(0.85) ? services : regular,
|
|
577
|
+
);
|
|
578
|
+
to = anyKey(regular);
|
|
579
|
+
type = EntityRelationshipType.DependsOn;
|
|
580
|
+
} else {
|
|
581
|
+
from = anyKey(regular);
|
|
582
|
+
to = anyKey(regular);
|
|
583
|
+
type = random.pick([...NESTING_TYPES, ...OTHER_RELATIONSHIP_TYPES]);
|
|
584
|
+
}
|
|
585
|
+
if (random.chance(0.04)) {
|
|
586
|
+
to = from;
|
|
587
|
+
}
|
|
588
|
+
const hidden: number = random.next();
|
|
589
|
+
builder.addRelationship({
|
|
590
|
+
from,
|
|
591
|
+
to,
|
|
592
|
+
type,
|
|
593
|
+
deleted: hidden < 0.05,
|
|
594
|
+
projectId:
|
|
595
|
+
hidden >= 0.05 && hidden < 0.1 ? options.otherProjectId : undefined,
|
|
596
|
+
});
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
/* A containment ring now and then, so cycles are cut often. */
|
|
600
|
+
if (nestable.length >= 2 && random.chance(0.35)) {
|
|
601
|
+
const ring: Array<ReferenceItem> = [];
|
|
602
|
+
const size: number = random.int(2, Math.min(4, nestable.length));
|
|
603
|
+
for (let index: number = 0; index < size; index++) {
|
|
604
|
+
const member: ReferenceItem = random.pick(nestable);
|
|
605
|
+
if (isContainer(member.type) && !ring.includes(member)) {
|
|
606
|
+
ring.push(member);
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
for (
|
|
610
|
+
let index: number = 0;
|
|
611
|
+
index < ring.length && ring.length > 1;
|
|
612
|
+
index++
|
|
613
|
+
) {
|
|
614
|
+
builder.addRelationship({
|
|
615
|
+
from: ring[index]!.key,
|
|
616
|
+
to: ring[(index + 1) % ring.length]!.key,
|
|
617
|
+
type: EntityRelationshipType.PartOf,
|
|
618
|
+
lastSeenAt: new Date(options.rangeStart.getTime() + MINUTE_MS),
|
|
619
|
+
});
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
return { items: builder.items, relationships: builder.relationships };
|
|
624
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import Response from "../../../../Server/Utils/Response";
|
|
2
|
+
import {
|
|
3
|
+
ExpressRequest,
|
|
4
|
+
ExpressResponse,
|
|
5
|
+
} from "../../../../Server/Utils/Express";
|
|
6
|
+
import { describe, expect, jest, test } from "@jest/globals";
|
|
7
|
+
|
|
8
|
+
/*
|
|
9
|
+
* Response.sendJsonStringResponse sends a body that is already serialized
|
|
10
|
+
* JSON — the Topology maps hand out their cached payload this way — exactly
|
|
11
|
+
* as given: the right content type, never cached by the browser or a proxy,
|
|
12
|
+
* and never copied into the audit log.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
interface FakeResponse {
|
|
16
|
+
headers: Record<string, string>;
|
|
17
|
+
status: number | null;
|
|
18
|
+
body: string | null;
|
|
19
|
+
logBody: unknown;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function fakeResponse(): { res: ExpressResponse; state: FakeResponse } {
|
|
23
|
+
const state: FakeResponse = {
|
|
24
|
+
headers: {},
|
|
25
|
+
status: null,
|
|
26
|
+
body: null,
|
|
27
|
+
logBody: undefined,
|
|
28
|
+
};
|
|
29
|
+
const res: Record<string, unknown> = {
|
|
30
|
+
setHeader: jest.fn((name: string, value: string): void => {
|
|
31
|
+
state.headers[name.toLowerCase()] = value;
|
|
32
|
+
}),
|
|
33
|
+
writeHead: jest.fn(
|
|
34
|
+
(status: number, headers: Record<string, string>): void => {
|
|
35
|
+
state.status = status;
|
|
36
|
+
for (const [name, value] of Object.entries(headers)) {
|
|
37
|
+
state.headers[name.toLowerCase()] = value;
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
),
|
|
41
|
+
end: jest.fn((body: string): void => {
|
|
42
|
+
state.body = body;
|
|
43
|
+
}),
|
|
44
|
+
send: jest.fn(() => {
|
|
45
|
+
throw new Error("send() would re-process the body");
|
|
46
|
+
}),
|
|
47
|
+
};
|
|
48
|
+
Object.defineProperty(res, "logBody", {
|
|
49
|
+
set: (value: unknown): void => {
|
|
50
|
+
state.logBody = value;
|
|
51
|
+
},
|
|
52
|
+
get: (): unknown => {
|
|
53
|
+
return state.logBody;
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
return { res: res as unknown as ExpressResponse, state };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
describe("Response.sendJsonStringResponse", () => {
|
|
60
|
+
test("sends the string as is, as JSON, with no-store headers", () => {
|
|
61
|
+
const { res, state } = fakeResponse();
|
|
62
|
+
const json: string = JSON.stringify({ nodes: [{ key: "a" }], ok: true });
|
|
63
|
+
|
|
64
|
+
Response.sendJsonStringResponse({} as ExpressRequest, res, json);
|
|
65
|
+
|
|
66
|
+
expect(state.status).toBe(200);
|
|
67
|
+
expect(state.body).toBe(json);
|
|
68
|
+
expect(state.headers["content-type"]).toBe(
|
|
69
|
+
"application/json; charset=utf-8",
|
|
70
|
+
);
|
|
71
|
+
expect(state.headers["cache-control"]).toBe(
|
|
72
|
+
"no-store, no-cache, must-revalidate",
|
|
73
|
+
);
|
|
74
|
+
expect(state.headers["pragma"]).toBe("no-cache");
|
|
75
|
+
expect(state.headers["expires"]).toBe("0");
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test("the audit log gets the size, never the payload", () => {
|
|
79
|
+
const { res, state } = fakeResponse();
|
|
80
|
+
const json: string = JSON.stringify({ secret: "inventory" });
|
|
81
|
+
|
|
82
|
+
Response.sendJsonStringResponse({} as ExpressRequest, res, json);
|
|
83
|
+
|
|
84
|
+
expect(state.logBody).toEqual({
|
|
85
|
+
contentType: "application/json",
|
|
86
|
+
characters: json.length,
|
|
87
|
+
});
|
|
88
|
+
expect(JSON.stringify(state.logBody)).not.toContain("inventory");
|
|
89
|
+
});
|
|
90
|
+
});
|