@oneuptime/common 12.0.34 → 13.0.0
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/GlobalConfig.ts +12 -12
- package/Models/DatabaseModels/InventoryItem.ts +44 -1
- package/Models/DatabaseModels/NetworkDevice.ts +126 -0
- package/Server/EnvironmentConfig.ts +47 -16
- package/Server/Infrastructure/Postgres/SchemaMigrations/1791700000000-AddMacAddressToNetworkDevice.ts +25 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +2 -0
- package/Server/Infrastructure/Redis.ts +8 -8
- package/Server/Infrastructure/Status.ts +10 -4
- package/Server/Services/DatabaseService.ts +65 -13
- package/Server/Services/InventoryItemService.ts +31 -1
- package/Server/Services/MonitorTemplateService.ts +140 -19
- package/Server/Services/NetworkDeviceAutoImportRuleEngineService.ts +349 -41
- package/Server/Services/NetworkDeviceDiscoveryScanService.ts +87 -2
- package/Server/Services/NetworkDeviceService.ts +69 -0
- package/Server/Services/UserNotificationRuleService.ts +76 -25
- package/Server/Types/Database/Permissions/ReadPermission.ts +45 -5
- package/Server/Types/Database/QueryUtil.ts +56 -0
- package/Server/Types/Markdown.ts +93 -1
- package/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.ts +82 -17
- package/Server/Utils/EmailRollup/EmailRollupFlushRunner.ts +10 -1
- package/Server/Utils/EmailRollup/EmailRollupRenderer.ts +2 -2
- package/Server/Utils/Monitor/Criteria/CompareCriteria.ts +82 -35
- package/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.ts +31 -5
- package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +202 -37
- package/Server/Utils/Monitor/MonitorCriteriaExpectationBuilder.ts +82 -9
- package/Server/Utils/Monitor/MonitorCriteriaMessageBuilder.ts +16 -7
- package/Server/Utils/Monitor/MonitorCriteriaMessageFormatter.ts +29 -17
- package/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.ts +107 -48
- package/Server/Utils/Monitor/NetworkDeviceMacLearningUtil.ts +158 -0
- package/Server/Utils/Monitor/NetworkInventoryUtil.ts +27 -0
- package/Server/Utils/Telemetry/EntityRegistry.ts +11 -1
- package/Server/Utils/Telemetry/TelemetryEntity.ts +83 -9
- package/Tests/App/Dashboard/AddNeighborToMonitoringModal.test.tsx +32 -0
- package/Tests/App/Dashboard/AdoptedDeviceDrawer.test.tsx +374 -0
- package/Tests/App/Dashboard/DashboardLabelVariables.test.tsx +580 -0
- package/Tests/App/Dashboard/DashboardMonitorLabelVariable.test.tsx +438 -0
- package/Tests/App/Dashboard/DashboardVariableAllContract.test.tsx +5 -2
- package/Tests/App/Dashboard/DeviceAttachmentCard.test.tsx +273 -0
- package/Tests/App/Dashboard/DiscoveryReviewInventoryRefresh.test.tsx +505 -0
- package/Tests/App/Dashboard/EntityDetailPanel.test.tsx +163 -0
- package/Tests/App/Dashboard/InfrastructureExplorer.test.tsx +401 -0
- package/Tests/App/Dashboard/InfrastructureGraph.test.tsx +232 -0
- package/Tests/App/Dashboard/InventoryItems.test.tsx +201 -0
- package/Tests/App/Dashboard/InventoryTypeAndStatusFacets.test.tsx +798 -0
- package/Tests/App/Dashboard/NetworkTopologyLiveView.test.tsx +320 -0
- package/Tests/App/Dashboard/NetworkTopologyToolbar.test.tsx +275 -0
- package/Tests/App/Dashboard/OnCallCalendarFeedEmptyState.test.tsx +975 -0
- package/Tests/App/Dashboard/OnCallCalendarFeedPlanGate.test.tsx +63 -0
- package/Tests/App/Dashboard/ServiceMapGraph.test.tsx +626 -0
- package/Tests/App/Dashboard/TopologyDataLoading.test.tsx +348 -0
- package/Tests/App/Dashboard/TopologyPageNavigation.test.tsx +331 -0
- package/Tests/App/Dashboard/UserSettingsEmailPreferences.test.tsx +603 -0
- package/Tests/App/Dashboard/UserSettingsNotificationSettings.test.tsx +324 -0
- package/Tests/App/PublicDashboard/DashboardVariableSelector.test.tsx +13 -4
- package/Tests/App/StatusPage/StatusPageOidcOrigin.test.tsx +223 -0
- package/Tests/Models/InventoryItemStatus.test.ts +147 -0
- package/Tests/Models/NetworkDeviceMacAddressColumn.test.ts +294 -0
- package/Tests/Server/API/DashboardPublicResourceListAPI.test.ts +129 -0
- package/Tests/Server/EnvironmentConfigValkey.test.ts +227 -0
- package/Tests/Server/Infrastructure/Postgres/AddMacAddressToNetworkDeviceMigration.test.ts +365 -0
- package/Tests/Server/Infrastructure/Postgres/CustomFieldValueMappingMigration.test.ts +62 -23
- package/Tests/Server/Infrastructure/ValkeyDeploymentConfig.test.ts +265 -0
- package/Tests/Server/Middleware/HttpMetricsMiddleware.test.ts +382 -0
- package/Tests/Server/Middleware/SCIMAuthorization.test.ts +525 -0
- package/Tests/Server/Services/DatabaseServiceAggregateBy.test.ts +291 -56
- package/Tests/Server/Services/DatabaseServiceSortTiebreaker.test.ts +151 -0
- package/Tests/Server/Services/InventoryItemManualCreate.test.ts +105 -0
- package/Tests/Server/Services/InventoryItemPromotionGate.test.ts +178 -0
- package/Tests/Server/Services/MonitorTemplateServiceCustomFieldSync.test.ts +502 -0
- package/Tests/Server/Services/NetworkDeviceAutoImportRuleEngineService.test.ts +1133 -81
- package/Tests/Server/Services/NetworkDeviceDiscoveryScanRegistration.test.ts +520 -0
- package/Tests/Server/Services/NetworkDeviceMacAddressNormalization.test.ts +572 -0
- package/Tests/Server/Types/Database/InventoryStatusPostgres.test.ts +368 -0
- package/Tests/Server/Types/Database/Permissions/ReadBlockPermission.test.ts +261 -14
- package/Tests/Server/Types/Database/QueryUtilIncludesAnyOfGroups.test.ts +169 -0
- package/Tests/Server/Types/Database/QueryUtilIncludesAnyOfGroupsPostgres.test.ts +297 -0
- package/Tests/Server/Types/Markdown.test.ts +194 -1
- package/Tests/Server/Types/Workflow/Components/IncomingWebhookUtils.test.ts +279 -0
- package/Tests/Server/Types/Workflow/Components/TextToJson.test.ts +212 -0
- package/Tests/Server/Utils/CustomField/CustomFieldMappingRegistry.test.ts +436 -0
- package/Tests/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.test.ts +214 -0
- package/Tests/Server/Utils/EmailRollup/EmailRollupFlushRunnerBehaviour.test.ts +1 -1
- package/Tests/Server/Utils/EmailRollup/EmailRollupRenderer.test.ts +10 -3
- package/Tests/Server/Utils/EventLoop.test.ts +359 -0
- package/Tests/Server/Utils/JsonWebToken.test.ts +30 -0
- package/Tests/Server/Utils/Monitor/Criteria/CompareCriteria.test.ts +242 -0
- package/Tests/Server/Utils/Monitor/Criteria/CompareCriteriaAggregation.test.ts +13 -7
- package/Tests/Server/Utils/Monitor/Criteria/DatabaseMonitorCriteria.test.ts +29 -0
- package/Tests/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.test.ts +30 -6
- package/Tests/Server/Utils/Monitor/Criteria/MetricMonitorCriteriaAnomalyUnits.test.ts +246 -0
- package/Tests/Server/Utils/Monitor/MonitorCriteriaEmailEvalLogAgreement.test.ts +280 -0
- package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluator.test.ts +224 -10
- package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorMetricUnits.test.ts +570 -0
- package/Tests/Server/Utils/Monitor/MonitorCriteriaExpectationBuilderUnits.test.ts +30 -5
- package/Tests/Server/Utils/Monitor/MonitorCriteriaMessageBuilderUnits.test.ts +68 -21
- package/Tests/Server/Utils/Monitor/MonitorCriteriaObservationBuilderUnits.test.ts +36 -19
- package/Tests/Server/Utils/Monitor/NetworkDeviceMacLearningUtil.test.ts +573 -0
- package/Tests/Server/Utils/Monitor/NetworkInventoryUtil.test.ts +317 -0
- package/Tests/Server/Utils/Telemetry/ContextSpanProcessor.test.ts +281 -0
- package/Tests/Server/Utils/Telemetry/InventoryDuplicateIdentity.test.ts +316 -0
- package/Tests/Server/Utils/Telemetry/TelemetryEntity.test.ts +111 -8
- package/Tests/Types/Database/IncludesAnyOfGroups.test.ts +106 -0
- package/Tests/Types/JSONFunctions.test.ts +40 -0
- package/Tests/Types/NetworkAutomation/AutoImportRunMerge.test.ts +235 -0
- package/Tests/Types/NetworkAutomation/RuleRunResult.test.ts +66 -0
- package/Tests/Types/NetworkAutomation/RuleRunResultDescribe.test.ts +309 -1
- package/Tests/Types/Telemetry/InventoryLiveness.test.ts +99 -0
- package/Tests/UI/Components/CustomFields/CustomFieldsDetail.test.tsx +160 -0
- package/Tests/UI/ReactRouterSingletonBuild.test.ts +133 -0
- package/Tests/UI/Rum/ReplayHeader.test.tsx +10 -2
- package/Tests/UI/Rum/ReplayPlayerChrome.test.tsx +514 -0
- package/Tests/UI/Rum/ReplayTimeFormat.test.ts +94 -0
- package/Tests/UI/Rum/ReplayUi.test.tsx +559 -0
- package/Tests/UI/Utils/Breadcrumb/fixtures/RealBreadcrumbTrails.ts +5 -0
- package/Tests/UI/Utils/Breadcrumb/fixtures/RealRoutePatterns.ts +1 -0
- package/Tests/Utils/Dashboard/Components/DashboardMonitorListComponent.test.ts +11 -1
- package/Tests/Utils/Dashboard/LabelVariable.test.ts +257 -0
- package/Tests/Utils/Dashboard/VariableUrlState.test.ts +144 -14
- package/Tests/Utils/MetricUnitUtil.test.ts +81 -0
- package/Tests/Utils/Monitor/DeviceMacLearningUtil.test.ts +497 -0
- package/Tests/Utils/Monitor/MetricValueFormatter.test.ts +546 -0
- package/Tests/Utils/Monitor/MonitorTemplateCustomFieldUtil.test.ts +218 -0
- package/Tests/Utils/Monitor/NetworkTopologyEndpointAdoption.test.ts +1755 -0
- package/Tests/Utils/NetworkAutomation/AutoImportRunChain.test.ts +320 -0
- package/Types/BaseDatabase/IncludesAnyOfGroups.ts +77 -0
- package/Types/Dashboard/DashboardComponents/ComponentArgument.ts +1 -0
- package/Types/Dashboard/DashboardComponents/DashboardMonitorListComponent.ts +1 -0
- package/Types/Dashboard/DashboardVariable.ts +8 -0
- package/Types/JSON.ts +4 -0
- package/Types/JSONFunctions.ts +16 -0
- package/Types/Monitor/SnmpMonitor/NetworkTopology.ts +13 -0
- package/Types/NetworkAutomation/RuleRunResult.ts +402 -13
- package/Types/SerializableObjectDictionary.ts +4 -0
- package/Types/Telemetry/InventoryLiveness.ts +56 -0
- package/UI/Components/CSVFileUpload/CSVFileUpload.tsx +9 -2
- package/UI/Components/CustomFields/CustomFieldsDetail.tsx +46 -21
- package/UI/Components/Dashboard/DashboardVariableControl.tsx +285 -0
- package/UI/esbuild-config.js +6 -0
- package/Utils/Dashboard/Components/DashboardMonitorListComponent.ts +12 -1
- package/Utils/Dashboard/LabelVariable.ts +152 -0
- package/Utils/Dashboard/VariableUrlState.ts +27 -5
- package/Utils/MetricUnitUtil.ts +34 -0
- package/Utils/Monitor/DeviceMacLearningUtil.ts +177 -0
- package/Utils/Monitor/MetricValueFormatter.ts +342 -0
- package/Utils/Monitor/MonitorTemplateCustomFieldUtil.ts +124 -0
- package/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.ts +7 -2
- package/Utils/Monitor/NetworkTopologyUtil.ts +693 -0
- package/Utils/NetworkAutomation/AutoImportRunChain.ts +138 -0
- package/build/dist/Models/DatabaseModels/GlobalConfig.js +12 -12
- package/build/dist/Models/DatabaseModels/GlobalConfig.js.map +1 -1
- package/build/dist/Models/DatabaseModels/InventoryItem.js +34 -1
- package/build/dist/Models/DatabaseModels/InventoryItem.js.map +1 -1
- package/build/dist/Models/DatabaseModels/NetworkDevice.js +128 -0
- package/build/dist/Models/DatabaseModels/NetworkDevice.js.map +1 -1
- package/build/dist/Server/EnvironmentConfig.js +44 -12
- package/build/dist/Server/EnvironmentConfig.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1791700000000-AddMacAddressToNetworkDevice.js +14 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1791700000000-AddMacAddressToNetworkDevice.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/Infrastructure/Redis.js +8 -8
- package/build/dist/Server/Infrastructure/Redis.js.map +1 -1
- package/build/dist/Server/Infrastructure/Status.js +10 -4
- package/build/dist/Server/Infrastructure/Status.js.map +1 -1
- package/build/dist/Server/Services/DatabaseService.js +58 -13
- package/build/dist/Server/Services/DatabaseService.js.map +1 -1
- package/build/dist/Server/Services/InventoryItemService.js +28 -1
- package/build/dist/Server/Services/InventoryItemService.js.map +1 -1
- package/build/dist/Server/Services/MonitorTemplateService.js +116 -17
- package/build/dist/Server/Services/MonitorTemplateService.js.map +1 -1
- package/build/dist/Server/Services/NetworkDeviceAutoImportRuleEngineService.js +270 -37
- package/build/dist/Server/Services/NetworkDeviceAutoImportRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/NetworkDeviceDiscoveryScanService.js +65 -0
- package/build/dist/Server/Services/NetworkDeviceDiscoveryScanService.js.map +1 -1
- package/build/dist/Server/Services/NetworkDeviceService.js +54 -0
- package/build/dist/Server/Services/NetworkDeviceService.js.map +1 -1
- package/build/dist/Server/Services/UserNotificationRuleService.js +60 -25
- package/build/dist/Server/Services/UserNotificationRuleService.js.map +1 -1
- package/build/dist/Server/Types/Database/Permissions/ReadPermission.js +29 -4
- package/build/dist/Server/Types/Database/Permissions/ReadPermission.js.map +1 -1
- package/build/dist/Server/Types/Database/QueryUtil.js +36 -0
- package/build/dist/Server/Types/Database/QueryUtil.js.map +1 -1
- package/build/dist/Server/Types/Markdown.js +75 -0
- package/build/dist/Server/Types/Markdown.js.map +1 -1
- package/build/dist/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.js +53 -18
- package/build/dist/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.js.map +1 -1
- package/build/dist/Server/Utils/EmailRollup/EmailRollupFlushRunner.js +10 -1
- package/build/dist/Server/Utils/EmailRollup/EmailRollupFlushRunner.js.map +1 -1
- package/build/dist/Server/Utils/EmailRollup/EmailRollupRenderer.js +2 -2
- package/build/dist/Server/Utils/EmailRollup/EmailRollupRenderer.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/CompareCriteria.js +58 -35
- package/build/dist/Server/Utils/Monitor/Criteria/CompareCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.js +29 -5
- package/build/dist/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +166 -35
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaExpectationBuilder.js +58 -9
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaExpectationBuilder.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaMessageBuilder.js +9 -3
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaMessageBuilder.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaMessageFormatter.js +26 -12
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaMessageFormatter.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.js +74 -33
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/NetworkDeviceMacLearningUtil.js +114 -0
- package/build/dist/Server/Utils/Monitor/NetworkDeviceMacLearningUtil.js.map +1 -0
- package/build/dist/Server/Utils/Monitor/NetworkInventoryUtil.js +25 -0
- package/build/dist/Server/Utils/Monitor/NetworkInventoryUtil.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/EntityRegistry.js +9 -1
- package/build/dist/Server/Utils/Telemetry/EntityRegistry.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/TelemetryEntity.js +70 -13
- package/build/dist/Server/Utils/Telemetry/TelemetryEntity.js.map +1 -1
- package/build/dist/Types/BaseDatabase/IncludesAnyOfGroups.js +54 -0
- package/build/dist/Types/BaseDatabase/IncludesAnyOfGroups.js.map +1 -0
- package/build/dist/Types/Dashboard/DashboardComponents/ComponentArgument.js +1 -0
- package/build/dist/Types/Dashboard/DashboardComponents/ComponentArgument.js.map +1 -1
- package/build/dist/Types/Dashboard/DashboardVariable.js +1 -0
- package/build/dist/Types/Dashboard/DashboardVariable.js.map +1 -1
- package/build/dist/Types/JSON.js +1 -0
- package/build/dist/Types/JSON.js.map +1 -1
- package/build/dist/Types/JSONFunctions.js +13 -0
- package/build/dist/Types/JSONFunctions.js.map +1 -1
- package/build/dist/Types/NetworkAutomation/RuleRunResult.js +291 -6
- package/build/dist/Types/NetworkAutomation/RuleRunResult.js.map +1 -1
- package/build/dist/Types/SerializableObjectDictionary.js +4 -0
- package/build/dist/Types/SerializableObjectDictionary.js.map +1 -1
- package/build/dist/Types/Telemetry/InventoryLiveness.js +37 -0
- package/build/dist/Types/Telemetry/InventoryLiveness.js.map +1 -0
- package/build/dist/UI/Components/CSVFileUpload/CSVFileUpload.js +7 -0
- package/build/dist/UI/Components/CSVFileUpload/CSVFileUpload.js.map +1 -1
- package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js +9 -2
- package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js.map +1 -1
- package/build/dist/UI/Components/Dashboard/DashboardVariableControl.js +137 -0
- package/build/dist/UI/Components/Dashboard/DashboardVariableControl.js.map +1 -0
- package/build/dist/Utils/Dashboard/Components/DashboardMonitorListComponent.js +9 -1
- package/build/dist/Utils/Dashboard/Components/DashboardMonitorListComponent.js.map +1 -1
- package/build/dist/Utils/Dashboard/LabelVariable.js +96 -0
- package/build/dist/Utils/Dashboard/LabelVariable.js.map +1 -0
- package/build/dist/Utils/Dashboard/VariableUrlState.js +19 -4
- package/build/dist/Utils/Dashboard/VariableUrlState.js.map +1 -1
- package/build/dist/Utils/MetricUnitUtil.js +26 -0
- package/build/dist/Utils/MetricUnitUtil.js.map +1 -1
- package/build/dist/Utils/Monitor/DeviceMacLearningUtil.js +112 -0
- package/build/dist/Utils/Monitor/DeviceMacLearningUtil.js.map +1 -0
- package/build/dist/Utils/Monitor/MetricValueFormatter.js +276 -0
- package/build/dist/Utils/Monitor/MetricValueFormatter.js.map +1 -0
- package/build/dist/Utils/Monitor/MonitorTemplateCustomFieldUtil.js +101 -0
- package/build/dist/Utils/Monitor/MonitorTemplateCustomFieldUtil.js.map +1 -0
- package/build/dist/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.js +7 -2
- package/build/dist/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.js.map +1 -1
- package/build/dist/Utils/Monitor/NetworkTopologyUtil.js +484 -0
- package/build/dist/Utils/Monitor/NetworkTopologyUtil.js.map +1 -1
- package/build/dist/Utils/NetworkAutomation/AutoImportRunChain.js +59 -0
- package/build/dist/Utils/NetworkAutomation/AutoImportRunChain.js.map +1 -0
- package/jest.config.json +2 -0
- package/package.json +1 -1
- package/test-setup.sh +9 -9
- package/tsconfig.json +51 -11
- package/Tests/App/Dashboard/NotificationEmailPreferences.test.tsx +0 -399
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
import InventoryItem, {
|
|
2
|
+
EntityAttributes,
|
|
3
|
+
ExtractedEntity,
|
|
4
|
+
ResourceEntityRef,
|
|
5
|
+
} from "../../../../Server/Utils/Telemetry/TelemetryEntity";
|
|
6
|
+
import EntityType from "../../../../Types/Telemetry/EntityType";
|
|
7
|
+
import { describe, expect, test } from "@jest/globals";
|
|
8
|
+
|
|
9
|
+
/*
|
|
10
|
+
* Regression suite for duplicate Inventory items.
|
|
11
|
+
*
|
|
12
|
+
* `InventoryItem` has a unique index on (projectId, entityType, entityKey),
|
|
13
|
+
* so two rows of the same type in one project can only mean one thing: the
|
|
14
|
+
* same real object hashed to two different keys. Nothing downstream can undo
|
|
15
|
+
* that — the two rows are, as far as the registry is concerned, two things —
|
|
16
|
+
* and because both keep being observed, neither ever ages out. The Inventory
|
|
17
|
+
* list then shows a node, pod or service twice, both marked Discovered, both
|
|
18
|
+
* with the same name, because the display name is derived from the attributes
|
|
19
|
+
* the two identities SHARE rather than the ones that differ.
|
|
20
|
+
*
|
|
21
|
+
* The rule these tests exist to hold: an entity's identity must depend only on
|
|
22
|
+
* WHICH OBJECT is being described, never on WHICH PIPELINE is describing it.
|
|
23
|
+
* Every case below is a pair of resources that our own shipped configuration
|
|
24
|
+
* produces for one object, differing only in what the producing collector was
|
|
25
|
+
* able to resolve.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
const PROJECT: string = "proj-dup-1";
|
|
29
|
+
|
|
30
|
+
function entitiesFor(
|
|
31
|
+
attrs: EntityAttributes,
|
|
32
|
+
entityRefs?: Array<ResourceEntityRef>,
|
|
33
|
+
): Array<ExtractedEntity> {
|
|
34
|
+
return InventoryItem.extractEntities({
|
|
35
|
+
projectId: PROJECT,
|
|
36
|
+
attributes: attrs,
|
|
37
|
+
...(entityRefs ? { entityRefs } : {}),
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function entityOfType(
|
|
42
|
+
attrs: EntityAttributes,
|
|
43
|
+
type: EntityType,
|
|
44
|
+
): ExtractedEntity | undefined {
|
|
45
|
+
return entitiesFor(attrs).find((e: ExtractedEntity) => {
|
|
46
|
+
return e.entityType === type;
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function keyOfType(attrs: EntityAttributes, type: EntityType): string {
|
|
51
|
+
const entity: ExtractedEntity | undefined = entityOfType(attrs, type);
|
|
52
|
+
expect(entity).toBeDefined();
|
|
53
|
+
return entity!.entityKey;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const CLUSTER: string = "prod-us";
|
|
57
|
+
|
|
58
|
+
/*
|
|
59
|
+
* The two collectors the `kubernetes-agent` chart installs, reduced to the
|
|
60
|
+
* resource attributes each actually carries for the SAME node.
|
|
61
|
+
*
|
|
62
|
+
* Deployment / metrics-collector: the `k8s_cluster` receiver reports node
|
|
63
|
+
* metrics (`k8s.node.condition_ready`, allocatable cpu/memory/storage) and
|
|
64
|
+
* resolves node uids from the Kubernetes API.
|
|
65
|
+
*
|
|
66
|
+
* DaemonSet / node-collector: kubeletstats, hostmetrics, the cAdvisor scrape
|
|
67
|
+
* and the pod filelog receiver. None of them can produce a node uid — the
|
|
68
|
+
* kubelet summary API has no such field and `k8sattributes` is not asked to
|
|
69
|
+
* extract one — so the chart stamps `k8s.node.name` from the downward API
|
|
70
|
+
* instead.
|
|
71
|
+
*/
|
|
72
|
+
const NODE_FROM_K8S_CLUSTER_RECEIVER: EntityAttributes = {
|
|
73
|
+
"k8s.cluster.name": CLUSTER,
|
|
74
|
+
"k8s.node.name": "ip-10-0-1-23.ec2.internal",
|
|
75
|
+
"k8s.node.uid": "a8170da5-c826-49b0-b48b-a35af366e689",
|
|
76
|
+
"service.name": `kubernetes-agent-${CLUSTER}`,
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const NODE_FROM_DAEMONSET: EntityAttributes = {
|
|
80
|
+
"k8s.cluster.name": CLUSTER,
|
|
81
|
+
"k8s.node.name": "ip-10-0-1-23.ec2.internal",
|
|
82
|
+
"service.name": `kubernetes-agent-${CLUSTER}`,
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
/*
|
|
86
|
+
* The same pod as seen by the deployment collector (k8sattributes resolves
|
|
87
|
+
* `k8s.pod.uid`) and by a pod-log record that only carries the pod name.
|
|
88
|
+
*/
|
|
89
|
+
const POD_WITH_UID: EntityAttributes = {
|
|
90
|
+
"k8s.cluster.name": CLUSTER,
|
|
91
|
+
"k8s.namespace.name": "shop",
|
|
92
|
+
"k8s.pod.name": "checkout-api-7d9f884c7d-q5bz2",
|
|
93
|
+
"k8s.pod.uid": "7c2f9dd0-8400-4c47-9c54-321ebeb9543b",
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const POD_WITHOUT_UID: EntityAttributes = {
|
|
97
|
+
"k8s.cluster.name": CLUSTER,
|
|
98
|
+
"k8s.namespace.name": "shop",
|
|
99
|
+
"k8s.pod.name": "checkout-api-7d9f884c7d-q5bz2",
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
describe("one object, one identity, whichever collector saw it", () => {
|
|
103
|
+
test("a node reported with a uid and the same node reported without one are one entity", () => {
|
|
104
|
+
expect(
|
|
105
|
+
keyOfType(NODE_FROM_K8S_CLUSTER_RECEIVER, EntityType.KubernetesNode),
|
|
106
|
+
).toBe(keyOfType(NODE_FROM_DAEMONSET, EntityType.KubernetesNode));
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test("a pod reported with a uid and the same pod reported without one are one entity", () => {
|
|
110
|
+
expect(keyOfType(POD_WITH_UID, EntityType.KubernetesPod)).toBe(
|
|
111
|
+
keyOfType(POD_WITHOUT_UID, EntityType.KubernetesPod),
|
|
112
|
+
);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test("node identity is the cluster plus the node name, and nothing else", () => {
|
|
116
|
+
expect(
|
|
117
|
+
entityOfType(NODE_FROM_K8S_CLUSTER_RECEIVER, EntityType.KubernetesNode)!
|
|
118
|
+
.identifyingAttributes,
|
|
119
|
+
).toEqual({
|
|
120
|
+
"k8s.cluster.name": CLUSTER,
|
|
121
|
+
"k8s.node.name": "ip-10-0-1-23.ec2.internal",
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test("pod identity is the cluster, namespace and pod name, and nothing else", () => {
|
|
126
|
+
expect(
|
|
127
|
+
entityOfType(POD_WITH_UID, EntityType.KubernetesPod)!
|
|
128
|
+
.identifyingAttributes,
|
|
129
|
+
).toEqual({
|
|
130
|
+
"k8s.cluster.name": CLUSTER,
|
|
131
|
+
"k8s.namespace.name": "shop",
|
|
132
|
+
"k8s.pod.name": "checkout-api-7d9f884c7d-q5bz2",
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
test("the uid is still recorded, just not as identity", () => {
|
|
137
|
+
expect(
|
|
138
|
+
entityOfType(NODE_FROM_K8S_CLUSTER_RECEIVER, EntityType.KubernetesNode)!
|
|
139
|
+
.descriptiveAttributes,
|
|
140
|
+
).toMatchObject({
|
|
141
|
+
"k8s.node.uid": "a8170da5-c826-49b0-b48b-a35af366e689",
|
|
142
|
+
});
|
|
143
|
+
expect(
|
|
144
|
+
entityOfType(POD_WITH_UID, EntityType.KubernetesPod)!
|
|
145
|
+
.descriptiveAttributes,
|
|
146
|
+
).toMatchObject({
|
|
147
|
+
"k8s.pod.uid": "7c2f9dd0-8400-4c47-9c54-321ebeb9543b",
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
test("a pod that keeps its name across a restart keeps its inventory row", () => {
|
|
152
|
+
/*
|
|
153
|
+
* A StatefulSet pod is recreated under the same name with a fresh uid.
|
|
154
|
+
* Under uid identity that minted a new registry row per incarnation; the
|
|
155
|
+
* name is what an inventory is actually tracking.
|
|
156
|
+
*/
|
|
157
|
+
const before: string = keyOfType(POD_WITH_UID, EntityType.KubernetesPod);
|
|
158
|
+
const after: string = keyOfType(
|
|
159
|
+
{
|
|
160
|
+
...POD_WITH_UID,
|
|
161
|
+
"k8s.pod.uid": "0000aaaa-1111-2222-3333-444455556666",
|
|
162
|
+
},
|
|
163
|
+
EntityType.KubernetesPod,
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
expect(after).toBe(before);
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
describe("identity that genuinely differs must stay different", () => {
|
|
171
|
+
test("the same node name in two clusters is two entities", () => {
|
|
172
|
+
expect(keyOfType(NODE_FROM_DAEMONSET, EntityType.KubernetesNode)).not.toBe(
|
|
173
|
+
keyOfType(
|
|
174
|
+
{ ...NODE_FROM_DAEMONSET, "k8s.cluster.name": "prod-eu" },
|
|
175
|
+
EntityType.KubernetesNode,
|
|
176
|
+
),
|
|
177
|
+
);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
test("the same pod name in two namespaces is two entities", () => {
|
|
181
|
+
expect(keyOfType(POD_WITHOUT_UID, EntityType.KubernetesPod)).not.toBe(
|
|
182
|
+
keyOfType(
|
|
183
|
+
{ ...POD_WITHOUT_UID, "k8s.namespace.name": "checkout" },
|
|
184
|
+
EntityType.KubernetesPod,
|
|
185
|
+
),
|
|
186
|
+
);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
test("the same pod name in two clusters is two entities", () => {
|
|
190
|
+
expect(keyOfType(POD_WITHOUT_UID, EntityType.KubernetesPod)).not.toBe(
|
|
191
|
+
keyOfType(
|
|
192
|
+
{ ...POD_WITHOUT_UID, "k8s.cluster.name": "prod-eu" },
|
|
193
|
+
EntityType.KubernetesPod,
|
|
194
|
+
),
|
|
195
|
+
);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
test("two different nodes in one cluster are two entities", () => {
|
|
199
|
+
expect(keyOfType(NODE_FROM_DAEMONSET, EntityType.KubernetesNode)).not.toBe(
|
|
200
|
+
keyOfType(
|
|
201
|
+
{
|
|
202
|
+
...NODE_FROM_DAEMONSET,
|
|
203
|
+
"k8s.node.name": "ip-10-0-1-24.ec2.internal",
|
|
204
|
+
},
|
|
205
|
+
EntityType.KubernetesNode,
|
|
206
|
+
),
|
|
207
|
+
);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
test("a uid-only node and a named node are not silently merged", () => {
|
|
211
|
+
/*
|
|
212
|
+
* Nothing can prove these are the same object from the resource alone, so
|
|
213
|
+
* they stay distinct. The uid-only shape exists only for a producer that
|
|
214
|
+
* carries no name at all.
|
|
215
|
+
*/
|
|
216
|
+
expect(
|
|
217
|
+
keyOfType(
|
|
218
|
+
{ "k8s.cluster.name": CLUSTER, "k8s.node.uid": "node-uid-only" },
|
|
219
|
+
EntityType.KubernetesNode,
|
|
220
|
+
),
|
|
221
|
+
).not.toBe(keyOfType(NODE_FROM_DAEMONSET, EntityType.KubernetesNode));
|
|
222
|
+
});
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
describe("casing and whitespace do not fork identity", () => {
|
|
226
|
+
test.each([
|
|
227
|
+
["upper-cased node name", { "k8s.node.name": "IP-10-0-1-23.EC2.INTERNAL" }],
|
|
228
|
+
["padded node name", { "k8s.node.name": " ip-10-0-1-23.ec2.internal " }],
|
|
229
|
+
["upper-cased cluster", { "k8s.cluster.name": "PROD-US" }],
|
|
230
|
+
])(
|
|
231
|
+
"%s resolves to the same node entity",
|
|
232
|
+
(_label: string, override: EntityAttributes) => {
|
|
233
|
+
expect(
|
|
234
|
+
keyOfType(
|
|
235
|
+
{ ...NODE_FROM_DAEMONSET, ...override },
|
|
236
|
+
EntityType.KubernetesNode,
|
|
237
|
+
),
|
|
238
|
+
).toBe(keyOfType(NODE_FROM_DAEMONSET, EntityType.KubernetesNode));
|
|
239
|
+
},
|
|
240
|
+
);
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
describe("one resource describes at most one entity per type", () => {
|
|
244
|
+
/*
|
|
245
|
+
* A resource is one pod, on one node, in one cluster. `entity_refs` are
|
|
246
|
+
* producer-supplied and nothing stops a producer sending two refs of the
|
|
247
|
+
* same type on one resource; promoting both put two Inventory rows in from
|
|
248
|
+
* a single batch. The extra ref stays a membership key so no signal loses
|
|
249
|
+
* its queryable identity.
|
|
250
|
+
*/
|
|
251
|
+
const TWO_POD_REFS: Array<ResourceEntityRef> = [
|
|
252
|
+
{ type: "k8s.pod", idKeys: ["k8s.pod.name"] },
|
|
253
|
+
{ type: "k8s.pod", idKeys: ["k8s.pod.uid"] },
|
|
254
|
+
];
|
|
255
|
+
|
|
256
|
+
test("only the first ref of a type is promoted to a registry row", () => {
|
|
257
|
+
const pods: Array<ExtractedEntity> = entitiesFor(
|
|
258
|
+
POD_WITH_UID,
|
|
259
|
+
TWO_POD_REFS,
|
|
260
|
+
).filter((e: ExtractedEntity) => {
|
|
261
|
+
return e.entityType === EntityType.KubernetesPod;
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
expect(pods).toHaveLength(2);
|
|
265
|
+
expect(
|
|
266
|
+
pods.filter((p: ExtractedEntity) => {
|
|
267
|
+
return !p.membershipOnly;
|
|
268
|
+
}),
|
|
269
|
+
).toHaveLength(1);
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
test("the demoted ref keeps its key so signals stay queryable by it", () => {
|
|
273
|
+
const pods: Array<ExtractedEntity> = entitiesFor(
|
|
274
|
+
POD_WITH_UID,
|
|
275
|
+
TWO_POD_REFS,
|
|
276
|
+
).filter((e: ExtractedEntity) => {
|
|
277
|
+
return e.entityType === EntityType.KubernetesPod;
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
const keys: Array<string> = pods.map((p: ExtractedEntity) => {
|
|
281
|
+
return p.entityKey;
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
expect(new Set(keys).size).toBe(2);
|
|
285
|
+
expect(
|
|
286
|
+
InventoryItem.extractEntityKeys({
|
|
287
|
+
projectId: PROJECT,
|
|
288
|
+
attributes: POD_WITH_UID,
|
|
289
|
+
entityRefs: TWO_POD_REFS,
|
|
290
|
+
}),
|
|
291
|
+
).toEqual(expect.arrayContaining(keys));
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
test("refs of different types are all promoted", () => {
|
|
295
|
+
const entities: Array<ExtractedEntity> = entitiesFor(POD_WITH_UID, [
|
|
296
|
+
{ type: "k8s.pod", idKeys: ["k8s.pod.name"] },
|
|
297
|
+
{ type: "k8s.namespace", idKeys: ["k8s.namespace.name"] },
|
|
298
|
+
]);
|
|
299
|
+
|
|
300
|
+
expect(
|
|
301
|
+
entities.filter((e: ExtractedEntity) => {
|
|
302
|
+
return !e.membershipOnly;
|
|
303
|
+
}),
|
|
304
|
+
).toHaveLength(2);
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
test("the heuristic resolvers already emit one entity per type", () => {
|
|
308
|
+
const types: Array<EntityType> = entitiesFor(POD_WITH_UID).map(
|
|
309
|
+
(e: ExtractedEntity) => {
|
|
310
|
+
return e.entityType;
|
|
311
|
+
},
|
|
312
|
+
);
|
|
313
|
+
|
|
314
|
+
expect(new Set(types).size).toBe(types.length);
|
|
315
|
+
});
|
|
316
|
+
});
|
|
@@ -750,7 +750,7 @@ describe("descriptive attributes & labels (never identity-bearing)", () => {
|
|
|
750
750
|
name: "worker-1",
|
|
751
751
|
},
|
|
752
752
|
])(
|
|
753
|
-
"k8s.$label:
|
|
753
|
+
"k8s.$label: keys on the name and carries the UID descriptively",
|
|
754
754
|
({
|
|
755
755
|
type,
|
|
756
756
|
uidKey,
|
|
@@ -765,19 +765,122 @@ describe("descriptive attributes & labels (never identity-bearing)", () => {
|
|
|
765
765
|
nameKey: string;
|
|
766
766
|
name: string;
|
|
767
767
|
}) => {
|
|
768
|
-
const uidOnly: ExtractedEntity | undefined = entityOfType(
|
|
769
|
-
{ [uidKey]: uid },
|
|
770
|
-
type,
|
|
771
|
-
);
|
|
772
768
|
const named: ExtractedEntity | undefined = entityOfType(
|
|
773
769
|
{ [uidKey]: uid, [nameKey]: name },
|
|
774
770
|
type,
|
|
775
771
|
);
|
|
776
772
|
|
|
777
773
|
expect(named).toBeDefined();
|
|
778
|
-
expect(named!.
|
|
779
|
-
|
|
780
|
-
|
|
774
|
+
expect(named!.identifyingAttributes).toEqual({ [nameKey]: name });
|
|
775
|
+
/*
|
|
776
|
+
* The name is carried descriptively as well as identifying it. That is
|
|
777
|
+
* deliberate: `buildDescriptiveUpdate` reads the display name out of
|
|
778
|
+
* the descriptive bag first, so a row whose identity later changes
|
|
779
|
+
* shape still has a name to show.
|
|
780
|
+
*/
|
|
781
|
+
expect(named!.descriptiveAttributes).toEqual({
|
|
782
|
+
[nameKey]: name,
|
|
783
|
+
[uidKey]: uid,
|
|
784
|
+
});
|
|
785
|
+
},
|
|
786
|
+
);
|
|
787
|
+
|
|
788
|
+
/*
|
|
789
|
+
* The regression test for duplicate Inventory rows.
|
|
790
|
+
*
|
|
791
|
+
* The shipped Kubernetes agent runs two collectors over the same nodes and
|
|
792
|
+
* pods. The Deployment's `k8s_cluster` receiver resolves uids; the
|
|
793
|
+
* DaemonSet's kubeletstats / hostmetrics / cAdvisor / filelog receivers
|
|
794
|
+
* never do. Identity must not depend on which of them is talking.
|
|
795
|
+
*/
|
|
796
|
+
test.each([
|
|
797
|
+
{
|
|
798
|
+
label: "node",
|
|
799
|
+
type: EntityType.KubernetesNode,
|
|
800
|
+
withUid: {
|
|
801
|
+
"k8s.cluster.name": "prod-us",
|
|
802
|
+
"k8s.node.name": "worker-1",
|
|
803
|
+
"k8s.node.uid": "node-uid-1",
|
|
804
|
+
},
|
|
805
|
+
withoutUid: {
|
|
806
|
+
"k8s.cluster.name": "prod-us",
|
|
807
|
+
"k8s.node.name": "worker-1",
|
|
808
|
+
},
|
|
809
|
+
},
|
|
810
|
+
{
|
|
811
|
+
label: "pod",
|
|
812
|
+
type: EntityType.KubernetesPod,
|
|
813
|
+
withUid: {
|
|
814
|
+
"k8s.cluster.name": "prod-us",
|
|
815
|
+
"k8s.namespace.name": "shop",
|
|
816
|
+
"k8s.pod.name": "checkout-7d9f",
|
|
817
|
+
"k8s.pod.uid": "pod-uid-1",
|
|
818
|
+
},
|
|
819
|
+
withoutUid: {
|
|
820
|
+
"k8s.cluster.name": "prod-us",
|
|
821
|
+
"k8s.namespace.name": "shop",
|
|
822
|
+
"k8s.pod.name": "checkout-7d9f",
|
|
823
|
+
},
|
|
824
|
+
},
|
|
825
|
+
])(
|
|
826
|
+
"k8s.$label: a producer that resolves the UID and one that does not agree on identity",
|
|
827
|
+
({
|
|
828
|
+
type,
|
|
829
|
+
withUid,
|
|
830
|
+
withoutUid,
|
|
831
|
+
}: {
|
|
832
|
+
label: string;
|
|
833
|
+
type: EntityType;
|
|
834
|
+
withUid: EntityAttributes;
|
|
835
|
+
withoutUid: EntityAttributes;
|
|
836
|
+
}) => {
|
|
837
|
+
const resolved: ExtractedEntity | undefined = entityOfType(withUid, type);
|
|
838
|
+
const unresolved: ExtractedEntity | undefined = entityOfType(
|
|
839
|
+
withoutUid,
|
|
840
|
+
type,
|
|
841
|
+
);
|
|
842
|
+
|
|
843
|
+
expect(resolved).toBeDefined();
|
|
844
|
+
expect(unresolved).toBeDefined();
|
|
845
|
+
expect(resolved!.entityKey).toBe(unresolved!.entityKey);
|
|
846
|
+
expect(resolved!.identifyingAttributes).toEqual(
|
|
847
|
+
unresolved!.identifyingAttributes,
|
|
848
|
+
);
|
|
849
|
+
},
|
|
850
|
+
);
|
|
851
|
+
|
|
852
|
+
test.each([
|
|
853
|
+
{
|
|
854
|
+
label: "node",
|
|
855
|
+
type: EntityType.KubernetesNode,
|
|
856
|
+
uidKey: "k8s.node.uid",
|
|
857
|
+
uid: "node-uid-1",
|
|
858
|
+
},
|
|
859
|
+
{
|
|
860
|
+
label: "pod",
|
|
861
|
+
type: EntityType.KubernetesPod,
|
|
862
|
+
uidKey: "k8s.pod.uid",
|
|
863
|
+
uid: "pod-uid-1",
|
|
864
|
+
},
|
|
865
|
+
])(
|
|
866
|
+
"k8s.$label: falls back to the UID when the resource carries no name",
|
|
867
|
+
({
|
|
868
|
+
type,
|
|
869
|
+
uidKey,
|
|
870
|
+
uid,
|
|
871
|
+
}: {
|
|
872
|
+
label: string;
|
|
873
|
+
type: EntityType;
|
|
874
|
+
uidKey: string;
|
|
875
|
+
uid: string;
|
|
876
|
+
}) => {
|
|
877
|
+
const entity: ExtractedEntity | undefined = entityOfType(
|
|
878
|
+
{ [uidKey]: uid },
|
|
879
|
+
type,
|
|
880
|
+
);
|
|
881
|
+
|
|
882
|
+
expect(entity).toBeDefined();
|
|
883
|
+
expect(entity!.identifyingAttributes).toEqual({ [uidKey]: uid });
|
|
781
884
|
},
|
|
782
885
|
);
|
|
783
886
|
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import IncludesAnyOfGroups from "../../../Types/BaseDatabase/IncludesAnyOfGroups";
|
|
2
|
+
import BadDataException from "../../../Types/Exception/BadDataException";
|
|
3
|
+
import { JSONObject, ObjectType } from "../../../Types/JSON";
|
|
4
|
+
import JSONFunctions from "../../../Types/JSONFunctions";
|
|
5
|
+
import SerializableObjectDictionary from "../../../Types/SerializableObjectDictionary";
|
|
6
|
+
|
|
7
|
+
describe("IncludesAnyOfGroups", () => {
|
|
8
|
+
test("preserves group boundaries and removes duplicate ids within a group", () => {
|
|
9
|
+
const value: IncludesAnyOfGroups = new IncludesAnyOfGroups([
|
|
10
|
+
["network", "router", "network"],
|
|
11
|
+
["0660", "0661"],
|
|
12
|
+
]);
|
|
13
|
+
expect(value.groups).toEqual([
|
|
14
|
+
["network", "router"],
|
|
15
|
+
["0660", "0661"],
|
|
16
|
+
]);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
test("retains empty groups so they can match nothing", () => {
|
|
20
|
+
expect(new IncludesAnyOfGroups([["network"], []]).groups).toEqual([
|
|
21
|
+
["network"],
|
|
22
|
+
[],
|
|
23
|
+
]);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test("owns its data and does not expose mutable validated groups", () => {
|
|
27
|
+
const groups: Array<Array<string>> = [["network"], ["0660"]];
|
|
28
|
+
const value: IncludesAnyOfGroups = new IncludesAnyOfGroups(groups);
|
|
29
|
+
groups[0]!.push("router");
|
|
30
|
+
value.groups[1]!.push("0661");
|
|
31
|
+
(value.toJSON()["value"] as Array<Array<string>>)[0]!.push("other");
|
|
32
|
+
expect(value.groups).toEqual([["network"], ["0660"]]);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test("round trips through the API JSON registry with leading zeros intact", () => {
|
|
36
|
+
const value: IncludesAnyOfGroups = new IncludesAnyOfGroups([
|
|
37
|
+
["network"],
|
|
38
|
+
["0660", "0661"],
|
|
39
|
+
]);
|
|
40
|
+
const serialized: JSONObject = JSONFunctions.serialize({ labels: value });
|
|
41
|
+
expect(serialized["labels"]).toEqual({
|
|
42
|
+
_type: ObjectType.IncludesAnyOfGroups,
|
|
43
|
+
value: [["network"], ["0660", "0661"]],
|
|
44
|
+
});
|
|
45
|
+
const result: JSONObject = JSONFunctions.deserialize(
|
|
46
|
+
JSON.parse(JSON.stringify(serialized)),
|
|
47
|
+
) as JSONObject;
|
|
48
|
+
expect(SerializableObjectDictionary[ObjectType.IncludesAnyOfGroups]).toBe(
|
|
49
|
+
IncludesAnyOfGroups,
|
|
50
|
+
);
|
|
51
|
+
expect(result["labels"]).toBeInstanceOf(IncludesAnyOfGroups);
|
|
52
|
+
expect((result["labels"] as IncludesAnyOfGroups).groups).toEqual(
|
|
53
|
+
value.groups,
|
|
54
|
+
);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test.each(
|
|
58
|
+
[
|
|
59
|
+
undefined,
|
|
60
|
+
null,
|
|
61
|
+
[],
|
|
62
|
+
"network",
|
|
63
|
+
["network"],
|
|
64
|
+
[null],
|
|
65
|
+
[[null]],
|
|
66
|
+
[[1]],
|
|
67
|
+
[[{}]],
|
|
68
|
+
[[""]],
|
|
69
|
+
[["a".repeat(257)]],
|
|
70
|
+
Array.from({ length: 33 }, () => {
|
|
71
|
+
return ["id"];
|
|
72
|
+
}),
|
|
73
|
+
[
|
|
74
|
+
Array.from({ length: 1001 }, () => {
|
|
75
|
+
return "id";
|
|
76
|
+
}),
|
|
77
|
+
],
|
|
78
|
+
Array.from({ length: 11 }, () => {
|
|
79
|
+
return Array.from({ length: 1000 }, () => {
|
|
80
|
+
return "id";
|
|
81
|
+
});
|
|
82
|
+
}),
|
|
83
|
+
].map((groups: unknown) => {
|
|
84
|
+
return [groups];
|
|
85
|
+
}),
|
|
86
|
+
)("rejects invalid or oversized group data %#", (groups: unknown) => {
|
|
87
|
+
expect(() => {
|
|
88
|
+
return new IncludesAnyOfGroups(groups as Array<Array<string>>);
|
|
89
|
+
}).toThrow(BadDataException);
|
|
90
|
+
expect(() => {
|
|
91
|
+
return IncludesAnyOfGroups.fromJSON({
|
|
92
|
+
_type: ObjectType.IncludesAnyOfGroups,
|
|
93
|
+
value: groups,
|
|
94
|
+
} as JSONObject);
|
|
95
|
+
}).toThrow(BadDataException);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
test("rejects a different operator type", () => {
|
|
99
|
+
expect(() => {
|
|
100
|
+
return IncludesAnyOfGroups.fromJSON({
|
|
101
|
+
_type: ObjectType.Includes,
|
|
102
|
+
value: [["network"]],
|
|
103
|
+
});
|
|
104
|
+
}).toThrow(BadDataException);
|
|
105
|
+
});
|
|
106
|
+
});
|
|
@@ -484,6 +484,46 @@ describe("JSONFunctions Class", () => {
|
|
|
484
484
|
}).toThrow("Expected JSONObject, but got JSONArray");
|
|
485
485
|
});
|
|
486
486
|
|
|
487
|
+
/*
|
|
488
|
+
* An array was once the only shape refused, so every other non-object -
|
|
489
|
+
* all of them valid JSON - came back typed as a JSONObject and was read
|
|
490
|
+
* as one. The two callers that matter both did exactly that: the Text to
|
|
491
|
+
* JSON workflow component announced a bare number as a successful parse,
|
|
492
|
+
* and JSONWebToken.decodeJsonPayload took a string payload and read
|
|
493
|
+
* undefined out of every field of it.
|
|
494
|
+
*/
|
|
495
|
+
test("parseJSONObject throws when given a bare number", () => {
|
|
496
|
+
expect(() => {
|
|
497
|
+
return JSONFunctions.parseJSONObject("42");
|
|
498
|
+
}).toThrow("Expected JSONObject, but got number");
|
|
499
|
+
});
|
|
500
|
+
|
|
501
|
+
test("parseJSONObject throws when given a bare string", () => {
|
|
502
|
+
expect(() => {
|
|
503
|
+
return JSONFunctions.parseJSONObject('"just a string"');
|
|
504
|
+
}).toThrow("Expected JSONObject, but got string");
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
test("parseJSONObject throws when given a bare boolean", () => {
|
|
508
|
+
expect(() => {
|
|
509
|
+
return JSONFunctions.parseJSONObject("true");
|
|
510
|
+
}).toThrow("Expected JSONObject, but got boolean");
|
|
511
|
+
});
|
|
512
|
+
|
|
513
|
+
test("parseJSONObject throws when given null", () => {
|
|
514
|
+
expect(() => {
|
|
515
|
+
return JSONFunctions.parseJSONObject("null");
|
|
516
|
+
}).toThrow("Expected JSONObject, but got null");
|
|
517
|
+
});
|
|
518
|
+
|
|
519
|
+
test("parseJSONObject still accepts an empty object", () => {
|
|
520
|
+
expect(JSONFunctions.parseJSONObject("{}")).toEqual({});
|
|
521
|
+
});
|
|
522
|
+
|
|
523
|
+
test("parseJSONObject still accepts JSON5 unquoted keys", () => {
|
|
524
|
+
expect(JSONFunctions.parseJSONObject("{ a: 1 }")).toEqual({ a: 1 });
|
|
525
|
+
});
|
|
526
|
+
|
|
487
527
|
test("parseJSONArray returns an array", () => {
|
|
488
528
|
expect(JSONFunctions.parseJSONArray("[1, 2]")).toEqual([1, 2]);
|
|
489
529
|
});
|