@oneuptime/common 12.0.30 → 12.0.31
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/AnalyticsModels/SecurityEvent.ts +48 -16
- package/Models/AnalyticsModels/ThreatIntelIndicator.ts +10 -5
- package/Models/DatabaseModels/AlertSeverity.ts +44 -0
- package/Models/DatabaseModels/DetectionRule.ts +16 -5
- package/Models/DatabaseModels/GoogleSecOpsConnection.ts +14 -5
- package/Models/DatabaseModels/IncidentSeverity.ts +44 -0
- package/Models/DatabaseModels/Index.ts +2 -0
- package/Models/DatabaseModels/NetworkDevice.ts +113 -3
- package/Models/DatabaseModels/NetworkDeviceAutoImportRule.ts +88 -0
- package/Models/DatabaseModels/NetworkDeviceDiscoveryScan.ts +22 -0
- package/Models/DatabaseModels/NetworkDeviceOidTemplate.ts +499 -0
- package/Models/DatabaseModels/ThreatIntelFeed.ts +16 -5
- package/Server/API/GlobalConfigAPI.ts +362 -183
- package/Server/API/TelemetryAPI.ts +42 -4
- package/Server/EnvironmentConfig.ts +112 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1790500000000-AddNetworkDeviceOidTemplate.ts +59 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1790600000000-AddAutoImportRuleOidTemplate.ts +25 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +4 -0
- package/Server/Middleware/MultipartFormData.ts +118 -32
- package/Server/Services/AlertEpisodeLabelRuleEngineService.ts +9 -1
- package/Server/Services/AlertEpisodeOnCallRuleEngineService.ts +9 -1
- package/Server/Services/AlertEpisodeOwnerRuleEngineService.ts +9 -1
- package/Server/Services/AlertEpisodePrivacyRuleEngineService.ts +9 -1
- package/Server/Services/AlertGroupingEngineService.ts +9 -1
- package/Server/Services/AlertLabelRuleEngineService.ts +9 -1
- package/Server/Services/AlertOnCallRuleEngineService.ts +9 -1
- package/Server/Services/AlertOwnerRuleEngineService.ts +9 -1
- package/Server/Services/AlertPrivacyRuleEngineService.ts +9 -1
- package/Server/Services/AlertReminderRuleService.ts +9 -1
- package/Server/Services/AutoRemediationRuleEngineService.ts +9 -1
- package/Server/Services/CephClusterLabelRuleEngineService.ts +9 -1
- package/Server/Services/CephClusterOwnerRuleEngineService.ts +9 -1
- package/Server/Services/CloudResourceLabelRuleEngineService.ts +9 -1
- package/Server/Services/CloudResourceOwnerRuleEngineService.ts +9 -1
- package/Server/Services/DashboardLabelRuleEngineService.ts +9 -1
- package/Server/Services/DashboardOwnerRuleEngineService.ts +9 -1
- package/Server/Services/DockerHostLabelRuleEngineService.ts +9 -1
- package/Server/Services/DockerHostOwnerRuleEngineService.ts +9 -1
- package/Server/Services/DockerSwarmClusterLabelRuleEngineService.ts +9 -1
- package/Server/Services/DockerSwarmClusterOwnerRuleEngineService.ts +9 -1
- package/Server/Services/HostLabelRuleEngineService.ts +9 -1
- package/Server/Services/HostOwnerRuleEngineService.ts +9 -1
- package/Server/Services/IncidentEpisodeLabelRuleEngineService.ts +9 -1
- package/Server/Services/IncidentEpisodeOnCallRuleEngineService.ts +9 -1
- package/Server/Services/IncidentEpisodeOwnerRuleEngineService.ts +9 -1
- package/Server/Services/IncidentEpisodePrivacyRuleEngineService.ts +9 -1
- package/Server/Services/IncidentGroupingEngineService.ts +9 -1
- package/Server/Services/IncidentLabelRuleEngineService.ts +9 -1
- package/Server/Services/IncidentOnCallRuleEngineService.ts +9 -1
- package/Server/Services/IncidentOwnerRuleEngineService.ts +9 -1
- package/Server/Services/IncidentPrivacyRuleEngineService.ts +9 -1
- package/Server/Services/IncidentReminderRuleService.ts +9 -1
- package/Server/Services/IncidentSlaRuleService.ts +9 -1
- package/Server/Services/IncomingCallPolicyLabelRuleEngineService.ts +9 -1
- package/Server/Services/IncomingCallPolicyOwnerRuleEngineService.ts +9 -1
- package/Server/Services/Index.ts +2 -0
- package/Server/Services/IoTFleetLabelRuleEngineService.ts +9 -1
- package/Server/Services/IoTFleetOwnerRuleEngineService.ts +9 -1
- package/Server/Services/KubernetesClusterLabelRuleEngineService.ts +9 -1
- package/Server/Services/KubernetesClusterOwnerRuleEngineService.ts +9 -1
- package/Server/Services/KubernetesResourceService.ts +5 -1
- package/Server/Services/MonitorLabelRuleEngineService.ts +9 -1
- package/Server/Services/MonitorOwnerRuleEngineService.ts +9 -1
- package/Server/Services/NetworkDeviceAutoImportRuleEngineService.ts +35 -0
- package/Server/Services/NetworkDeviceAutoImportRuleService.ts +96 -0
- package/Server/Services/NetworkDeviceLabelRuleEngineService.ts +9 -1
- package/Server/Services/NetworkDeviceOidTemplateService.ts +201 -0
- package/Server/Services/NetworkDeviceOwnerRuleEngineService.ts +9 -1
- package/Server/Services/NetworkDeviceService.ts +209 -1
- package/Server/Services/OnCallDutyPolicyLabelRuleEngineService.ts +9 -1
- package/Server/Services/OnCallDutyPolicyOwnerRuleEngineService.ts +9 -1
- package/Server/Services/OnCallDutyPolicyScheduleLabelRuleEngineService.ts +9 -1
- package/Server/Services/OnCallDutyPolicyScheduleOwnerRuleEngineService.ts +9 -1
- package/Server/Services/PodmanHostLabelRuleEngineService.ts +9 -1
- package/Server/Services/PodmanHostOwnerRuleEngineService.ts +9 -1
- package/Server/Services/ProxmoxClusterLabelRuleEngineService.ts +9 -1
- package/Server/Services/ProxmoxClusterOwnerRuleEngineService.ts +9 -1
- package/Server/Services/RumApplicationLabelRuleEngineService.ts +9 -1
- package/Server/Services/RumApplicationOwnerRuleEngineService.ts +9 -1
- package/Server/Services/RunbookLabelRuleEngineService.ts +9 -1
- package/Server/Services/RunbookOwnerRuleEngineService.ts +9 -1
- package/Server/Services/RunbookRuleEngineService.ts +9 -1
- package/Server/Services/ScheduledMaintenanceLabelRuleEngineService.ts +9 -1
- package/Server/Services/ScheduledMaintenanceOwnerRuleEngineService.ts +9 -1
- package/Server/Services/ScheduledMaintenanceReminderRuleService.ts +9 -1
- package/Server/Services/ServerlessFunctionLabelRuleEngineService.ts +9 -1
- package/Server/Services/ServerlessFunctionOwnerRuleEngineService.ts +9 -1
- package/Server/Services/ServiceLabelRuleEngineService.ts +9 -1
- package/Server/Services/ServiceOwnerRuleEngineService.ts +9 -1
- package/Server/Services/StatusPageLabelRuleEngineService.ts +9 -1
- package/Server/Services/StatusPageMonitorRuleEngineService.ts +7 -0
- package/Server/Services/StatusPageOwnerRuleEngineService.ts +9 -1
- package/Server/Services/TelemetrySourceMapService.ts +266 -23
- package/Server/Services/UserService.ts +27 -0
- package/Server/Services/WorkflowLabelRuleEngineService.ts +9 -1
- package/Server/Services/WorkflowOwnerRuleEngineService.ts +9 -1
- package/Server/Utils/EnterpriseLicense/EnterpriseLicenseSeatUtil.ts +186 -0
- package/Server/Utils/Monitor/CapturedMetricAttributeUtil.ts +245 -0
- package/Server/Utils/Monitor/Criteria/SnmpMonitorCriteria.ts +21 -2
- package/Server/Utils/Monitor/MonitorMetricUtil.ts +55 -21
- package/Server/Utils/Monitor/MonitorTemplateUtil.ts +32 -0
- package/Server/Utils/Monitor/NetworkDeviceMetricUtil.ts +15 -2
- package/Server/Utils/Monitor/NetworkDeviceWalkUtil.ts +18 -1
- package/Server/Utils/Monitor/NetworkInventoryUtil.ts +10 -0
- package/Server/Utils/Monitor/SeriesResourceLabels.ts +34 -0
- package/Server/Utils/Rules/RuleEngineRuleRead.ts +48 -0
- package/Server/Utils/Telemetry/ExceptionSanitizer.ts +14 -1
- package/Server/Utils/Telemetry/LogExceptionExtractor.ts +14 -2
- package/Server/Utils/Telemetry/SourceMapResolver.ts +16 -5
- package/Server/Utils/Telemetry/StackTraceParser.ts +399 -0
- package/Tests/App/Dashboard/EventOverlayHook.test.tsx +125 -0
- package/Tests/App/Dashboard/HeaderImageAssets.test.tsx +93 -0
- package/Tests/App/Dashboard/OnCallReadinessSurfaces.test.tsx +3 -3
- package/Tests/App/Dashboard/UserElementEmail.test.tsx +12 -4
- package/Tests/App/Dashboard/UserEmailCallSites.test.tsx +1 -1
- package/Tests/Models/DatabaseModels/DomainRoleTierCoverage.test.ts +14 -0
- package/Tests/Models/DatabaseModels/ModelRegistryInvariants.test.ts +469 -0
- package/Tests/Models/SecurityDomainAccessControl.test.ts +429 -0
- package/Tests/Server/API/GlobalConfigLicense.test.ts +657 -0
- package/Tests/Server/API/SecurityEventAttributesAPI.test.ts +84 -6
- package/Tests/Server/API/TelemetryResolveStackTraceAPI.test.ts +805 -0
- package/Tests/Server/Infrastructure/Postgres/AddOnCallCalendarFeedsMigration.test.ts +32 -7
- package/Tests/Server/Middleware/IdentityRateLimit.test.ts +1144 -0
- package/Tests/Server/Middleware/MultipartFormData.test.ts +618 -3
- package/Tests/Server/Services/MonitorLabelRuleEngineScale.test.ts +520 -0
- package/Tests/Server/Services/NetworkDeviceOidTemplateService.test.ts +332 -0
- package/Tests/Server/Services/NetworkDeviceOidTemplateWriteGuards.test.ts +409 -0
- package/Tests/Server/Services/RuleEngineRuleFetchLimit.test.ts +239 -0
- package/Tests/Server/Services/TelemetrySourceMapService.test.ts +1210 -2
- package/Tests/Server/Services/UserServiceSeatLimit.test.ts +182 -0
- package/Tests/Server/Types/AnalyticsDatabase/SecurityEventModelPermission.test.ts +255 -0
- package/Tests/Server/Types/Database/Permissions/ReadBlockPermission.test.ts +212 -0
- package/Tests/Server/Types/Database/Permissions/SecurityRoleAccess.test.ts +413 -0
- package/Tests/Server/Utils/CodeRepository.test.ts +689 -0
- package/Tests/Server/Utils/EnterpriseLicense/EnterpriseLicenseSeatUtil.test.ts +495 -0
- package/Tests/Server/Utils/ExceptionSanitizer.test.ts +77 -0
- package/Tests/Server/Utils/Monitor/CapturedMetricAttributeUtil.test.ts +284 -0
- package/Tests/Server/Utils/Monitor/MonitorCustomMetricAttributes.test.ts +503 -0
- package/Tests/Server/Utils/Monitor/MonitorTemplatePrototypePollution.test.ts +106 -0
- package/Tests/Server/Utils/Monitor/NetworkDeviceWalkLogWritePath.test.ts +33 -4
- package/Tests/Server/Utils/Monitor/NetworkDeviceWalkUtil.test.ts +14 -4
- package/Tests/Server/Utils/Monitor/NetworkInventoryUtil.test.ts +44 -0
- package/Tests/Server/Utils/OpenAPISpec.test.ts +536 -0
- package/Tests/Server/Utils/Rules/RuleEngineRuleRead.test.ts +115 -0
- package/Tests/Server/Utils/StackTraceParser.test.ts +610 -0
- package/Tests/Server/Utils/Telemetry/LogExceptionExtractor.test.ts +0 -0
- package/Tests/Server/Utils/Telemetry/SourceMapLimits.test.ts +656 -0
- package/Tests/Server/Utils/Telemetry/SourceMapResolver.test.ts +100 -0
- package/Tests/Types/Monitor/SnmpMonitor/SnmpOidListUtil.test.ts +454 -0
- package/Tests/Types/SecurityRolePermissions.test.ts +193 -0
- package/Tests/UI/Components/Charts/AnnotationLayout.test.ts +773 -0
- package/Tests/UI/Components/Charts/ChartAnnotationRail.test.tsx +789 -0
- package/Tests/UI/Components/Charts/ChartBucketIdentity.test.ts +254 -0
- package/Tests/UI/Components/Charts/TimeAnnotation.test.ts +471 -0
- package/Tests/UI/Components/EditionLabelLicenseRefresh.test.tsx +405 -0
- package/Tests/UI/Components/ErrorMessageRefreshControl.test.tsx +83 -0
- package/Tests/UI/Components/MarkdownConverters.test.ts +686 -0
- package/Tests/UI/Components/ModelTable/BaseModelTableDeleteAndEmptyStates.test.tsx +341 -0
- package/Tests/UI/Components/OrderedStatesList.test.tsx +5 -5
- package/Tests/UI/Components/PendingProjectInvitations.test.tsx +1 -1
- package/Tests/UI/Components/TableSortAndMobileColumns.test.tsx +195 -0
- package/Tests/UI/Components/XAxis.test.ts +270 -5
- package/Tests/UI/Components/XAxisDaylightSaving.test.ts +113 -0
- package/Tests/UI/Styles/ChartFocusRing.test.ts +153 -0
- package/Tests/UI/Styles/ChartFocusRingCoverage.test.tsx +377 -0
- package/Tests/UI/Styles/ThemeStylesheet.ts +162 -0
- package/Tests/Utils/EnterpriseLicenseSeats.test.ts +507 -0
- package/Tests/Utils/NetworkDiscovery/DiscoveredDeviceBuilder.test.ts +951 -1
- package/Tests/Utils/NetworkDiscovery/DiscoveredHostUtil.test.ts +238 -0
- package/Tests/Utils/NetworkDiscovery/DiscoveryReverseDnsChain.test.ts +1056 -0
- package/Tests/Utils/NetworkDiscovery/ReverseDnsNameUtil.test.ts +882 -0
- package/Types/Date.ts +39 -0
- package/Types/Monitor/SnmpMonitor/SnmpOidListUtil.ts +333 -0
- package/Types/Permission.ts +96 -0
- package/Types/Rum/SessionReplay.ts +21 -0
- package/Types/Telemetry/SourceMap.ts +8 -0
- package/UI/Components/Charts/ChartLibrary/Annotations/AnnotationHoverCard.tsx +176 -0
- package/UI/Components/Charts/ChartLibrary/Annotations/AnnotationLayout.ts +745 -0
- package/UI/Components/Charts/ChartLibrary/Annotations/ChartAnnotationLayer.tsx +576 -0
- package/UI/Components/Charts/ChartLibrary/Annotations/UseChartAnnotations.tsx +204 -0
- package/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.tsx +491 -544
- package/UI/Components/Charts/ChartLibrary/BarChart/BarChart.tsx +324 -372
- package/UI/Components/Charts/ChartLibrary/LineChart/LineChart.tsx +487 -540
- package/UI/Components/Charts/ChartLibrary/Types/FormattedReferenceRegion.ts +7 -3
- package/UI/Components/Charts/ChartLibrary/Types/FormattedTimeReferenceLine.ts +14 -3
- package/UI/Components/Charts/Types/ChartEventKind.ts +13 -0
- package/UI/Components/Charts/Types/ReferenceRegionProps.ts +6 -0
- package/UI/Components/Charts/Types/TimeReferenceLineProps.ts +20 -3
- package/UI/Components/Charts/Utils/TimeAnnotation.ts +69 -35
- package/UI/Components/Charts/Utils/XAxis.ts +125 -18
- package/UI/Components/EditionLabel/EditionLabel.tsx +280 -26
- package/UI/Components/ErrorMessage/ErrorMessage.tsx +21 -10
- package/UI/Components/Feed/FeedItem.tsx +4 -5
- package/UI/Components/Header/HeaderIconDropdownButton.tsx +1 -2
- package/UI/Components/Header/UserProfile/UserProfile.tsx +1 -2
- package/UI/Components/Image/Image.tsx +20 -3
- package/UI/Components/Markdown.tsx/MarkdownConverters.ts +0 -0
- package/UI/Components/MemberRoleAssignment/MemberRoleAssignment.tsx +1 -2
- package/UI/Components/ModelTable/BaseModelTable.tsx +150 -24
- package/UI/Components/Table/TableHeader.tsx +64 -48
- package/UI/Components/Table/TableRow.tsx +13 -3
- package/UI/Styles/Theme.css +38 -0
- package/Utils/EnterpriseLicense/EnterpriseLicenseSeats.ts +271 -0
- package/Utils/NetworkDiscovery/AutoImportRuleMatcher.ts +6 -0
- package/Utils/NetworkDiscovery/DiscoveredDeviceBuilder.ts +141 -10
- package/Utils/NetworkDiscovery/DiscoveredHostUtil.ts +55 -3
- package/Utils/NetworkDiscovery/ReverseDnsNameUtil.ts +151 -0
- package/Utils/Rules/RuleEngineLimits.ts +32 -0
- package/build/dist/Models/AnalyticsModels/SecurityEvent.js +48 -16
- package/build/dist/Models/AnalyticsModels/SecurityEvent.js.map +1 -1
- package/build/dist/Models/AnalyticsModels/ThreatIntelIndicator.js +10 -5
- package/build/dist/Models/AnalyticsModels/ThreatIntelIndicator.js.map +1 -1
- package/build/dist/Models/DatabaseModels/AlertSeverity.js +46 -1
- package/build/dist/Models/DatabaseModels/AlertSeverity.js.map +1 -1
- package/build/dist/Models/DatabaseModels/DetectionRule.js +16 -5
- package/build/dist/Models/DatabaseModels/DetectionRule.js.map +1 -1
- package/build/dist/Models/DatabaseModels/GoogleSecOpsConnection.js +14 -5
- package/build/dist/Models/DatabaseModels/GoogleSecOpsConnection.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentSeverity.js +46 -1
- package/build/dist/Models/DatabaseModels/IncidentSeverity.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Index.js +2 -0
- package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
- package/build/dist/Models/DatabaseModels/NetworkDevice.js +115 -3
- package/build/dist/Models/DatabaseModels/NetworkDevice.js.map +1 -1
- package/build/dist/Models/DatabaseModels/NetworkDeviceAutoImportRule.js +88 -0
- package/build/dist/Models/DatabaseModels/NetworkDeviceAutoImportRule.js.map +1 -1
- package/build/dist/Models/DatabaseModels/NetworkDeviceDiscoveryScan.js.map +1 -1
- package/build/dist/Models/DatabaseModels/NetworkDeviceOidTemplate.js +519 -0
- package/build/dist/Models/DatabaseModels/NetworkDeviceOidTemplate.js.map +1 -0
- package/build/dist/Models/DatabaseModels/ThreatIntelFeed.js +16 -5
- package/build/dist/Models/DatabaseModels/ThreatIntelFeed.js.map +1 -1
- package/build/dist/Server/API/GlobalConfigAPI.js +293 -176
- package/build/dist/Server/API/GlobalConfigAPI.js.map +1 -1
- package/build/dist/Server/API/TelemetryAPI.js +31 -3
- package/build/dist/Server/API/TelemetryAPI.js.map +1 -1
- package/build/dist/Server/EnvironmentConfig.js +83 -0
- package/build/dist/Server/EnvironmentConfig.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790500000000-AddNetworkDeviceOidTemplate.js +26 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790500000000-AddNetworkDeviceOidTemplate.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790600000000-AddAutoImportRuleOidTemplate.js +14 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790600000000-AddAutoImportRuleOidTemplate.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +4 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Middleware/MultipartFormData.js +93 -28
- package/build/dist/Server/Middleware/MultipartFormData.js.map +1 -1
- package/build/dist/Server/Services/AlertEpisodeLabelRuleEngineService.js +8 -1
- package/build/dist/Server/Services/AlertEpisodeLabelRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/AlertEpisodeOnCallRuleEngineService.js +8 -1
- package/build/dist/Server/Services/AlertEpisodeOnCallRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/AlertEpisodeOwnerRuleEngineService.js +8 -1
- package/build/dist/Server/Services/AlertEpisodeOwnerRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/AlertEpisodePrivacyRuleEngineService.js +8 -1
- package/build/dist/Server/Services/AlertEpisodePrivacyRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/AlertGroupingEngineService.js +8 -1
- package/build/dist/Server/Services/AlertGroupingEngineService.js.map +1 -1
- package/build/dist/Server/Services/AlertLabelRuleEngineService.js +8 -1
- package/build/dist/Server/Services/AlertLabelRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/AlertOnCallRuleEngineService.js +8 -1
- package/build/dist/Server/Services/AlertOnCallRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/AlertOwnerRuleEngineService.js +8 -1
- package/build/dist/Server/Services/AlertOwnerRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/AlertPrivacyRuleEngineService.js +8 -1
- package/build/dist/Server/Services/AlertPrivacyRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/AlertReminderRuleService.js +8 -1
- package/build/dist/Server/Services/AlertReminderRuleService.js.map +1 -1
- package/build/dist/Server/Services/AutoRemediationRuleEngineService.js +8 -1
- package/build/dist/Server/Services/AutoRemediationRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/CephClusterLabelRuleEngineService.js +8 -1
- package/build/dist/Server/Services/CephClusterLabelRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/CephClusterOwnerRuleEngineService.js +8 -1
- package/build/dist/Server/Services/CephClusterOwnerRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/CloudResourceLabelRuleEngineService.js +8 -1
- package/build/dist/Server/Services/CloudResourceLabelRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/CloudResourceOwnerRuleEngineService.js +8 -1
- package/build/dist/Server/Services/CloudResourceOwnerRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/DashboardLabelRuleEngineService.js +8 -1
- package/build/dist/Server/Services/DashboardLabelRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/DashboardOwnerRuleEngineService.js +8 -1
- package/build/dist/Server/Services/DashboardOwnerRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/DockerHostLabelRuleEngineService.js +8 -1
- package/build/dist/Server/Services/DockerHostLabelRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/DockerHostOwnerRuleEngineService.js +8 -1
- package/build/dist/Server/Services/DockerHostOwnerRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/DockerSwarmClusterLabelRuleEngineService.js +8 -1
- package/build/dist/Server/Services/DockerSwarmClusterLabelRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/DockerSwarmClusterOwnerRuleEngineService.js +8 -1
- package/build/dist/Server/Services/DockerSwarmClusterOwnerRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/HostLabelRuleEngineService.js +8 -1
- package/build/dist/Server/Services/HostLabelRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/HostOwnerRuleEngineService.js +8 -1
- package/build/dist/Server/Services/HostOwnerRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/IncidentEpisodeLabelRuleEngineService.js +8 -1
- package/build/dist/Server/Services/IncidentEpisodeLabelRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/IncidentEpisodeOnCallRuleEngineService.js +8 -1
- package/build/dist/Server/Services/IncidentEpisodeOnCallRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/IncidentEpisodeOwnerRuleEngineService.js +8 -1
- package/build/dist/Server/Services/IncidentEpisodeOwnerRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/IncidentEpisodePrivacyRuleEngineService.js +8 -1
- package/build/dist/Server/Services/IncidentEpisodePrivacyRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/IncidentGroupingEngineService.js +8 -1
- package/build/dist/Server/Services/IncidentGroupingEngineService.js.map +1 -1
- package/build/dist/Server/Services/IncidentLabelRuleEngineService.js +8 -1
- package/build/dist/Server/Services/IncidentLabelRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/IncidentOnCallRuleEngineService.js +8 -1
- package/build/dist/Server/Services/IncidentOnCallRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/IncidentOwnerRuleEngineService.js +8 -1
- package/build/dist/Server/Services/IncidentOwnerRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/IncidentPrivacyRuleEngineService.js +8 -1
- package/build/dist/Server/Services/IncidentPrivacyRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/IncidentReminderRuleService.js +8 -1
- package/build/dist/Server/Services/IncidentReminderRuleService.js.map +1 -1
- package/build/dist/Server/Services/IncidentSlaRuleService.js +8 -1
- package/build/dist/Server/Services/IncidentSlaRuleService.js.map +1 -1
- package/build/dist/Server/Services/IncomingCallPolicyLabelRuleEngineService.js +8 -1
- package/build/dist/Server/Services/IncomingCallPolicyLabelRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/IncomingCallPolicyOwnerRuleEngineService.js +8 -1
- package/build/dist/Server/Services/IncomingCallPolicyOwnerRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/Index.js +2 -0
- package/build/dist/Server/Services/Index.js.map +1 -1
- package/build/dist/Server/Services/IoTFleetLabelRuleEngineService.js +8 -1
- package/build/dist/Server/Services/IoTFleetLabelRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/IoTFleetOwnerRuleEngineService.js +8 -1
- package/build/dist/Server/Services/IoTFleetOwnerRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/KubernetesClusterLabelRuleEngineService.js +8 -1
- package/build/dist/Server/Services/KubernetesClusterLabelRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/KubernetesClusterOwnerRuleEngineService.js +8 -1
- package/build/dist/Server/Services/KubernetesClusterOwnerRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/KubernetesResourceService.js +5 -1
- package/build/dist/Server/Services/KubernetesResourceService.js.map +1 -1
- package/build/dist/Server/Services/MonitorLabelRuleEngineService.js +8 -1
- package/build/dist/Server/Services/MonitorLabelRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/MonitorOwnerRuleEngineService.js +8 -1
- package/build/dist/Server/Services/MonitorOwnerRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/NetworkDeviceAutoImportRuleEngineService.js +30 -21
- package/build/dist/Server/Services/NetworkDeviceAutoImportRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/NetworkDeviceAutoImportRuleService.js +64 -0
- package/build/dist/Server/Services/NetworkDeviceAutoImportRuleService.js.map +1 -1
- package/build/dist/Server/Services/NetworkDeviceLabelRuleEngineService.js +8 -1
- package/build/dist/Server/Services/NetworkDeviceLabelRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/NetworkDeviceOidTemplateService.js +180 -0
- package/build/dist/Server/Services/NetworkDeviceOidTemplateService.js.map +1 -0
- package/build/dist/Server/Services/NetworkDeviceOwnerRuleEngineService.js +8 -1
- package/build/dist/Server/Services/NetworkDeviceOwnerRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/NetworkDeviceService.js +154 -1
- package/build/dist/Server/Services/NetworkDeviceService.js.map +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyLabelRuleEngineService.js +8 -1
- package/build/dist/Server/Services/OnCallDutyPolicyLabelRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyOwnerRuleEngineService.js +8 -1
- package/build/dist/Server/Services/OnCallDutyPolicyOwnerRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyScheduleLabelRuleEngineService.js +8 -1
- package/build/dist/Server/Services/OnCallDutyPolicyScheduleLabelRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyScheduleOwnerRuleEngineService.js +8 -1
- package/build/dist/Server/Services/OnCallDutyPolicyScheduleOwnerRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/PodmanHostLabelRuleEngineService.js +8 -1
- package/build/dist/Server/Services/PodmanHostLabelRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/PodmanHostOwnerRuleEngineService.js +8 -1
- package/build/dist/Server/Services/PodmanHostOwnerRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/ProxmoxClusterLabelRuleEngineService.js +8 -1
- package/build/dist/Server/Services/ProxmoxClusterLabelRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/ProxmoxClusterOwnerRuleEngineService.js +8 -1
- package/build/dist/Server/Services/ProxmoxClusterOwnerRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/RumApplicationLabelRuleEngineService.js +8 -1
- package/build/dist/Server/Services/RumApplicationLabelRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/RumApplicationOwnerRuleEngineService.js +8 -1
- package/build/dist/Server/Services/RumApplicationOwnerRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/RunbookLabelRuleEngineService.js +8 -1
- package/build/dist/Server/Services/RunbookLabelRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/RunbookOwnerRuleEngineService.js +8 -1
- package/build/dist/Server/Services/RunbookOwnerRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/RunbookRuleEngineService.js +8 -1
- package/build/dist/Server/Services/RunbookRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/ScheduledMaintenanceLabelRuleEngineService.js +8 -1
- package/build/dist/Server/Services/ScheduledMaintenanceLabelRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/ScheduledMaintenanceOwnerRuleEngineService.js +8 -1
- package/build/dist/Server/Services/ScheduledMaintenanceOwnerRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/ScheduledMaintenanceReminderRuleService.js +8 -1
- package/build/dist/Server/Services/ScheduledMaintenanceReminderRuleService.js.map +1 -1
- package/build/dist/Server/Services/ServerlessFunctionLabelRuleEngineService.js +8 -1
- package/build/dist/Server/Services/ServerlessFunctionLabelRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/ServerlessFunctionOwnerRuleEngineService.js +8 -1
- package/build/dist/Server/Services/ServerlessFunctionOwnerRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/ServiceLabelRuleEngineService.js +8 -1
- package/build/dist/Server/Services/ServiceLabelRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/ServiceOwnerRuleEngineService.js +8 -1
- package/build/dist/Server/Services/ServiceOwnerRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageLabelRuleEngineService.js +8 -1
- package/build/dist/Server/Services/StatusPageLabelRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageMonitorRuleEngineService.js +6 -0
- package/build/dist/Server/Services/StatusPageMonitorRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageOwnerRuleEngineService.js +8 -1
- package/build/dist/Server/Services/StatusPageOwnerRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/TelemetrySourceMapService.js +213 -19
- package/build/dist/Server/Services/TelemetrySourceMapService.js.map +1 -1
- package/build/dist/Server/Services/UserService.js +25 -0
- package/build/dist/Server/Services/UserService.js.map +1 -1
- package/build/dist/Server/Services/WorkflowLabelRuleEngineService.js +8 -1
- package/build/dist/Server/Services/WorkflowLabelRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/WorkflowOwnerRuleEngineService.js +8 -1
- package/build/dist/Server/Services/WorkflowOwnerRuleEngineService.js.map +1 -1
- package/build/dist/Server/Utils/EnterpriseLicense/EnterpriseLicenseSeatUtil.js +145 -0
- package/build/dist/Server/Utils/EnterpriseLicense/EnterpriseLicenseSeatUtil.js.map +1 -0
- package/build/dist/Server/Utils/Monitor/CapturedMetricAttributeUtil.js +201 -0
- package/build/dist/Server/Utils/Monitor/CapturedMetricAttributeUtil.js.map +1 -0
- package/build/dist/Server/Utils/Monitor/Criteria/SnmpMonitorCriteria.js +19 -2
- package/build/dist/Server/Utils/Monitor/Criteria/SnmpMonitorCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorMetricUtil.js +42 -23
- package/build/dist/Server/Utils/Monitor/MonitorMetricUtil.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorTemplateUtil.js +29 -0
- package/build/dist/Server/Utils/Monitor/MonitorTemplateUtil.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/NetworkDeviceMetricUtil.js +11 -2
- package/build/dist/Server/Utils/Monitor/NetworkDeviceMetricUtil.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/NetworkDeviceWalkUtil.js +16 -1
- package/build/dist/Server/Utils/Monitor/NetworkDeviceWalkUtil.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/NetworkInventoryUtil.js +10 -0
- package/build/dist/Server/Utils/Monitor/NetworkInventoryUtil.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/SeriesResourceLabels.js +33 -0
- package/build/dist/Server/Utils/Monitor/SeriesResourceLabels.js.map +1 -1
- package/build/dist/Server/Utils/Rules/RuleEngineRuleRead.js +37 -0
- package/build/dist/Server/Utils/Rules/RuleEngineRuleRead.js.map +1 -0
- package/build/dist/Server/Utils/Telemetry/ExceptionSanitizer.js +12 -1
- package/build/dist/Server/Utils/Telemetry/ExceptionSanitizer.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/LogExceptionExtractor.js +14 -2
- package/build/dist/Server/Utils/Telemetry/LogExceptionExtractor.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/SourceMapResolver.js +15 -5
- package/build/dist/Server/Utils/Telemetry/SourceMapResolver.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/StackTraceParser.js +307 -0
- package/build/dist/Server/Utils/Telemetry/StackTraceParser.js.map +1 -1
- package/build/dist/Types/Date.js +27 -0
- package/build/dist/Types/Date.js.map +1 -1
- package/build/dist/Types/Monitor/SnmpMonitor/SnmpOidListUtil.js +253 -0
- package/build/dist/Types/Monitor/SnmpMonitor/SnmpOidListUtil.js.map +1 -0
- package/build/dist/Types/Permission.js +86 -0
- package/build/dist/Types/Permission.js.map +1 -1
- package/build/dist/Types/Rum/SessionReplay.js +19 -0
- package/build/dist/Types/Rum/SessionReplay.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartLibrary/Annotations/AnnotationHoverCard.js +83 -0
- package/build/dist/UI/Components/Charts/ChartLibrary/Annotations/AnnotationHoverCard.js.map +1 -0
- package/build/dist/UI/Components/Charts/ChartLibrary/Annotations/AnnotationLayout.js +531 -0
- package/build/dist/UI/Components/Charts/ChartLibrary/Annotations/AnnotationLayout.js.map +1 -0
- package/build/dist/UI/Components/Charts/ChartLibrary/Annotations/ChartAnnotationLayer.js +264 -0
- package/build/dist/UI/Components/Charts/ChartLibrary/Annotations/ChartAnnotationLayer.js.map +1 -0
- package/build/dist/UI/Components/Charts/ChartLibrary/Annotations/UseChartAnnotations.js +122 -0
- package/build/dist/UI/Components/Charts/ChartLibrary/Annotations/UseChartAnnotations.js.map +1 -0
- package/build/dist/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.js +213 -233
- package/build/dist/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartLibrary/BarChart/BarChart.js +139 -152
- package/build/dist/UI/Components/Charts/ChartLibrary/BarChart/BarChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartLibrary/LineChart/LineChart.js +212 -232
- package/build/dist/UI/Components/Charts/ChartLibrary/LineChart/LineChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/Types/ChartEventKind.js +14 -0
- package/build/dist/UI/Components/Charts/Types/ChartEventKind.js.map +1 -0
- package/build/dist/UI/Components/Charts/Utils/TimeAnnotation.js +57 -28
- package/build/dist/UI/Components/Charts/Utils/TimeAnnotation.js.map +1 -1
- package/build/dist/UI/Components/Charts/Utils/XAxis.js +103 -17
- package/build/dist/UI/Components/Charts/Utils/XAxis.js.map +1 -1
- package/build/dist/UI/Components/EditionLabel/EditionLabel.js +198 -23
- package/build/dist/UI/Components/EditionLabel/EditionLabel.js.map +1 -1
- package/build/dist/UI/Components/ErrorMessage/ErrorMessage.js +15 -5
- package/build/dist/UI/Components/ErrorMessage/ErrorMessage.js.map +1 -1
- package/build/dist/UI/Components/Feed/FeedItem.js +4 -5
- package/build/dist/UI/Components/Feed/FeedItem.js.map +1 -1
- package/build/dist/UI/Components/Header/HeaderIconDropdownButton.js +1 -2
- package/build/dist/UI/Components/Header/HeaderIconDropdownButton.js.map +1 -1
- package/build/dist/UI/Components/Header/UserProfile/UserProfile.js.map +1 -1
- package/build/dist/UI/Components/Image/Image.js +6 -1
- package/build/dist/UI/Components/Image/Image.js.map +1 -1
- package/build/dist/UI/Components/Markdown.tsx/MarkdownConverters.js +0 -0
- package/build/dist/UI/Components/Markdown.tsx/MarkdownConverters.js.map +1 -1
- package/build/dist/UI/Components/MemberRoleAssignment/MemberRoleAssignment.js +1 -2
- package/build/dist/UI/Components/MemberRoleAssignment/MemberRoleAssignment.js.map +1 -1
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js +104 -23
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
- package/build/dist/UI/Components/Table/TableHeader.js +20 -22
- package/build/dist/UI/Components/Table/TableHeader.js.map +1 -1
- package/build/dist/UI/Components/Table/TableRow.js +13 -4
- package/build/dist/UI/Components/Table/TableRow.js.map +1 -1
- package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseSeats.js +153 -0
- package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseSeats.js.map +1 -0
- package/build/dist/Utils/NetworkDiscovery/AutoImportRuleMatcher.js.map +1 -1
- package/build/dist/Utils/NetworkDiscovery/DiscoveredDeviceBuilder.js +127 -7
- package/build/dist/Utils/NetworkDiscovery/DiscoveredDeviceBuilder.js.map +1 -1
- package/build/dist/Utils/NetworkDiscovery/DiscoveredHostUtil.js +50 -3
- package/build/dist/Utils/NetworkDiscovery/DiscoveredHostUtil.js.map +1 -1
- package/build/dist/Utils/NetworkDiscovery/ReverseDnsNameUtil.js +132 -0
- package/build/dist/Utils/NetworkDiscovery/ReverseDnsNameUtil.js.map +1 -0
- package/build/dist/Utils/Rules/RuleEngineLimits.js +31 -0
- package/build/dist/Utils/Rules/RuleEngineLimits.js.map +1 -0
- package/jest.config.json +1 -0
- package/package.json +2 -2
|
@@ -0,0 +1,1056 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DiscoveredDeviceScanSource,
|
|
3
|
+
buildDeviceName,
|
|
4
|
+
buildFallbackDeviceName,
|
|
5
|
+
buildNetworkDeviceFromDiscoveredHost,
|
|
6
|
+
getDiscoveredHostDisplayName,
|
|
7
|
+
} from "../../../Utils/NetworkDiscovery/DiscoveredDeviceBuilder";
|
|
8
|
+
import { normalizeDiscoveredHosts } from "../../../Utils/NetworkDiscovery/DiscoveredHostUtil";
|
|
9
|
+
import NetworkDevice from "../../../Models/DatabaseModels/NetworkDevice";
|
|
10
|
+
import { DiscoveredNetworkDevice } from "../../../Models/DatabaseModels/NetworkDeviceDiscoveryScan";
|
|
11
|
+
import NetworkDeviceMonitoringMethod from "../../../Types/NetworkDevice/NetworkDeviceMonitoringMethod";
|
|
12
|
+
import ObjectID from "../../../Types/ObjectID";
|
|
13
|
+
import { describe, expect, it } from "@jest/globals";
|
|
14
|
+
|
|
15
|
+
/*
|
|
16
|
+
* The WHOLE journey a PTR record makes, end to end, in one place.
|
|
17
|
+
*
|
|
18
|
+
* Every other suite around OneUptime issue #3529 tests one link:
|
|
19
|
+
* ReverseDnsNameUtil pins what a name may contain, DiscoveredHostUtil pins
|
|
20
|
+
* what normalisation does to a row, DiscoveredDeviceBuilder pins which field
|
|
21
|
+
* becomes which device column. Each of those can be green while the chain
|
|
22
|
+
* they form is broken, because the interesting failures live in the JOINTS —
|
|
23
|
+
* a value that survives normalisation but is re-read differently by the
|
|
24
|
+
* builder, a name one module accepts at its own ceiling and the next must
|
|
25
|
+
* cut down, or a row two layers deliberately disagree about.
|
|
26
|
+
*
|
|
27
|
+
* So this file runs the real path a resolved hostname actually travels:
|
|
28
|
+
*
|
|
29
|
+
* probe payload
|
|
30
|
+
* -> stored jsonb (modelled honestly as JSON.parse(JSON.stringify(...)),
|
|
31
|
+
* because `discoveredDevices` is written VERBATIM from the probe's
|
|
32
|
+
* payload by ProbeIngest/DiscoveryScan.ts — no schema, no coercion,
|
|
33
|
+
* so the column is exactly a JSON round trip of whatever was sent)
|
|
34
|
+
* -> normalizeDiscoveredHosts
|
|
35
|
+
* -> getDiscoveredHostDisplayName / buildDeviceName
|
|
36
|
+
* -> buildNetworkDeviceFromDiscoveredHost
|
|
37
|
+
*
|
|
38
|
+
* Nothing here is mocked. The point is that these five steps agree, and a
|
|
39
|
+
* double or a stub would be agreeing with itself.
|
|
40
|
+
*
|
|
41
|
+
* The JSON round trip is not ceremony. It is what erases `undefined`-valued
|
|
42
|
+
* keys, and what leaves numbers, nulls, objects and arrays sitting in fields
|
|
43
|
+
* the TypeScript interface declares as `string | undefined` — every hostile
|
|
44
|
+
* shape below reaches the readers only because the column cannot refuse it.
|
|
45
|
+
*
|
|
46
|
+
* SCOPE NOTE, so no comment here over-claims: the Dashboard's Discovery page
|
|
47
|
+
* is NOT imported by this file, and nothing below observes what the Review
|
|
48
|
+
* dialog renders. What is pinned is that the functions the dialog and the
|
|
49
|
+
* import loop both call return the same answers on the same row. Where a
|
|
50
|
+
* comment mentions the dialog it is explaining WHY a case matters, not
|
|
51
|
+
* claiming the dialog is under test.
|
|
52
|
+
*/
|
|
53
|
+
|
|
54
|
+
const PROJECT_ID: ObjectID = new ObjectID(
|
|
55
|
+
"33333333-3333-4333-8333-333333333333",
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
/*
|
|
59
|
+
* A scan with no credentials at all — the shape a ping-only sweep really has.
|
|
60
|
+
*
|
|
61
|
+
* The reporter's hosts answer ICMP and nothing else, so they import down the
|
|
62
|
+
* Monitor branch of the builder, which returns before it ever asks for a
|
|
63
|
+
* credential set. Supplying a rich SNMP fixture here would only invite a
|
|
64
|
+
* reader to think credentials were part of what these tests pin; they are
|
|
65
|
+
* pinned in DiscoveredDeviceBuilder.test.ts and AutoImportScanCredentialSelect
|
|
66
|
+
* .test.ts instead.
|
|
67
|
+
*/
|
|
68
|
+
const PING_ONLY_SCAN: DiscoveredDeviceScanSource = {};
|
|
69
|
+
|
|
70
|
+
/*
|
|
71
|
+
* The two ceilings this chain has to reconcile, written as literals rather
|
|
72
|
+
* than imported from the modules under test.
|
|
73
|
+
*
|
|
74
|
+
* ReverseDnsNameUtil accepts a presentation-form DNS name up to 253
|
|
75
|
+
* characters; DiscoveredDeviceBuilder clamps a device name to 80 because the
|
|
76
|
+
* slug is slugify(name) plus eleven characters into its own varchar(100).
|
|
77
|
+
* Importing MAX_DEVICE_NAME_LENGTH and asserting against it would make the
|
|
78
|
+
* length tests below survive someone raising the constant to 253 — which is
|
|
79
|
+
* exactly the change that starts failing device creates with a slug-length
|
|
80
|
+
* error. The numbers are the contract, so the numbers are what is asserted.
|
|
81
|
+
*/
|
|
82
|
+
const MAX_DNS_NAME_LENGTH: number = 253;
|
|
83
|
+
const MAX_NAME_LENGTH: number = 80;
|
|
84
|
+
|
|
85
|
+
/*
|
|
86
|
+
* A PTR name that is perfectly legal DNS and too long to be a device name:
|
|
87
|
+
* one label at RFC 1035's 63-octet ceiling, in a three-label zone. 85
|
|
88
|
+
* characters — under 253, over 80 — so it is accepted whole by one module and
|
|
89
|
+
* cut down by the next. Long labels like this are ordinary on estates that
|
|
90
|
+
* encode rack, unit and role into the hostname.
|
|
91
|
+
*/
|
|
92
|
+
const LONG_LABEL: string = `hq-fileserver-${"a".repeat(49)}`;
|
|
93
|
+
const LONG_PTR_NAME: string = `${LONG_LABEL}.dc14.corp.example.net`;
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* The payload as it comes back OUT of the jsonb column.
|
|
97
|
+
*
|
|
98
|
+
* Typed `Array<unknown>` on the way in on purpose: the probe payloads below
|
|
99
|
+
* deliberately hold values the `DiscoveredNetworkDevice` interface forbids,
|
|
100
|
+
* and pretending otherwise would be testing a payload the column cannot
|
|
101
|
+
* actually contain. The cast on the way out is exactly the lie the real code
|
|
102
|
+
* lives with — `getDiscoveredHosts` checks that the VALUE is an array and
|
|
103
|
+
* hands the elements on as hosts without looking at them.
|
|
104
|
+
*/
|
|
105
|
+
function storedScanResults(
|
|
106
|
+
payload: Array<unknown>,
|
|
107
|
+
): Array<DiscoveredNetworkDevice> {
|
|
108
|
+
return JSON.parse(JSON.stringify(payload)) as Array<DiscoveredNetworkDevice>;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** Store the payload and normalise it, exactly as every reader does. */
|
|
112
|
+
function hostsFromPayload(
|
|
113
|
+
payload: Array<unknown>,
|
|
114
|
+
): Array<DiscoveredNetworkDevice> {
|
|
115
|
+
return normalizeDiscoveredHosts(storedScanResults(payload));
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** Store the payload, normalise it, and name every surviving row. */
|
|
119
|
+
function namesFromPayload(payload: Array<unknown>): Array<string> {
|
|
120
|
+
return hostsFromPayload(payload).map(
|
|
121
|
+
(host: DiscoveredNetworkDevice): string => {
|
|
122
|
+
return buildDeviceName(host);
|
|
123
|
+
},
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** Store the payload, normalise it, and build the devices it imports as. */
|
|
128
|
+
function devicesFromPayload(payload: Array<unknown>): Array<NetworkDevice> {
|
|
129
|
+
return hostsFromPayload(payload).map(
|
|
130
|
+
(host: DiscoveredNetworkDevice): NetworkDevice => {
|
|
131
|
+
return buildNetworkDeviceFromDiscoveredHost({
|
|
132
|
+
projectId: PROJECT_ID,
|
|
133
|
+
host: host,
|
|
134
|
+
scan: PING_ONLY_SCAN,
|
|
135
|
+
});
|
|
136
|
+
},
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** The names of a built batch, which is what an operator ticks. */
|
|
141
|
+
function deviceNames(devices: Array<NetworkDevice>): Array<string | undefined> {
|
|
142
|
+
return devices.map((device: NetworkDevice): string | undefined => {
|
|
143
|
+
return device.name;
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/** The addresses of a built batch, which is what the poller dials. */
|
|
148
|
+
function deviceHostnames(
|
|
149
|
+
devices: Array<NetworkDevice>,
|
|
150
|
+
): Array<string | undefined> {
|
|
151
|
+
return devices.map((device: NetworkDevice): string | undefined => {
|
|
152
|
+
return device.hostname;
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
describe("Reverse DNS, probe payload to created NetworkDevice", () => {
|
|
157
|
+
it("names the reporter's four ping-only hosts by PTR and still addresses them by IP", () => {
|
|
158
|
+
/*
|
|
159
|
+
* Issue #3529 as filed. Four hosts on 10.18.166.0/24 that answer ping and
|
|
160
|
+
* have nothing readable over SNMP, on an estate that publishes a DNS
|
|
161
|
+
* record for each. Before the feature the name line was
|
|
162
|
+
* `sysName || ipAddress`, and with no sysName that is four bare
|
|
163
|
+
* addresses — the exact screenshot in the issue.
|
|
164
|
+
*
|
|
165
|
+
* .55's PTR carries the trailing root label. Resolvers differ on whether
|
|
166
|
+
* they include it, so the fully qualified spelling is in the payload
|
|
167
|
+
* rather than in a unit test of the normaliser alone: if the root-dot
|
|
168
|
+
* strip were ever dropped, THIS is where an operator would see it, as a
|
|
169
|
+
* device named "hq-camera-11.corp.example.net." with a stray dot.
|
|
170
|
+
*/
|
|
171
|
+
const payload: Array<unknown> = [
|
|
172
|
+
{
|
|
173
|
+
ipAddress: "10.18.166.51",
|
|
174
|
+
snmpReachable: false,
|
|
175
|
+
dnsHostname: "hq-fileserver.corp.example.net",
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
ipAddress: "10.18.166.53",
|
|
179
|
+
snmpReachable: false,
|
|
180
|
+
dnsHostname: "hq-printer-02.corp.example.net",
|
|
181
|
+
},
|
|
182
|
+
// No PTR record at all: this one must still read as its address.
|
|
183
|
+
{ ipAddress: "10.18.166.54", snmpReachable: false },
|
|
184
|
+
{
|
|
185
|
+
ipAddress: "10.18.166.55",
|
|
186
|
+
snmpReachable: false,
|
|
187
|
+
dnsHostname: "hq-camera-11.corp.example.net.",
|
|
188
|
+
},
|
|
189
|
+
];
|
|
190
|
+
|
|
191
|
+
const devices: Array<NetworkDevice> = devicesFromPayload(payload);
|
|
192
|
+
|
|
193
|
+
expect(deviceNames(devices)).toEqual([
|
|
194
|
+
"hq-fileserver.corp.example.net",
|
|
195
|
+
"hq-printer-02.corp.example.net",
|
|
196
|
+
"10.18.166.54",
|
|
197
|
+
"hq-camera-11.corp.example.net",
|
|
198
|
+
]);
|
|
199
|
+
|
|
200
|
+
/*
|
|
201
|
+
* The other half of what the issue asked for, in its own words: "retain
|
|
202
|
+
* the IP address as the address/IP field". `hostname` is what the SNMP
|
|
203
|
+
* poller dials, what a trap's source address is correlated against, and
|
|
204
|
+
* the dedup key NetworkDeviceService.getRegisteredHostnames matches a
|
|
205
|
+
* later scan's results on. A device that stored its PTR name here would
|
|
206
|
+
* stop polling the day its reverse zone changed, and would be discovered
|
|
207
|
+
* as a second, "unregistered" host on the very next sweep.
|
|
208
|
+
*/
|
|
209
|
+
expect(deviceHostnames(devices)).toEqual([
|
|
210
|
+
"10.18.166.51",
|
|
211
|
+
"10.18.166.53",
|
|
212
|
+
"10.18.166.54",
|
|
213
|
+
"10.18.166.55",
|
|
214
|
+
]);
|
|
215
|
+
|
|
216
|
+
// Naming a host by DNS must not change how it is monitored.
|
|
217
|
+
for (const device of devices) {
|
|
218
|
+
expect(device.monitoringMethod).toBe(
|
|
219
|
+
NetworkDeviceMonitoringMethod.Monitor,
|
|
220
|
+
);
|
|
221
|
+
expect(device.isPollingEnabled).toBe(false);
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it("names a payload that predates the feature exactly as it always did", () => {
|
|
226
|
+
/*
|
|
227
|
+
* The back-compat guarantee, taken end to end rather than at one function.
|
|
228
|
+
*
|
|
229
|
+
* Two populations send payloads with no `dnsHostname` key: probes running
|
|
230
|
+
* a build from before #3529, and every row already sitting in a
|
|
231
|
+
* `discoveredDevices` column. Both must keep producing the names they
|
|
232
|
+
* produced yesterday — `sysName` when there is one, the address when
|
|
233
|
+
* there is not. A regression here is worse than a missing feature: it
|
|
234
|
+
* renames devices that import correctly today.
|
|
235
|
+
*/
|
|
236
|
+
const payload: Array<unknown> = [
|
|
237
|
+
{ ipAddress: "10.18.166.51", sysName: "core-switch-01" },
|
|
238
|
+
{ ipAddress: "10.18.166.53", snmpReachable: false },
|
|
239
|
+
/*
|
|
240
|
+
* `dnsHostname: undefined` is what a NEW probe sends for a host with no
|
|
241
|
+
* PTR record, and JSON.stringify erases the key entirely. So the modern
|
|
242
|
+
* no-record row and the legacy row are byte-identical in the column,
|
|
243
|
+
* which is precisely why no reader needs to tell them apart.
|
|
244
|
+
*/
|
|
245
|
+
{
|
|
246
|
+
ipAddress: "10.18.166.54",
|
|
247
|
+
snmpReachable: false,
|
|
248
|
+
dnsHostname: undefined,
|
|
249
|
+
},
|
|
250
|
+
];
|
|
251
|
+
|
|
252
|
+
const hosts: Array<DiscoveredNetworkDevice> = hostsFromPayload(payload);
|
|
253
|
+
|
|
254
|
+
expect(
|
|
255
|
+
hosts.map((host: DiscoveredNetworkDevice): string => {
|
|
256
|
+
return buildDeviceName(host);
|
|
257
|
+
}),
|
|
258
|
+
).toEqual(["core-switch-01", "10.18.166.53", "10.18.166.54"]);
|
|
259
|
+
|
|
260
|
+
/*
|
|
261
|
+
* Normalisation must not INVENT the key either. Readers are entitled to
|
|
262
|
+
* ask `"dnsHostname" in host`, and a blank-but-present key would show an
|
|
263
|
+
* empty DNS line under every legacy host. `toHaveProperty` rather than a
|
|
264
|
+
* value comparison on purpose: `toEqual` treats an absent key and a key
|
|
265
|
+
* set to `undefined` as the same thing, and that is the exact distinction
|
|
266
|
+
* DiscoveredHostUtil promises to keep.
|
|
267
|
+
*/
|
|
268
|
+
for (const host of hosts) {
|
|
269
|
+
expect(host).not.toHaveProperty("dnsHostname");
|
|
270
|
+
}
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
it("falls back to the address for every hostile PTR value the column can hold", () => {
|
|
274
|
+
/*
|
|
275
|
+
* `dnsHostname` is the only field in a scan result whose value is chosen
|
|
276
|
+
* by the scanned network. A discovery sweep is routinely pointed at a
|
|
277
|
+
* subnet nobody in the project administers, so "whoever runs that
|
|
278
|
+
* resolver picks a device's name, its slug and a line in the dashboard"
|
|
279
|
+
* is the actual threat model — not a hypothetical one.
|
|
280
|
+
*
|
|
281
|
+
* Deliberately a SHORT list. ReverseDnsNameUtil.test.ts and
|
|
282
|
+
* DiscoveredDeviceBuilder.test.ts already enumerate hostile spellings one
|
|
283
|
+
* function at a time; repeating all of them here would buy coverage the
|
|
284
|
+
* chain does not add anything to. What is kept is the rows where the
|
|
285
|
+
* chain itself is the interesting part:
|
|
286
|
+
*
|
|
287
|
+
* - markup, the payload that makes a name dangerous rather than merely
|
|
288
|
+
* ugly once it is rendered and slugified;
|
|
289
|
+
* - a name with interior spaces, which would become a device whose name
|
|
290
|
+
* and slug disagree;
|
|
291
|
+
* - the bare "in-addr.arpa" apex, which a suffix-only zone check lets
|
|
292
|
+
* straight through and which is a strictly WORSE label than the
|
|
293
|
+
* address it was derived from;
|
|
294
|
+
* - the address restated as a dotted quad, which defeats the entire
|
|
295
|
+
* purpose of the feature by dressing an address up as a resolved name;
|
|
296
|
+
* - `null`, which the interface forbids and the jsonb column will
|
|
297
|
+
* happily store.
|
|
298
|
+
*
|
|
299
|
+
* The assertion is the same for all of them: name the device by its
|
|
300
|
+
* ADDRESS, and do not throw. Throwing matters as much as the value does —
|
|
301
|
+
* these functions run inside a React render, where a TypeError takes out
|
|
302
|
+
* the whole modal rather than one row, so a thrown error fails the
|
|
303
|
+
* `toEqual` below and that is the intended way for it to be caught.
|
|
304
|
+
*/
|
|
305
|
+
const payload: Array<unknown> = [
|
|
306
|
+
{
|
|
307
|
+
ipAddress: "10.18.166.60",
|
|
308
|
+
snmpReachable: false,
|
|
309
|
+
dnsHostname: '<script>alert("pwned")</script>',
|
|
310
|
+
},
|
|
311
|
+
{
|
|
312
|
+
ipAddress: "10.18.166.61",
|
|
313
|
+
snmpReachable: false,
|
|
314
|
+
dnsHostname: " hq server 01 ",
|
|
315
|
+
},
|
|
316
|
+
{
|
|
317
|
+
ipAddress: "10.18.166.63",
|
|
318
|
+
snmpReachable: false,
|
|
319
|
+
dnsHostname: "in-addr.arpa",
|
|
320
|
+
},
|
|
321
|
+
{
|
|
322
|
+
ipAddress: "10.18.166.64",
|
|
323
|
+
snmpReachable: false,
|
|
324
|
+
dnsHostname: "10.18.166.64",
|
|
325
|
+
},
|
|
326
|
+
{ ipAddress: "10.18.166.68", snmpReachable: false, dnsHostname: null },
|
|
327
|
+
];
|
|
328
|
+
|
|
329
|
+
const addresses: Array<string> = [
|
|
330
|
+
"10.18.166.60",
|
|
331
|
+
"10.18.166.61",
|
|
332
|
+
"10.18.166.63",
|
|
333
|
+
"10.18.166.64",
|
|
334
|
+
"10.18.166.68",
|
|
335
|
+
];
|
|
336
|
+
|
|
337
|
+
const hosts: Array<DiscoveredNetworkDevice> = hostsFromPayload(payload);
|
|
338
|
+
|
|
339
|
+
expect(
|
|
340
|
+
hosts.map((host: DiscoveredNetworkDevice): string => {
|
|
341
|
+
return buildDeviceName(host);
|
|
342
|
+
}),
|
|
343
|
+
).toEqual(addresses);
|
|
344
|
+
|
|
345
|
+
// Nothing survives into the column's cleaned reading either.
|
|
346
|
+
for (const host of hosts) {
|
|
347
|
+
expect(host).not.toHaveProperty("dnsHostname");
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
expect(deviceHostnames(devicesFromPayload(payload))).toEqual(addresses);
|
|
351
|
+
|
|
352
|
+
/*
|
|
353
|
+
* The SECOND line of defence, pinned separately because it is the one a
|
|
354
|
+
* refactor is most likely to remove as redundant.
|
|
355
|
+
*
|
|
356
|
+
* `getDiscoveredHostDisplayName` re-normalises `dnsHostname` itself
|
|
357
|
+
* rather than trusting the row it was handed. That is what protects a row
|
|
358
|
+
* written straight through the API, and a row stored by a probe whose
|
|
359
|
+
* version never heard of these rules — neither of which passed through
|
|
360
|
+
* normalizeDiscoveredHosts on the way in. Building names from the RAW
|
|
361
|
+
* stored payload must give the same answers.
|
|
362
|
+
*/
|
|
363
|
+
expect(
|
|
364
|
+
storedScanResults(payload).map(
|
|
365
|
+
(host: DiscoveredNetworkDevice): string => {
|
|
366
|
+
return getDiscoveredHostDisplayName(host);
|
|
367
|
+
},
|
|
368
|
+
),
|
|
369
|
+
).toEqual(addresses);
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
it("cuts a legal-but-long PTR name down to the device ceiling and refuses one no resolver could return", () => {
|
|
373
|
+
/*
|
|
374
|
+
* THE sharpest joint in the chain: two modules with different ceilings.
|
|
375
|
+
*
|
|
376
|
+
* ReverseDnsNameUtil accepts up to 253 characters, because that is what a
|
|
377
|
+
* DNS presentation-form name may be. DiscoveredDeviceBuilder clamps to
|
|
378
|
+
* 80, because the name is slugified into its own varchar(100) and the
|
|
379
|
+
* create path THROWS on overflow rather than truncating. So a name in
|
|
380
|
+
* between is accepted whole by the first module and MUST be cut by the
|
|
381
|
+
* second — and it is the only place in the feature where the string an
|
|
382
|
+
* operator reads and the string written to the column are not identical.
|
|
383
|
+
*
|
|
384
|
+
* Both rows are needed, and they pull in opposite directions:
|
|
385
|
+
*
|
|
386
|
+
* - 85 characters is a real hostname, so it is TRUNCATED. If the clamp
|
|
387
|
+
* were dropped the device create would fail on the slug and the host
|
|
388
|
+
* would silently never import.
|
|
389
|
+
* - 304 characters was never a DNS name, so it is REJECTED outright and
|
|
390
|
+
* the host falls back to its address. Truncating it instead would put
|
|
391
|
+
* an 80-character fragment of an untrusted string into the name
|
|
392
|
+
* column and present it as a resolved hostname.
|
|
393
|
+
*/
|
|
394
|
+
const tooLongForDns: string = ["a", "b", "c", "d", "e"]
|
|
395
|
+
.map((letter: string): string => {
|
|
396
|
+
return letter.repeat(60);
|
|
397
|
+
})
|
|
398
|
+
.join(".");
|
|
399
|
+
|
|
400
|
+
// Fixture self-check: labels stay legal, only the total length offends.
|
|
401
|
+
expect(LONG_PTR_NAME.length).toBe(85);
|
|
402
|
+
expect(tooLongForDns.length).toBeGreaterThan(MAX_DNS_NAME_LENGTH);
|
|
403
|
+
|
|
404
|
+
const payload: Array<unknown> = [
|
|
405
|
+
{
|
|
406
|
+
ipAddress: "10.18.166.51",
|
|
407
|
+
snmpReachable: false,
|
|
408
|
+
dnsHostname: LONG_PTR_NAME,
|
|
409
|
+
},
|
|
410
|
+
{
|
|
411
|
+
ipAddress: "10.18.166.53",
|
|
412
|
+
snmpReachable: false,
|
|
413
|
+
dnsHostname: tooLongForDns,
|
|
414
|
+
},
|
|
415
|
+
];
|
|
416
|
+
|
|
417
|
+
const hosts: Array<DiscoveredNetworkDevice> = hostsFromPayload(payload);
|
|
418
|
+
|
|
419
|
+
/*
|
|
420
|
+
* The 85-character name survives normalisation INTACT — the column keeps
|
|
421
|
+
* the resolver's answer, not a device-shaped abbreviation of it, so a
|
|
422
|
+
* later reader with a wider column is not stuck with this one's ceiling.
|
|
423
|
+
*/
|
|
424
|
+
expect(hosts[0]?.dnsHostname).toBe(LONG_PTR_NAME);
|
|
425
|
+
expect(getDiscoveredHostDisplayName(hosts[0]!)).toBe(LONG_PTR_NAME);
|
|
426
|
+
|
|
427
|
+
// The 304-character one is not a name at all, so the key is gone.
|
|
428
|
+
expect(hosts[1]).not.toHaveProperty("dnsHostname");
|
|
429
|
+
|
|
430
|
+
/*
|
|
431
|
+
* The clamped name spelled out rather than derived with `.substring(80)`,
|
|
432
|
+
* so this asserts the ANSWER instead of restating the implementation:
|
|
433
|
+
* the 63-character label, then ".dc14.corp." and the first six letters of
|
|
434
|
+
* "example", which is where character 80 lands.
|
|
435
|
+
*/
|
|
436
|
+
const clampedName: string = `${LONG_LABEL}.dc14.corp.exampl`;
|
|
437
|
+
|
|
438
|
+
expect(clampedName.length).toBe(MAX_NAME_LENGTH);
|
|
439
|
+
|
|
440
|
+
const devices: Array<NetworkDevice> = devicesFromPayload(payload);
|
|
441
|
+
|
|
442
|
+
expect(deviceNames(devices)).toEqual([clampedName, "10.18.166.53"]);
|
|
443
|
+
expect(deviceHostnames(devices)).toEqual(["10.18.166.51", "10.18.166.53"]);
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
it("keeps naming the good rows when the payload contains junk rows", () => {
|
|
447
|
+
/*
|
|
448
|
+
* One bad element must not poison the batch.
|
|
449
|
+
*
|
|
450
|
+
* The only guard on `discoveredDevices` checks that the VALUE is an
|
|
451
|
+
* array; it never looks at the elements. A single `null` row used to
|
|
452
|
+
* throw a TypeError the moment the operator clicked a filter button —
|
|
453
|
+
* inside the modal body, during render. This pins that the rows around
|
|
454
|
+
* the junk still name and address correctly.
|
|
455
|
+
*
|
|
456
|
+
* `null`, the number and the string are dropped because they are not
|
|
457
|
+
* objects. The address-less OBJECT is kept, and the contract for it is
|
|
458
|
+
* the second assertion below, not its mere survival: it becomes a device
|
|
459
|
+
* with `hostname: ""`, which is a row that carries its sysName as a name
|
|
460
|
+
* and collides on the dedup key with every other address-less row. That
|
|
461
|
+
* is the deliberate choice — a visibly broken row beats a vanished one —
|
|
462
|
+
* and it is stated here so a change to it has to be a decision.
|
|
463
|
+
*/
|
|
464
|
+
const payload: Array<unknown> = [
|
|
465
|
+
null,
|
|
466
|
+
42,
|
|
467
|
+
"10.18.166.51",
|
|
468
|
+
{ sysName: "orphan-no-address" },
|
|
469
|
+
{
|
|
470
|
+
ipAddress: "10.18.166.53",
|
|
471
|
+
snmpReachable: false,
|
|
472
|
+
dnsHostname: "hq-ups-01.corp.example.net",
|
|
473
|
+
},
|
|
474
|
+
{ ipAddress: "10.18.166.54", snmpReachable: false },
|
|
475
|
+
];
|
|
476
|
+
|
|
477
|
+
const devices: Array<NetworkDevice> = devicesFromPayload(payload);
|
|
478
|
+
|
|
479
|
+
expect(deviceNames(devices)).toEqual([
|
|
480
|
+
"orphan-no-address",
|
|
481
|
+
"hq-ups-01.corp.example.net",
|
|
482
|
+
"10.18.166.54",
|
|
483
|
+
]);
|
|
484
|
+
|
|
485
|
+
expect(deviceHostnames(devices)).toEqual([
|
|
486
|
+
"",
|
|
487
|
+
"10.18.166.53",
|
|
488
|
+
"10.18.166.54",
|
|
489
|
+
]);
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
it("normalises to a fixed point, so every reader of a row agrees", () => {
|
|
493
|
+
/*
|
|
494
|
+
* The chain has to be IDEMPOTENT, because it is not run once.
|
|
495
|
+
*
|
|
496
|
+
* The dashboard normalises the scan's rows when the Review dialog opens
|
|
497
|
+
* and again on re-render; the server-side auto-import rule engine
|
|
498
|
+
* normalises its own copy of the same jsonb; the import loop names a host
|
|
499
|
+
* that has already been through both. If a second pass could change an
|
|
500
|
+
* answer, the name an operator ticked and the name the device was created
|
|
501
|
+
* with could differ — which is the exact class of bug the shared
|
|
502
|
+
* normaliser was written to end.
|
|
503
|
+
*
|
|
504
|
+
* The payload is chosen so the first pass genuinely CHANGES every row:
|
|
505
|
+
* a root dot is stripped, a padded address is trimmed, a numeric sysName
|
|
506
|
+
* is blanked, and a rejected PTR key is deleted. A payload that was
|
|
507
|
+
* already clean would make this test pass no matter what.
|
|
508
|
+
*/
|
|
509
|
+
const payload: Array<unknown> = [
|
|
510
|
+
{
|
|
511
|
+
ipAddress: "10.18.166.51",
|
|
512
|
+
snmpReachable: false,
|
|
513
|
+
dnsHostname: "hq-router-01.corp.example.net.",
|
|
514
|
+
},
|
|
515
|
+
{ ipAddress: " 10.18.166.53 ", sysName: 42 },
|
|
516
|
+
{
|
|
517
|
+
ipAddress: "10.18.166.54",
|
|
518
|
+
snmpReachable: false,
|
|
519
|
+
dnsHostname: "in-addr.arpa",
|
|
520
|
+
},
|
|
521
|
+
];
|
|
522
|
+
|
|
523
|
+
const stored: Array<DiscoveredNetworkDevice> = storedScanResults(payload);
|
|
524
|
+
const once: Array<DiscoveredNetworkDevice> =
|
|
525
|
+
normalizeDiscoveredHosts(stored);
|
|
526
|
+
const twice: Array<DiscoveredNetworkDevice> =
|
|
527
|
+
normalizeDiscoveredHosts(once);
|
|
528
|
+
|
|
529
|
+
/*
|
|
530
|
+
* `toStrictEqual`, NOT `toEqual`. `toEqual` ignores keys whose value is
|
|
531
|
+
* `undefined`, so it cannot tell `delete row.dnsHostname` from
|
|
532
|
+
* `row.dnsHostname = undefined` — and that distinction IS the contract
|
|
533
|
+
* ("a reader that checks `if (host.dnsHostname)` and one that checks
|
|
534
|
+
* `"dnsHostname" in host` cannot disagree"). With `toEqual` this
|
|
535
|
+
* assertion would stay green through exactly the regression it is here to
|
|
536
|
+
* catch. The key lists are compared as well, so the failure message names
|
|
537
|
+
* the key that appeared or vanished rather than just the row.
|
|
538
|
+
*/
|
|
539
|
+
expect(twice).toStrictEqual(once);
|
|
540
|
+
expect(
|
|
541
|
+
twice.map((host: DiscoveredNetworkDevice): Array<string> => {
|
|
542
|
+
return Object.keys(host).sort();
|
|
543
|
+
}),
|
|
544
|
+
).toEqual(
|
|
545
|
+
once.map((host: DiscoveredNetworkDevice): Array<string> => {
|
|
546
|
+
return Object.keys(host).sort();
|
|
547
|
+
}),
|
|
548
|
+
);
|
|
549
|
+
|
|
550
|
+
// The rejected PTR key is absent after BOTH passes, not present-and-blank.
|
|
551
|
+
expect(once[2]).not.toHaveProperty("dnsHostname");
|
|
552
|
+
expect(twice[2]).not.toHaveProperty("dnsHostname");
|
|
553
|
+
|
|
554
|
+
const namesOnce: Array<string> = once.map(
|
|
555
|
+
(host: DiscoveredNetworkDevice): string => {
|
|
556
|
+
return buildDeviceName(host);
|
|
557
|
+
},
|
|
558
|
+
);
|
|
559
|
+
const namesTwice: Array<string> = twice.map(
|
|
560
|
+
(host: DiscoveredNetworkDevice): string => {
|
|
561
|
+
return buildDeviceName(host);
|
|
562
|
+
},
|
|
563
|
+
);
|
|
564
|
+
|
|
565
|
+
expect(namesOnce).toEqual([
|
|
566
|
+
"hq-router-01.corp.example.net",
|
|
567
|
+
"10.18.166.53",
|
|
568
|
+
"10.18.166.54",
|
|
569
|
+
]);
|
|
570
|
+
expect(namesTwice).toEqual(namesOnce);
|
|
571
|
+
});
|
|
572
|
+
|
|
573
|
+
it("gives a wildcard PTR zone one shared name and three distinct fallbacks", () => {
|
|
574
|
+
/*
|
|
575
|
+
* A wildcard reverse zone — "*.166.18.10.in-addr.arpa PTR
|
|
576
|
+
* unassigned.dhcp.corp.example.net" — is common on DHCP ranges, and it is
|
|
577
|
+
* what makes the naming feature collide with a database constraint:
|
|
578
|
+
* NetworkDevice names are unique per project, so the first host imports
|
|
579
|
+
* and the next two fail the create.
|
|
580
|
+
*
|
|
581
|
+
* Both halves are load-bearing and pull in opposite directions. The
|
|
582
|
+
* primary name must be SHARED (it is what the resolver actually said, and
|
|
583
|
+
* showing three different names for one PTR record would be a fiction),
|
|
584
|
+
* while the retry name must be DISTINCT or the retry is pointless — the
|
|
585
|
+
* second create fails exactly as the first did. The address is the only
|
|
586
|
+
* thing that tells these three hosts apart, which is why it is what the
|
|
587
|
+
* fallback appends.
|
|
588
|
+
*/
|
|
589
|
+
const payload: Array<unknown> = [
|
|
590
|
+
{
|
|
591
|
+
ipAddress: "10.18.166.51",
|
|
592
|
+
snmpReachable: false,
|
|
593
|
+
dnsHostname: "unassigned.dhcp.corp.example.net",
|
|
594
|
+
},
|
|
595
|
+
{
|
|
596
|
+
ipAddress: "10.18.166.53",
|
|
597
|
+
snmpReachable: false,
|
|
598
|
+
dnsHostname: "unassigned.dhcp.corp.example.net",
|
|
599
|
+
},
|
|
600
|
+
{
|
|
601
|
+
ipAddress: "10.18.166.54",
|
|
602
|
+
snmpReachable: false,
|
|
603
|
+
dnsHostname: "unassigned.dhcp.corp.example.net",
|
|
604
|
+
},
|
|
605
|
+
];
|
|
606
|
+
|
|
607
|
+
const hosts: Array<DiscoveredNetworkDevice> = hostsFromPayload(payload);
|
|
608
|
+
|
|
609
|
+
expect(
|
|
610
|
+
hosts.map((host: DiscoveredNetworkDevice): string => {
|
|
611
|
+
return buildDeviceName(host);
|
|
612
|
+
}),
|
|
613
|
+
).toEqual([
|
|
614
|
+
"unassigned.dhcp.corp.example.net",
|
|
615
|
+
"unassigned.dhcp.corp.example.net",
|
|
616
|
+
"unassigned.dhcp.corp.example.net",
|
|
617
|
+
]);
|
|
618
|
+
|
|
619
|
+
const fallbacks: Array<string> = hosts.map(
|
|
620
|
+
(host: DiscoveredNetworkDevice): string => {
|
|
621
|
+
return buildFallbackDeviceName(host);
|
|
622
|
+
},
|
|
623
|
+
);
|
|
624
|
+
|
|
625
|
+
expect(fallbacks).toEqual([
|
|
626
|
+
"unassigned.dhcp.corp.example.net (10.18.166.51)",
|
|
627
|
+
"unassigned.dhcp.corp.example.net (10.18.166.53)",
|
|
628
|
+
"unassigned.dhcp.corp.example.net (10.18.166.54)",
|
|
629
|
+
]);
|
|
630
|
+
|
|
631
|
+
// The property the retry depends on, stated as a property.
|
|
632
|
+
expect(new Set<string>(fallbacks).size).toBe(3);
|
|
633
|
+
});
|
|
634
|
+
|
|
635
|
+
it("keeps the collision fallback under the name ceiling however long the PTR name and the address are", () => {
|
|
636
|
+
/*
|
|
637
|
+
* The wildcard case above with a name that is already AT the ceiling,
|
|
638
|
+
* which is the realistic version: long DHCP zone names are the norm, so
|
|
639
|
+
* "shared name" and "long name" arrive together, and this is the only
|
|
640
|
+
* shape where `buildFallbackDeviceName`'s own truncation does anything.
|
|
641
|
+
*
|
|
642
|
+
* The fallback appends " (address)" to a name that is already the full 80
|
|
643
|
+
* characters, so the base has to be cut AGAIN — by exactly the suffix's
|
|
644
|
+
* width — or the composed name overflows the slug and the retry fails for
|
|
645
|
+
* a different reason than the collision it was meant to fix. Distinctness
|
|
646
|
+
* has to survive that second cut, which is why the suffix goes on the END:
|
|
647
|
+
* the two names are identical for their first 65 characters.
|
|
648
|
+
*/
|
|
649
|
+
const payload: Array<unknown> = [
|
|
650
|
+
{
|
|
651
|
+
ipAddress: "10.18.166.51",
|
|
652
|
+
snmpReachable: false,
|
|
653
|
+
dnsHostname: LONG_PTR_NAME,
|
|
654
|
+
},
|
|
655
|
+
{
|
|
656
|
+
ipAddress: "10.18.166.53",
|
|
657
|
+
snmpReachable: false,
|
|
658
|
+
dnsHostname: LONG_PTR_NAME,
|
|
659
|
+
},
|
|
660
|
+
];
|
|
661
|
+
|
|
662
|
+
const hosts: Array<DiscoveredNetworkDevice> = hostsFromPayload(payload);
|
|
663
|
+
|
|
664
|
+
const fallbacks: Array<string> = hosts.map(
|
|
665
|
+
(host: DiscoveredNetworkDevice): string => {
|
|
666
|
+
return buildFallbackDeviceName(host);
|
|
667
|
+
},
|
|
668
|
+
);
|
|
669
|
+
|
|
670
|
+
/*
|
|
671
|
+
* 65 characters of name (80 minus the 15-character suffix) plus the
|
|
672
|
+
* suffix. Written out rather than computed so a regression that cut to
|
|
673
|
+
* the wrong width is a visible diff and not an arithmetic identity.
|
|
674
|
+
*/
|
|
675
|
+
expect(fallbacks).toEqual([
|
|
676
|
+
`${LONG_LABEL}.d (10.18.166.51)`,
|
|
677
|
+
`${LONG_LABEL}.d (10.18.166.53)`,
|
|
678
|
+
]);
|
|
679
|
+
|
|
680
|
+
for (const fallback of fallbacks) {
|
|
681
|
+
expect(fallback.length).toBe(MAX_NAME_LENGTH);
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
expect(new Set<string>(fallbacks).size).toBe(2);
|
|
685
|
+
|
|
686
|
+
/*
|
|
687
|
+
* The degenerate end of the same branch: an "address" longer than the
|
|
688
|
+
* whole name ceiling, so the suffix alone overflows it and the width left
|
|
689
|
+
* for the base name goes NEGATIVE. `ipAddress` is not validated anywhere
|
|
690
|
+
* on this path — it is whatever the probe wrote into jsonb — so a broken
|
|
691
|
+
* probe build really can put a line of sweep output here.
|
|
692
|
+
*
|
|
693
|
+
* TWO floors have to hold at once, and they pull against each other.
|
|
694
|
+
* Without the `Math.max(1, ...)`, `substring(0, -6)` returns "" and the
|
|
695
|
+
* device is named for the junk address alone, with nothing of the
|
|
696
|
+
* resolver's answer left in it. But that floor is also what used to let
|
|
697
|
+
* the COMPOSED name run past the ceiling — one base character plus an
|
|
698
|
+
* 83-character suffix is 84 — and a name over the ceiling fails the create
|
|
699
|
+
* on the slug's own length, which is a different error than the collision
|
|
700
|
+
* the retry was for, on a path whose whole job is to recover from one.
|
|
701
|
+
*
|
|
702
|
+
* So the composition is clamped as a whole: the first character of the
|
|
703
|
+
* name survives, and the result still fits.
|
|
704
|
+
*/
|
|
705
|
+
const junkAddress: string = `10.18.166.55-${"z".repeat(70)}`;
|
|
706
|
+
const junkHosts: Array<DiscoveredNetworkDevice> = hostsFromPayload([
|
|
707
|
+
{
|
|
708
|
+
ipAddress: junkAddress,
|
|
709
|
+
snmpReachable: false,
|
|
710
|
+
dnsHostname: "hq-nas-01.corp.example.net",
|
|
711
|
+
},
|
|
712
|
+
]);
|
|
713
|
+
|
|
714
|
+
const junkFallback: string = buildFallbackDeviceName(junkHosts[0]!);
|
|
715
|
+
|
|
716
|
+
expect(junkFallback.length).toBe(MAX_NAME_LENGTH);
|
|
717
|
+
expect(junkFallback.startsWith("h (10.18.166.55-")).toBe(true);
|
|
718
|
+
});
|
|
719
|
+
|
|
720
|
+
it("creates the retried device under the fallback name while still addressing it by IP", () => {
|
|
721
|
+
/*
|
|
722
|
+
* The retry as `importSelectedDevices` and the rule engine actually
|
|
723
|
+
* perform it, rather than as a string.
|
|
724
|
+
*
|
|
725
|
+
* Both paths build the device, catch the unique-name failure, and rebuild
|
|
726
|
+
* it with `name: buildFallbackDeviceName(host)`. Two things have to hold
|
|
727
|
+
* for that to work and neither is visible from the naming functions
|
|
728
|
+
* alone: the builder must PREFER the supplied name over its own default
|
|
729
|
+
* (otherwise the retry recreates the colliding name and fails again), and
|
|
730
|
+
* the retry must change nothing else — above all not `hostname`, because
|
|
731
|
+
* a retried device that addressed itself by name would poll the wrong
|
|
732
|
+
* thing forever.
|
|
733
|
+
*
|
|
734
|
+
* The default half is pinned on the same host in the same test, so the
|
|
735
|
+
* two names are visibly different strings produced by one builder.
|
|
736
|
+
*/
|
|
737
|
+
const host: DiscoveredNetworkDevice = hostsFromPayload([
|
|
738
|
+
{
|
|
739
|
+
ipAddress: "10.18.166.51",
|
|
740
|
+
snmpReachable: false,
|
|
741
|
+
dnsHostname: "unassigned.dhcp.corp.example.net",
|
|
742
|
+
},
|
|
743
|
+
])[0]!;
|
|
744
|
+
|
|
745
|
+
const firstAttempt: NetworkDevice = buildNetworkDeviceFromDiscoveredHost({
|
|
746
|
+
projectId: PROJECT_ID,
|
|
747
|
+
host: host,
|
|
748
|
+
scan: PING_ONLY_SCAN,
|
|
749
|
+
});
|
|
750
|
+
|
|
751
|
+
// No name supplied: the builder's default IS the displayed name.
|
|
752
|
+
expect(firstAttempt.name).toBe("unassigned.dhcp.corp.example.net");
|
|
753
|
+
|
|
754
|
+
const retry: NetworkDevice = buildNetworkDeviceFromDiscoveredHost({
|
|
755
|
+
projectId: PROJECT_ID,
|
|
756
|
+
host: host,
|
|
757
|
+
scan: PING_ONLY_SCAN,
|
|
758
|
+
name: buildFallbackDeviceName(host),
|
|
759
|
+
});
|
|
760
|
+
|
|
761
|
+
expect(retry.name).toBe("unassigned.dhcp.corp.example.net (10.18.166.51)");
|
|
762
|
+
expect(retry.name).not.toBe(firstAttempt.name);
|
|
763
|
+
|
|
764
|
+
// Everything the retry must NOT have changed.
|
|
765
|
+
expect(retry.hostname).toBe("10.18.166.51");
|
|
766
|
+
expect(retry.hostname).toBe(firstAttempt.hostname);
|
|
767
|
+
expect(retry.monitoringMethod).toBe(NetworkDeviceMonitoringMethod.Monitor);
|
|
768
|
+
expect(retry.isPollingEnabled).toBe(false);
|
|
769
|
+
expect(retry.projectId).toBe(PROJECT_ID);
|
|
770
|
+
});
|
|
771
|
+
|
|
772
|
+
it("stringifies a numeric address while naming the host by its PTR record", () => {
|
|
773
|
+
/*
|
|
774
|
+
* A number in `ipAddress` is a shipped probe bug the normaliser exists to
|
|
775
|
+
* absorb: the selection record keys the host as the string "10" (object
|
|
776
|
+
* keys always are), while the imported-set is matched with `Set.has(10)`,
|
|
777
|
+
* which does not coerce — so the host imported and then could never be
|
|
778
|
+
* retired, and pressing Import again duplicated it.
|
|
779
|
+
*
|
|
780
|
+
* With a PTR name on the same row it becomes a chain claim, and the two
|
|
781
|
+
* fields must be treated OPPOSITELY: `ipAddress` is stringified (it is
|
|
782
|
+
* still an address, just badly typed) while `sysName` and `dnsHostname`
|
|
783
|
+
* are not (see the sysName test below). `hostname` must come out as the
|
|
784
|
+
* string "10", because NetworkDeviceService.getRegisteredHostnames dedups
|
|
785
|
+
* against a Set of strings and a number would silently never match.
|
|
786
|
+
*/
|
|
787
|
+
const hosts: Array<DiscoveredNetworkDevice> = hostsFromPayload([
|
|
788
|
+
{
|
|
789
|
+
ipAddress: 10,
|
|
790
|
+
snmpReachable: false,
|
|
791
|
+
dnsHostname: "hq-gw-01.corp.example.net",
|
|
792
|
+
},
|
|
793
|
+
]);
|
|
794
|
+
|
|
795
|
+
expect(hosts[0]?.ipAddress).toBe("10");
|
|
796
|
+
|
|
797
|
+
const device: NetworkDevice = devicesFromPayload([
|
|
798
|
+
{
|
|
799
|
+
ipAddress: 10,
|
|
800
|
+
snmpReachable: false,
|
|
801
|
+
dnsHostname: "hq-gw-01.corp.example.net",
|
|
802
|
+
},
|
|
803
|
+
])[0]!;
|
|
804
|
+
|
|
805
|
+
expect(device.name).toBe("hq-gw-01.corp.example.net");
|
|
806
|
+
expect(device.hostname).toBe("10");
|
|
807
|
+
// `toBe("10")` would also pass for the number under `==`, so say it.
|
|
808
|
+
expect(typeof device.hostname).toBe("string");
|
|
809
|
+
});
|
|
810
|
+
|
|
811
|
+
it("preserves a PTR record's own capitalisation and still rejects an upper-case in-addr.arpa echo", () => {
|
|
812
|
+
/*
|
|
813
|
+
* Case is a joint, in both directions, and neither direction is visible
|
|
814
|
+
* from one module.
|
|
815
|
+
*
|
|
816
|
+
* (a) ReverseDnsNameUtil PRESERVES case deliberately — the reverse zone's
|
|
817
|
+
* spelling is as close to the operator's intent as this data gets —
|
|
818
|
+
* so a device must be created as "HQ-FileServer.Corp.Example.NET".
|
|
819
|
+
* Any lower-casing added for "consistency" between here and the
|
|
820
|
+
* device name would be caught here.
|
|
821
|
+
* (b) The zone check compares the LOWER-CASED name but the function
|
|
822
|
+
* returns the original. A resolver that echoes the query name back in
|
|
823
|
+
* upper case (they exist; DNS is case-insensitive on the wire and
|
|
824
|
+
* 0x20 encoding makes mixed-case echoes ordinary) must still be
|
|
825
|
+
* rejected. Comparing the un-lowered string against the zone list is
|
|
826
|
+
* the classic version of this bug, and every other hostile-value case
|
|
827
|
+
* in this file spells the zone in lower case, so this row is the only
|
|
828
|
+
* one that would catch it.
|
|
829
|
+
*/
|
|
830
|
+
const payload: Array<unknown> = [
|
|
831
|
+
{
|
|
832
|
+
ipAddress: "10.18.166.51",
|
|
833
|
+
snmpReachable: false,
|
|
834
|
+
dnsHostname: "HQ-FileServer.Corp.Example.NET",
|
|
835
|
+
},
|
|
836
|
+
{
|
|
837
|
+
ipAddress: "10.18.166.53",
|
|
838
|
+
snmpReachable: false,
|
|
839
|
+
dnsHostname: "53.166.18.10.IN-ADDR.ARPA",
|
|
840
|
+
},
|
|
841
|
+
// The apex spelled the same way, which a suffix-only check misses.
|
|
842
|
+
{
|
|
843
|
+
ipAddress: "10.18.166.54",
|
|
844
|
+
snmpReachable: false,
|
|
845
|
+
dnsHostname: "In-Addr.Arpa.",
|
|
846
|
+
},
|
|
847
|
+
];
|
|
848
|
+
|
|
849
|
+
const hosts: Array<DiscoveredNetworkDevice> = hostsFromPayload(payload);
|
|
850
|
+
|
|
851
|
+
expect(hosts[0]?.dnsHostname).toBe("HQ-FileServer.Corp.Example.NET");
|
|
852
|
+
expect(hosts[1]).not.toHaveProperty("dnsHostname");
|
|
853
|
+
expect(hosts[2]).not.toHaveProperty("dnsHostname");
|
|
854
|
+
|
|
855
|
+
expect(deviceNames(devicesFromPayload(payload))).toEqual([
|
|
856
|
+
"HQ-FileServer.Corp.Example.NET",
|
|
857
|
+
"10.18.166.53",
|
|
858
|
+
"10.18.166.54",
|
|
859
|
+
]);
|
|
860
|
+
});
|
|
861
|
+
|
|
862
|
+
it("blanks a non-string sysName so the PTR name wins, rather than stringifying it", () => {
|
|
863
|
+
/*
|
|
864
|
+
* `sysName` comes out of the same verbatim jsonb blob as everything else,
|
|
865
|
+
* so its declared `string | undefined` type describes what the probe
|
|
866
|
+
* SHOULD send, not what is stored. `(42).trim()` is a TypeError thrown
|
|
867
|
+
* during render, which takes out the whole modal rather than one row.
|
|
868
|
+
*
|
|
869
|
+
* EXPECTED BEHAVIOUR, stated plainly because the interesting part is what
|
|
870
|
+
* does NOT happen. A non-string sysName is BLANKED, not coerced, and that
|
|
871
|
+
* is the difference between this field and `ipAddress` (which IS
|
|
872
|
+
* stringified, as the numeric-address test above pins). Blanking is what
|
|
873
|
+
* these rows prove is right:
|
|
874
|
+
*
|
|
875
|
+
* - sysName 42 -> "" -> the PTR name wins. Not "42".
|
|
876
|
+
* - sysName {...} -> "" -> the PTR name wins. NOT "[object Object]",
|
|
877
|
+
* which is what `String()` returns and which, being
|
|
878
|
+
* truthy, would beat a perfectly good PTR record on
|
|
879
|
+
* the very same row.
|
|
880
|
+
* - sysName null -> "" -> the PTR name wins. NOT "null" (typeof null
|
|
881
|
+
* is "object", so it is not the `undefined` the
|
|
882
|
+
* guard already skips).
|
|
883
|
+
*
|
|
884
|
+
* The blanking is asserted on the ROW as well as through the name,
|
|
885
|
+
* because the two layers guard it independently: delete the blanking from
|
|
886
|
+
* normalizeDiscoveredHosts and getDiscoveredHostDisplayName's own `typeof`
|
|
887
|
+
* check still produces every name below. Only `hosts[n].sysName` can tell
|
|
888
|
+
* that the normalised row — which is what an auto-import rule matches on
|
|
889
|
+
* and what the selection record holds — was cleaned too.
|
|
890
|
+
*
|
|
891
|
+
* The fourth row is the one that makes blanking visible in the NAME as
|
|
892
|
+
* well: junk in `sysName` and no PTR record either, so the name has
|
|
893
|
+
* nowhere to fall but the address.
|
|
894
|
+
*/
|
|
895
|
+
const payload: Array<unknown> = [
|
|
896
|
+
{
|
|
897
|
+
ipAddress: "10.18.166.51",
|
|
898
|
+
sysName: 42,
|
|
899
|
+
dnsHostname: "hq-switch-01.corp.example.net",
|
|
900
|
+
},
|
|
901
|
+
{
|
|
902
|
+
ipAddress: "10.18.166.53",
|
|
903
|
+
sysName: { oid: "1.3.6.1.2.1.1.5.0" },
|
|
904
|
+
dnsHostname: "hq-switch-02.corp.example.net",
|
|
905
|
+
},
|
|
906
|
+
{
|
|
907
|
+
ipAddress: "10.18.166.54",
|
|
908
|
+
sysName: null,
|
|
909
|
+
dnsHostname: "hq-switch-03.corp.example.net",
|
|
910
|
+
},
|
|
911
|
+
{ ipAddress: "10.18.166.56", sysName: 42 },
|
|
912
|
+
/*
|
|
913
|
+
* An ABSENT sysName must not GAIN the key: `sysName` is optional and
|
|
914
|
+
* `"sysName" in host` is a question other code is entitled to ask. Note
|
|
915
|
+
* `sysName: undefined` is erased by the JSON round trip, so "absent" is
|
|
916
|
+
* the only form the column can actually store.
|
|
917
|
+
*/
|
|
918
|
+
{
|
|
919
|
+
ipAddress: "10.18.166.57",
|
|
920
|
+
sysName: undefined,
|
|
921
|
+
dnsHostname: "hq-switch-04.corp.example.net",
|
|
922
|
+
},
|
|
923
|
+
];
|
|
924
|
+
|
|
925
|
+
const hosts: Array<DiscoveredNetworkDevice> = hostsFromPayload(payload);
|
|
926
|
+
|
|
927
|
+
expect(hosts[0]?.sysName).toBe("");
|
|
928
|
+
expect(hosts[1]?.sysName).toBe("");
|
|
929
|
+
expect(hosts[2]?.sysName).toBe("");
|
|
930
|
+
expect(hosts[3]?.sysName).toBe("");
|
|
931
|
+
expect(hosts[4]).not.toHaveProperty("sysName");
|
|
932
|
+
|
|
933
|
+
const devices: Array<NetworkDevice> = devicesFromPayload(payload);
|
|
934
|
+
|
|
935
|
+
expect(deviceNames(devices)).toEqual([
|
|
936
|
+
"hq-switch-01.corp.example.net",
|
|
937
|
+
"hq-switch-02.corp.example.net",
|
|
938
|
+
"hq-switch-03.corp.example.net",
|
|
939
|
+
"10.18.166.56",
|
|
940
|
+
"hq-switch-04.corp.example.net",
|
|
941
|
+
]);
|
|
942
|
+
|
|
943
|
+
expect(deviceHostnames(devices)).toEqual([
|
|
944
|
+
"10.18.166.51",
|
|
945
|
+
"10.18.166.53",
|
|
946
|
+
"10.18.166.54",
|
|
947
|
+
"10.18.166.56",
|
|
948
|
+
"10.18.166.57",
|
|
949
|
+
]);
|
|
950
|
+
});
|
|
951
|
+
|
|
952
|
+
it("leaves a whitespace-only sysName on the row but never lets it become a name", () => {
|
|
953
|
+
/*
|
|
954
|
+
* The one place the two layers deliberately DISAGREE, which is why it can
|
|
955
|
+
* only be pinned end to end.
|
|
956
|
+
*
|
|
957
|
+
* normalizeDiscoveredHosts passes a string sysName through untouched — it
|
|
958
|
+
* only rewrites non-strings — so " " is still " " on the row an
|
|
959
|
+
* auto-import rule matches against. getDiscoveredHostDisplayName trims
|
|
960
|
+
* before testing for emptiness, so the same value does not name anything
|
|
961
|
+
* and the PTR record wins.
|
|
962
|
+
*
|
|
963
|
+
* Both halves are asserted because either one alone is satisfiable by the
|
|
964
|
+
* wrong implementation: if the normaliser started trimming, the row
|
|
965
|
+
* assertion fails; if the display stopped trimming, " " is truthy, it
|
|
966
|
+
* beats the PTR record, and a device is created whose name is three
|
|
967
|
+
* spaces and whose slug is empty.
|
|
968
|
+
*/
|
|
969
|
+
const payload: Array<unknown> = [
|
|
970
|
+
{
|
|
971
|
+
ipAddress: "10.18.166.51",
|
|
972
|
+
sysName: " ",
|
|
973
|
+
dnsHostname: "hq-esxi-04.corp.example.net",
|
|
974
|
+
},
|
|
975
|
+
// The same blank sysName with nothing to fall through to but the address.
|
|
976
|
+
{ ipAddress: "10.18.166.53", sysName: " " },
|
|
977
|
+
];
|
|
978
|
+
|
|
979
|
+
const hosts: Array<DiscoveredNetworkDevice> = hostsFromPayload(payload);
|
|
980
|
+
|
|
981
|
+
expect(hosts[0]?.sysName).toBe(" ");
|
|
982
|
+
expect(hosts[1]?.sysName).toBe(" ");
|
|
983
|
+
|
|
984
|
+
expect(namesFromPayload(payload)).toEqual([
|
|
985
|
+
"hq-esxi-04.corp.example.net",
|
|
986
|
+
"10.18.166.53",
|
|
987
|
+
]);
|
|
988
|
+
|
|
989
|
+
expect(deviceNames(devicesFromPayload(payload))).toEqual([
|
|
990
|
+
"hq-esxi-04.corp.example.net",
|
|
991
|
+
"10.18.166.53",
|
|
992
|
+
]);
|
|
993
|
+
});
|
|
994
|
+
|
|
995
|
+
it("gives two rows for one address a single registration state but two different PTR names", () => {
|
|
996
|
+
/*
|
|
997
|
+
* The dedup pass and the naming feature meet here, and the meeting is
|
|
998
|
+
* only visible in the chain.
|
|
999
|
+
*
|
|
1000
|
+
* A scan can list the same address twice — two interfaces answering, a
|
|
1001
|
+
* re-scan merged in, a probe retry — and the rows need not agree. The
|
|
1002
|
+
* normaliser fixes ONE of those disagreements: `isAlreadyRegistered` is a
|
|
1003
|
+
* property of the ADDRESS, so if any row says the address is registered,
|
|
1004
|
+
* every row for it does. Without that pass one checkbox governs both rows
|
|
1005
|
+
* while only one of them refuses to import, and whether an inventoried
|
|
1006
|
+
* device got created a second time depended on the probe's ordering.
|
|
1007
|
+
*
|
|
1008
|
+
* It deliberately does NOT unify the names, and it cannot: the two rows
|
|
1009
|
+
* hold two different PTR answers and neither is more true than the other.
|
|
1010
|
+
* So the operator ticks one row, sees one name, and — if the import walks
|
|
1011
|
+
* the other row — creates a device under the other name. Pinning the
|
|
1012
|
+
* disagreement is the point: it is a real hazard of naming devices by
|
|
1013
|
+
* DNS, and this assertion is where anyone who later tries to dedup rows
|
|
1014
|
+
* by address will find out that the names were never dedupable.
|
|
1015
|
+
*/
|
|
1016
|
+
const payload: Array<unknown> = [
|
|
1017
|
+
{
|
|
1018
|
+
ipAddress: "10.18.166.51",
|
|
1019
|
+
snmpReachable: false,
|
|
1020
|
+
dnsHostname: "hq-nas-01.corp.example.net",
|
|
1021
|
+
isAlreadyRegistered: true,
|
|
1022
|
+
},
|
|
1023
|
+
{
|
|
1024
|
+
ipAddress: "10.18.166.51",
|
|
1025
|
+
snmpReachable: false,
|
|
1026
|
+
dnsHostname: "hq-nas-01-old.corp.example.net",
|
|
1027
|
+
},
|
|
1028
|
+
];
|
|
1029
|
+
|
|
1030
|
+
const hosts: Array<DiscoveredNetworkDevice> = hostsFromPayload(payload);
|
|
1031
|
+
|
|
1032
|
+
// One address, one registration state, whatever order the probe sent.
|
|
1033
|
+
expect(
|
|
1034
|
+
hosts.map((host: DiscoveredNetworkDevice): boolean | undefined => {
|
|
1035
|
+
return host.isAlreadyRegistered;
|
|
1036
|
+
}),
|
|
1037
|
+
).toEqual([true, true]);
|
|
1038
|
+
|
|
1039
|
+
const names: Array<string | undefined> = deviceNames(
|
|
1040
|
+
devicesFromPayload(payload),
|
|
1041
|
+
);
|
|
1042
|
+
|
|
1043
|
+
expect(names).toEqual([
|
|
1044
|
+
"hq-nas-01.corp.example.net",
|
|
1045
|
+
"hq-nas-01-old.corp.example.net",
|
|
1046
|
+
]);
|
|
1047
|
+
// Two names for one checkbox: the hazard, stated as a property.
|
|
1048
|
+
expect(new Set<string | undefined>(names).size).toBe(2);
|
|
1049
|
+
|
|
1050
|
+
// Both still address the one host they are, which is the dedup key.
|
|
1051
|
+
expect(deviceHostnames(devicesFromPayload(payload))).toEqual([
|
|
1052
|
+
"10.18.166.51",
|
|
1053
|
+
"10.18.166.51",
|
|
1054
|
+
]);
|
|
1055
|
+
});
|
|
1056
|
+
});
|