@oneuptime/common 14.0.3 → 14.0.5
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/Alert.ts +55 -0
- package/Models/DatabaseModels/DatabaseServer.ts +1650 -0
- package/Models/DatabaseModels/DatabaseServerEndpoint.ts +540 -0
- package/Models/DatabaseModels/DatabaseServerFeed.ts +625 -0
- package/Models/DatabaseModels/DatabaseServerLabelRule.ts +514 -0
- package/Models/DatabaseModels/DatabaseServerOwnerRule.ts +596 -0
- package/Models/DatabaseModels/DatabaseServerOwnerTeam.ts +491 -0
- package/Models/DatabaseModels/DatabaseServerOwnerUser.ts +490 -0
- package/Models/DatabaseModels/Incident.ts +55 -0
- package/Models/DatabaseModels/Index.ts +16 -0
- package/Models/DatabaseModels/NetworkDeviceDiscoveryScan.ts +26 -0
- package/Models/DatabaseModels/ScheduledMaintenance.ts +55 -0
- package/Models/DatabaseModels/TelemetryException.ts +2 -0
- package/Models/DatabaseModels/UserProjectSsoConsent.ts +146 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1795000000000-AddDatabaseServerTables.ts +673 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1795100000000-AddUserProjectSsoConsent.ts +51 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +4 -0
- package/Server/Middleware/MultipartFormData.ts +1 -1
- package/Server/Middleware/TelemetryIngest.ts +15 -8
- package/Server/Services/AlertReminderRuleService.ts +100 -24
- package/Server/Services/DatabaseServerEndpointService.ts +895 -0
- package/Server/Services/DatabaseServerFeedService.ts +128 -0
- package/Server/Services/DatabaseServerLabelRuleEngineService.ts +340 -0
- package/Server/Services/DatabaseServerLabelRuleService.ts +14 -0
- package/Server/Services/DatabaseServerOwnerRuleEngineService.ts +425 -0
- package/Server/Services/DatabaseServerOwnerRuleService.ts +14 -0
- package/Server/Services/DatabaseServerOwnerTeamService.ts +180 -0
- package/Server/Services/DatabaseServerOwnerUserService.ts +173 -0
- package/Server/Services/DatabaseServerService.ts +4546 -0
- package/Server/Services/DatabaseService.ts +14 -8
- package/Server/Services/DockerResourceService.ts +43 -2
- package/Server/Services/IncidentReminderRuleService.ts +103 -23
- package/Server/Services/Index.ts +16 -0
- package/Server/Services/InventoryItemService.ts +113 -5
- package/Server/Services/IoTDeviceService.ts +145 -37
- package/Server/Services/OpenTelemetryIngestService.ts +19 -0
- package/Server/Services/PodmanResourceService.ts +43 -2
- package/Server/Services/ScheduledMaintenanceReminderRuleService.ts +107 -23
- package/Server/Services/ServiceService.ts +1 -1
- package/Server/Services/TelemetryUsageBillingService.ts +29 -2
- package/Server/Services/UserProjectSsoConsentService.ts +74 -0
- package/Server/Services/UserService.ts +14 -1
- package/Server/Types/Database/Permissions/OwnerTableRegistry.ts +18 -0
- package/Server/Types/Database/QueryHelper.ts +30 -1
- package/Server/Utils/AI/Toolbox/ResourceTools.ts +69 -1
- package/Server/Utils/Database/AffectedResourceRelations.ts +6 -0
- package/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.ts +188 -6
- package/Server/Utils/Monitor/MonitorAlert.ts +28 -1
- package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +2262 -755
- package/Server/Utils/Monitor/MonitorIncident.ts +28 -1
- package/Server/Utils/Monitor/MonitorMaintenanceSuppression.ts +268 -6
- package/Server/Utils/Monitor/MonitorResource.ts +47 -11
- package/Server/Utils/Monitor/MonitorResourceContext.ts +1 -0
- package/Server/Utils/Monitor/MonitorStepResourceIdentity.ts +239 -21
- package/Server/Utils/Monitor/PerSeriesResolutionRootCause.ts +43 -0
- package/Server/Utils/Monitor/SeriesResourceLabels.ts +291 -0
- package/Server/Utils/Monitor/SeriesResourceLinker.ts +111 -12
- package/Server/Utils/Rules/RuleRun/RuleRunRegistry.ts +25 -0
- package/Server/Utils/StartServer.ts +26 -6
- package/Server/Utils/TeamMember/ProjectLeaveResourceCleanup.ts +2 -0
- package/Server/Utils/Telemetry/DatabaseEndpointDiscovery.ts +688 -0
- package/Server/Utils/Telemetry/EntityRegistry.ts +5 -1
- package/Server/Utils/Telemetry/LogExceptionExtractor.ts +18 -1
- package/Server/Utils/Telemetry/ResourceEntityFilter.ts +175 -12
- package/Server/Utils/Telemetry/ResourceFacetResolver.ts +12 -0
- package/Server/Utils/Telemetry/ServiceDependencyDiscovery.ts +606 -36
- package/Server/Utils/Telemetry/Telemetry.ts +16 -0
- package/Server/Utils/Telemetry/TelemetryEntity.ts +22 -22
- package/Server/Utils/Telemetry/TelemetryIngestionKeyGuard.ts +16 -10
- package/Tests/App/Accounts/SignedInRedirect.test.tsx +241 -0
- package/Tests/App/AdminDashboard/AdminHeaderSmallScreens.test.tsx +221 -4
- package/Tests/App/Dashboard/AIInsightsResourcePageContext.test.ts +42 -0
- package/Tests/App/Dashboard/AffectedResourcesDisplay.test.tsx +100 -0
- package/Tests/App/Dashboard/AffectedResourcesPicker.test.tsx +86 -0
- package/Tests/App/Dashboard/AffectedResourcesPickerHiddenTypes.test.tsx +2 -0
- package/Tests/App/Dashboard/AffectedResourcesSummaryStep.test.tsx +19 -0
- package/Tests/App/Dashboard/BreadcrumbCoverage.test.tsx +14 -0
- package/Tests/App/Dashboard/CorrelateFilterBuilder.test.tsx +26 -1
- package/Tests/App/Dashboard/DashboardAppSsoError.test.tsx +501 -0
- package/Tests/App/Dashboard/DashboardHeaderRequests.test.tsx +64 -1
- package/Tests/App/Dashboard/DashboardHeaderSmallScreens.test.tsx +178 -18
- package/Tests/App/Dashboard/DashboardMasterPageSsoRedirect.test.tsx +444 -0
- package/Tests/App/Dashboard/DashboardNavBarForeignHiddenRule.test.tsx +573 -0
- package/Tests/App/Dashboard/DatabaseChartAxes.test.tsx +431 -0
- package/Tests/App/Dashboard/DatabaseContainerDetailBadge.test.tsx +249 -0
- package/Tests/App/Dashboard/DatabaseDashboardE2EFixes.test.ts +681 -0
- package/Tests/App/Dashboard/DatabaseDocumentationMarkdown.test.ts +1801 -0
- package/Tests/App/Dashboard/DatabaseFleetSummaryRefresh.test.tsx +280 -0
- package/Tests/App/Dashboard/DatabaseFormHelpers.test.tsx +321 -0
- package/Tests/App/Dashboard/DatabaseMetricMonitorLink.test.tsx +795 -0
- package/Tests/App/Dashboard/DatabaseOverviewHeader.test.tsx +580 -0
- package/Tests/App/Dashboard/DatabaseRecommendationsEngineMetrics.test.ts +270 -0
- package/Tests/App/Dashboard/DatabaseServerComponents.test.tsx +1486 -0
- package/Tests/App/Dashboard/DatabaseServerEngineMetricsFilter.test.ts +223 -0
- package/Tests/App/Dashboard/DatabaseServerPresentation.test.ts +435 -0
- package/Tests/App/Dashboard/DatabaseServerSummary.test.ts +186 -0
- package/Tests/App/Dashboard/DatabaseServerTelemetryQueries.test.ts +1553 -0
- package/Tests/App/Dashboard/DatabaseTableTruncation.test.tsx +326 -0
- package/Tests/App/Dashboard/DatabaseTelemetryScope.test.ts +512 -0
- package/Tests/App/Dashboard/DatabaseTelemetryTabs.test.tsx +774 -0
- package/Tests/App/Dashboard/DatabaseViewLayout.test.tsx +233 -0
- package/Tests/App/Dashboard/DatabaseWorkloadBadge.test.tsx +972 -0
- package/Tests/App/Dashboard/DiscoveryReviewInventoryRefresh.test.tsx +5 -0
- package/Tests/App/Dashboard/DiscoveryReviewUnnamedHostHint.test.tsx +1061 -0
- package/Tests/App/Dashboard/EventOverlayScope.test.ts +147 -0
- package/Tests/App/Dashboard/EventOverlayScopeHook.test.tsx +1 -0
- package/Tests/App/Dashboard/LogsResourceFacetChips.test.tsx +1 -0
- package/Tests/App/Dashboard/MetricsViewerDrillDown.test.tsx +310 -0
- package/Tests/App/Dashboard/MetricsViewerEntityScopedRows.test.tsx +495 -0
- package/Tests/App/Dashboard/MonitorCreateFromMonitorBackedDevice.test.tsx +132 -1
- package/Tests/App/Dashboard/NavigationSearchKeywords.test.tsx +5 -0
- package/Tests/App/Dashboard/ProductOrientation.test.tsx +4 -3
- package/Tests/App/Dashboard/RecommendationsLoadedContext.test.tsx +349 -0
- package/Tests/App/Dashboard/ResourceArchiveSettings.test.tsx +62 -0
- package/Tests/App/Dashboard/ResourceFeedPageWiring.test.ts +8 -1
- package/Tests/App/Dashboard/ResourceFeedPagesFilterSort.test.tsx +14 -0
- package/Tests/App/Dashboard/ScheduledMaintenanceOverviewPage.test.tsx +2 -0
- package/Tests/App/Dashboard/ServiceMapDatabaseLink.test.tsx +1085 -0
- package/Tests/App/Dashboard/TelemetryResourceRetentionSettings.test.tsx +10 -0
- package/Tests/App/Dashboard/TracesResourceFacets.test.tsx +2 -1
- package/Tests/App/StatusPage/StatusPageHeaderForeignHiddenRule.test.tsx +284 -0
- package/Tests/ClassStrings.ts +178 -0
- package/Tests/Fixtures/MobilePasskeyUser.json +1 -2
- package/Tests/ForeignHiddenRuleGuard.ts +2152 -0
- package/Tests/Models/DatabaseModels/DatabaseServerModels.test.ts +2250 -0
- package/Tests/Models/DatabaseModels/ResourceFeedModels.test.ts +24 -5
- package/Tests/Models/DatabaseModels/StaticRuleCriteriaInheritance.test.ts +4 -2
- package/Tests/RenderedMarkup.ts +518 -0
- package/Tests/ResponsiveVisibility.test.ts +490 -4
- package/Tests/ResponsiveVisibility.ts +505 -52
- package/Tests/Server/Infrastructure/Postgres/AddConfigurableRuleCriteriaMigration.test.ts +3 -0
- package/Tests/Server/Infrastructure/Postgres/AddDatabaseServerTablesMigration.test.ts +541 -0
- package/Tests/Server/Infrastructure/Postgres/AddUserProjectSsoConsentMigration.test.ts +234 -0
- package/Tests/Server/Services/ContainerSnapshotUpsertPostgres.test.ts +319 -0
- package/Tests/Server/Services/CrossProjectRelationWriteGuard.test.ts +13 -0
- package/Tests/Server/Services/DatabaseServerEndpointService.test.ts +2033 -0
- package/Tests/Server/Services/DatabaseServerFeedAndOwnerServices.test.ts +529 -0
- package/Tests/Server/Services/DatabaseServerLifecycle.test.ts +3302 -0
- package/Tests/Server/Services/DatabaseServerRegistries.test.ts +603 -0
- package/Tests/Server/Services/DatabaseServerRuleEngines.test.ts +754 -0
- package/Tests/Server/Services/DatabaseServerService.test.ts +3558 -0
- package/Tests/Server/Services/DatabaseServerSqlPostgres.test.ts +2045 -0
- package/Tests/Server/Services/DatabaseServiceUpdateColumnsIfUnlockedPostgres.test.ts +362 -0
- package/Tests/Server/Services/DatabaseServiceUpdateColumnsIfUnlockedReply.test.ts +210 -0
- package/Tests/Server/Services/HookFreeWriteLengthClamp.test.ts +18 -0
- package/Tests/Server/Services/IncidentAlertSloLinkSurvivesEdit.test.ts +2 -0
- package/Tests/Server/Services/IncomingCallPolicyEscalationRuleTarget.test.ts +225 -0
- package/Tests/Server/Services/InventoryItemDisplayName.test.ts +280 -2
- package/Tests/Server/Services/InventoryUpsertSchemaParity.test.ts +8 -2
- package/Tests/Server/Services/IoTDeviceLatestMetricsPostgres.test.ts +689 -0
- package/Tests/Server/Services/IoTDeviceService.test.ts +379 -0
- package/Tests/Server/Services/LatestMetricsValuesShape.test.ts +360 -0
- package/Tests/Server/Services/LlmModelPriceService.test.ts +402 -0
- package/Tests/Server/Services/OpenTelemetryResourceRetentionMemo.test.ts +7 -1
- package/Tests/Server/Services/OwnerRowUniqueness.test.ts +61 -9
- package/Tests/Server/Services/ReminderRuleLookupPostgres.test.ts +2250 -0
- package/Tests/Server/Services/ReminderRuleRefreshOpenSubjectsPaging.test.ts +653 -0
- package/Tests/Server/Services/ResourceUpdateLastSeenLivenessFallback.test.ts +68 -20
- package/Tests/Server/Services/StaticRuleCriteriaRuntimeCoverage.test.ts +3 -3
- package/Tests/Server/Services/TelemetryResourceFacetCounting.test.ts +9 -0
- package/Tests/Server/Services/UserProjectSsoConsentPostgres.test.ts +153 -0
- package/Tests/Server/Services/UserProjectSsoConsentService.test.ts +192 -0
- package/Tests/Server/Services/UserServiceFirstMasterAdminElection.test.ts +71 -0
- package/Tests/Server/Services/UserSignupIdTakeoverPostgres.test.ts +238 -0
- package/Tests/Server/Services/WorkspaceNotificationSummaryHelpers.test.ts +592 -0
- package/Tests/Server/Types/Billing/PayAsYouGoMeteredAuthorization.test.ts +3 -0
- package/Tests/Server/Types/Database/EventOverlayScopePostgres.test.ts +1 -0
- package/Tests/Server/Types/Database/FeedEventTypeFilterQuery.test.ts +11 -0
- package/Tests/Server/Types/Database/Permissions/InfrastructureOwnershipScope.test.ts +8 -0
- package/Tests/Server/Types/Database/QueryHelperBooleanForCriteriaBackedRule.test.ts +1094 -0
- package/Tests/Server/Utils/AI/ResourceTools.test.ts +134 -0
- package/Tests/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.test.ts +41 -0
- package/Tests/Server/Utils/Monitor/Criteria/MetricMonitorCriteriaAnomalyUnits.test.ts +279 -9
- package/Tests/Server/Utils/Monitor/Criteria/MetricMonitorCriteriaDisplayContext.test.ts +933 -0
- package/Tests/Server/Utils/Monitor/DatabaseMaintenanceSuppression.test.ts +582 -0
- package/Tests/Server/Utils/Monitor/DatabaseServerMonitorLinking.test.ts +753 -0
- package/Tests/Server/Utils/Monitor/MonitorActionTimestamps.test.ts +1 -0
- package/Tests/Server/Utils/Monitor/MonitorAlertCriteriaReferences.test.ts +1 -0
- package/Tests/Server/Utils/Monitor/MonitorAlertResourceLinking.test.ts +119 -0
- package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluator.test.ts +60 -25
- package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorAffectedResourceEdgeCases.test.ts +1719 -0
- package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorKubernetesAffectedResources.test.ts +1447 -0
- package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorKubernetesRatioEmail.test.ts +352 -0
- package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorPlatformMetricUnits.test.ts +717 -0
- package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorPlatformRawScan.test.ts +2125 -0
- package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorPlatformSeriesAffectedResources.test.ts +1288 -0
- package/Tests/Server/Utils/Monitor/MonitorDependencySuppressionCreatorSkip.test.ts +1 -0
- package/Tests/Server/Utils/Monitor/MonitorIncidentCriteriaReferences.test.ts +1 -0
- package/Tests/Server/Utils/Monitor/MonitorIncidentMemberRoleMembership.test.ts +1 -0
- package/Tests/Server/Utils/Monitor/MonitorIncidentResourceLinking.test.ts +88 -0
- package/Tests/Server/Utils/Monitor/MonitorMaintenanceSuppression.test.ts +205 -0
- package/Tests/Server/Utils/Monitor/MonitorResourceContext.test.ts +62 -0
- package/Tests/Server/Utils/Monitor/MonitorResourceDatabaseMaintenance.test.ts +803 -0
- package/Tests/Server/Utils/Monitor/MonitorStepResourceIdentity.test.ts +133 -3
- package/Tests/Server/Utils/Monitor/MonitorSummaryPersistence.test.ts +1 -0
- package/Tests/Server/Utils/Monitor/PerSeriesRecoveryResolution.test.ts +472 -1
- package/Tests/Server/Utils/Monitor/PerSeriesSecondEntityAlerting.test.ts +1 -0
- package/Tests/Server/Utils/Monitor/SeriesResourceLinker.test.ts +236 -2
- package/Tests/Server/Utils/SeriesResourceLabels.test.ts +70 -0
- package/Tests/Server/Utils/StartServerProtobufContentType.test.ts +54 -0
- package/Tests/Server/Utils/Telemetry/DatabaseEndpointDiscovery.test.ts +1895 -0
- package/Tests/Server/Utils/Telemetry/EntityRegistry.test.ts +7 -0
- package/Tests/Server/Utils/Telemetry/LogExceptionExtractorSeverityGate.test.ts +90 -0
- package/Tests/Server/Utils/Telemetry/ResourceEntityFilter.test.ts +570 -0
- package/Tests/Server/Utils/Telemetry/ResourceFacetResolver.test.ts +97 -2
- package/Tests/Server/Utils/Telemetry/ServiceDependencyDiscovery.test.ts +1337 -0
- package/Tests/Server/Utils/Telemetry/TelemetryEntity.test.ts +295 -0
- package/Tests/Types/AI/AIResourceContext.test.ts +30 -0
- package/Tests/Types/DatabaseServer/DatabaseContainerClassifier.test.ts +3644 -0
- package/Tests/Types/DatabaseServer/DatabaseContainerCommand.test.ts +340 -0
- package/Tests/Types/DatabaseServer/DatabaseContainerCommandCorpus.ts +826 -0
- package/Tests/Types/DatabaseServer/DatabaseContainerCommandPostgres.test.ts +150 -0
- package/Tests/Types/DatabaseServer/DatabaseEndpoint.test.ts +2657 -0
- package/Tests/Types/DatabaseServer/DatabaseServerDiscoverySource.test.ts +64 -0
- package/Tests/Types/DatabaseServer/DatabaseServerEntityKeys.test.ts +952 -0
- package/Tests/Types/DatabaseServer/DatabaseServerMetricCatalog.test.ts +1161 -0
- package/Tests/Types/DatabaseServer/DatabaseSystem.test.ts +1514 -0
- package/Tests/Types/DatabaseServer/DatabaseTelemetryResolver.test.ts +1558 -0
- package/Tests/Types/Monitor/CephAlertTemplates.test.ts +255 -6
- package/Tests/Types/Monitor/DatabaseAlertTemplates.test.ts +2254 -0
- package/Tests/Types/Monitor/KubernetesAlertTemplates.test.ts +298 -0
- package/Tests/Types/Monitor/KubernetesMetricCatalog.test.ts +271 -0
- package/Tests/Types/Monitor/Recommendation/MonitorRecommendationCatalog.test.ts +253 -9
- package/Tests/Types/Monitor/Recommendation/MonitorRecommendationCoverage.test.ts +13 -5
- package/Tests/Types/Monitor/Recommendation/MonitorRecommendationNotificationMode.test.ts +4 -2
- package/Tests/Types/Monitor/Recommendation/MonitorRecommendationUtil.test.ts +111 -2
- package/Tests/Types/Monitor/Recommendation/RecommendationCriteriaBuilder.test.ts +61 -0
- package/Tests/Types/Monitor/SeriesContext/SeriesLabelDisplay.test.ts +114 -0
- package/Tests/Types/Monitor/Utils/KubernetesAgentEmittedMetrics.ts +181 -0
- package/Tests/Types/NetworkDevice/DiscoveredHostNamingStatus.test.ts +401 -0
- package/Tests/Types/Telemetry/ResourceEntityFacet.test.ts +4 -1
- package/Tests/Types/Telemetry/ResourceFacetCatalog.test.ts +24 -2
- package/Tests/UI/Components/Card.test.tsx +93 -2
- package/Tests/UI/Components/FeedOptions.test.ts +6 -0
- package/Tests/UI/Components/FeedOptionsButton.test.tsx +8 -1
- package/Tests/UI/Components/ForeignHiddenRuleResilience.test.tsx +1147 -0
- package/Tests/UI/Components/HeaderRightRail.test.tsx +41 -1
- package/Tests/UI/Components/LogsEntityNames.test.ts +16 -1
- package/Tests/UI/Components/LogsFacetSidebarHiddenFacets.test.tsx +7 -3
- package/Tests/UI/Components/ResourceFacetConfigs.test.ts +2 -1
- package/Tests/UI/Components/RuleCriteria/RuleCriteriaModelTable.test.tsx +3 -3
- package/Tests/UI/Components/SrOnlyForeignRule.test.tsx +994 -0
- package/Tests/UI/Components/StatusPage/ResourceListSelection.test.tsx +34 -1
- package/Tests/UI/Components/TelemetryFacetSidebarHiddenFacets.test.tsx +4 -1
- package/Tests/UI/ForeignHiddenRuleGuard.test.ts +1951 -0
- package/Tests/UI/Rum/ReplayPlayerChrome.test.tsx +19 -1
- package/Tests/UI/Rum/ReplayTabSwitcher.test.tsx +19 -1
- package/Tests/UI/TailwindPlayCdnForeignHiddenRule.test.ts +1005 -0
- package/Tests/UI/ThemeGroupHoverReveal.test.ts +847 -0
- package/Tests/UI/Utils/Breadcrumb/fixtures/RealBreadcrumbTrails.ts +70 -0
- package/Tests/UI/Utils/Breadcrumb/fixtures/RealRoutePatterns.ts +21 -0
- package/Tests/UI/Utils/ModelAPISsoRedirect.test.ts +127 -0
- package/Tests/UI/Utils/Navigation.test.ts +102 -0
- package/Tests/UI/Utils/Telemetry/TelemetryEntityNames.test.ts +23 -0
- package/Tests/Utils/LabelRuleImportExport.test.ts +5 -0
- package/Tests/Utils/MetricUnitUtil.test.ts +227 -2
- package/Tests/Utils/Monitor/MetricValueFormatter.test.ts +288 -2
- package/Tests/Utils/Monitor/PlatformResourceIdentityAndUnits.test.ts +1559 -0
- package/Tests/Utils/NetworkDiscovery/DiscoveredHostNamingDiagnosis.test.ts +2546 -0
- package/Tests/Utils/NetworkDiscovery/DiscoveredHostUtil.test.ts +315 -0
- package/Tests/Utils/NetworkDiscovery/DiscoveryReverseDnsChain.test.ts +336 -1
- package/Tests/Utils/NetworkDiscovery/NetbiosNameUtil.test.ts +244 -0
- package/Tests/Utils/ValueFormatter.test.ts +50 -0
- package/Tests/tsconfig.json +16 -0
- package/Types/AI/AIResourceContext.ts +7 -0
- package/Types/Dashboard/DashboardTemplates.ts +2 -1
- package/Types/DatabaseServer/DatabaseConnectionSpan.ts +77 -0
- package/Types/DatabaseServer/DatabaseContainerClassifier.ts +2068 -0
- package/Types/DatabaseServer/DatabaseContainerCommand.ts +801 -0
- package/Types/DatabaseServer/DatabaseEndpoint.ts +1943 -0
- package/Types/DatabaseServer/DatabaseServerDiscoverySource.ts +68 -0
- package/Types/DatabaseServer/DatabaseServerMetricCatalog.ts +966 -0
- package/Types/DatabaseServer/DatabaseSystem.ts +2124 -0
- package/Types/DatabaseServer/DatabaseTelemetryResolver.ts +555 -0
- package/Types/Email/EmailTemplateType.ts +1 -0
- package/Types/Monitor/CephAlertTemplates.ts +18 -0
- package/Types/Monitor/DatabaseAlertTemplates.ts +2020 -0
- package/Types/Monitor/KubernetesAlertTemplates.ts +64 -24
- package/Types/Monitor/KubernetesMetricCatalog.ts +118 -19
- package/Types/Monitor/MetricMonitor/MetricCriteriaContext.ts +14 -0
- package/Types/Monitor/MetricMonitor/MetricMonitorResponse.ts +93 -10
- package/Types/Monitor/Recommendation/MonitorRecommendationCatalog.ts +109 -8
- package/Types/Monitor/Recommendation/MonitorRecommendationTypes.ts +36 -7
- package/Types/Monitor/Recommendation/MonitorRecommendationUtil.ts +43 -2
- package/Types/Monitor/Recommendation/RecommendationCriteriaBuilder.ts +41 -6
- package/Types/Monitor/SeriesContext/SeriesLabelDisplay.ts +26 -0
- package/Types/NetworkDevice/DiscoveredHostNamingStatus.ts +147 -0
- package/Types/Permission.ts +267 -0
- package/Types/Probe/ProbeApiIngestResponse.ts +8 -0
- package/Types/Rules/RuleCriteriaFieldRegistry.ts +10 -0
- package/Types/Rules/RuleRun.ts +12 -0
- package/Types/Telemetry/EntityType.ts +23 -0
- package/Types/Telemetry/ResourceFacetCatalog.ts +14 -0
- package/Types/Telemetry/ServiceType.ts +8 -0
- package/UI/Components/Banner/Banner.tsx +1 -1
- package/UI/Components/Card/Card.tsx +1 -1
- package/UI/Components/Charts/ChartGroup/ChartGroup.tsx +2 -2
- package/UI/Components/Charts/Line/LineChart.tsx +7 -0
- package/UI/Components/CommandPalette/CommandPalette.tsx +1 -1
- package/UI/Components/Date/StartAndEndDate.tsx +5 -5
- package/UI/Components/Diagram/HorizontalStepChain.tsx +1 -1
- package/UI/Components/Feed/FeedOptionsButton.tsx +1 -1
- package/UI/Components/Forms/BasicForm.tsx +1 -1
- package/UI/Components/Header/Header.tsx +1 -1
- package/UI/Components/Header/HeaderIconDropdownButton.tsx +1 -1
- package/UI/Components/HeaderAlert/HeaderAlert.tsx +1 -1
- package/UI/Components/LogsViewer/components/FacetValueRow.tsx +1 -1
- package/UI/Components/LogsViewer/components/LogsFacetSidebar.tsx +1 -1
- package/UI/Components/Modal/ModalFooter.tsx +1 -1
- package/UI/Components/ModelDetail/CardModelDetail.tsx +2 -2
- package/UI/Components/ModelTable/BaseModelTable.tsx +4 -4
- package/UI/Components/Navbar/NavBar.tsx +9 -2
- package/UI/Components/Navbar/NavBarMenuModal.tsx +2 -2
- package/UI/Components/Page/Page.tsx +1 -1
- package/UI/Components/Pagination/Pagination.tsx +3 -3
- package/UI/Components/ProgressButtons/ProgressButtonItem.tsx +1 -1
- package/UI/Components/SideMenu/SideMenu.tsx +4 -1
- package/UI/Components/StatusPage/ResourceGroupNavigator.tsx +4 -2
- package/UI/Components/StatusPage/ResourceList.tsx +6 -6
- package/UI/Components/TelemetryViewer/components/TelemetryFacetSidebar.tsx +1 -1
- package/UI/Components/TelemetryViewer/components/TelemetryFacetValueRow.tsx +1 -1
- package/UI/Components/Workflow/ColumnEditor/ModelQueryBuilder.tsx +1 -1
- package/UI/Components/Workflow/ComponentsModal.tsx +2 -2
- package/UI/Components/Workflow/StepTraceViewer.tsx +1 -1
- package/UI/Styles/Theme.css +8 -1
- package/UI/Utils/Navigation.ts +89 -42
- package/UI/Utils/Telemetry/TelemetryEntityNames.ts +7 -0
- package/UI/Utils/TelemetryService.ts +4 -3
- package/Utils/LabelRuleImportExport.ts +2 -0
- package/Utils/MetricUnitUtil.ts +103 -11
- package/Utils/Monitor/MetricValueFormatter.ts +66 -13
- package/Utils/Monitor/PlatformMetricUnitUtil.ts +215 -0
- package/Utils/Monitor/PlatformResourceIdentity.ts +514 -0
- package/Utils/NetworkDiscovery/DiscoveredHostNamingDiagnosis.ts +425 -0
- package/Utils/NetworkDiscovery/DiscoveredHostUtil.ts +32 -0
- package/Utils/NetworkDiscovery/NetbiosNameUtil.ts +73 -0
- package/Utils/Telemetry/DatabaseServerEntityKeys.ts +275 -0
- package/Utils/Telemetry/EntityKey.ts +176 -0
- package/Utils/ValueFormatter.ts +16 -0
- package/build/dist/Models/DatabaseModels/Alert.js +54 -0
- package/build/dist/Models/DatabaseModels/Alert.js.map +1 -1
- package/build/dist/Models/DatabaseModels/DatabaseServer.js +1683 -0
- package/build/dist/Models/DatabaseModels/DatabaseServer.js.map +1 -0
- package/build/dist/Models/DatabaseModels/DatabaseServerEndpoint.js +559 -0
- package/build/dist/Models/DatabaseModels/DatabaseServerEndpoint.js.map +1 -0
- package/build/dist/Models/DatabaseModels/DatabaseServerFeed.js +648 -0
- package/build/dist/Models/DatabaseModels/DatabaseServerFeed.js.map +1 -0
- package/build/dist/Models/DatabaseModels/DatabaseServerLabelRule.js +522 -0
- package/build/dist/Models/DatabaseModels/DatabaseServerLabelRule.js.map +1 -0
- package/build/dist/Models/DatabaseModels/DatabaseServerOwnerRule.js +603 -0
- package/build/dist/Models/DatabaseModels/DatabaseServerOwnerRule.js.map +1 -0
- package/build/dist/Models/DatabaseModels/DatabaseServerOwnerTeam.js +506 -0
- package/build/dist/Models/DatabaseModels/DatabaseServerOwnerTeam.js.map +1 -0
- package/build/dist/Models/DatabaseModels/DatabaseServerOwnerUser.js +505 -0
- package/build/dist/Models/DatabaseModels/DatabaseServerOwnerUser.js.map +1 -0
- package/build/dist/Models/DatabaseModels/Incident.js +54 -0
- package/build/dist/Models/DatabaseModels/Incident.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Index.js +16 -0
- package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
- package/build/dist/Models/DatabaseModels/NetworkDeviceDiscoveryScan.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ScheduledMaintenance.js +54 -0
- package/build/dist/Models/DatabaseModels/ScheduledMaintenance.js.map +1 -1
- package/build/dist/Models/DatabaseModels/TelemetryException.js +2 -0
- package/build/dist/Models/DatabaseModels/TelemetryException.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserProjectSsoConsent.js +162 -0
- package/build/dist/Models/DatabaseModels/UserProjectSsoConsent.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795000000000-AddDatabaseServerTables.js +240 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795000000000-AddDatabaseServerTables.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795100000000-AddUserProjectSsoConsent.js +26 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795100000000-AddUserProjectSsoConsent.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 +1 -1
- package/build/dist/Server/Middleware/TelemetryIngest.js +15 -8
- package/build/dist/Server/Middleware/TelemetryIngest.js.map +1 -1
- package/build/dist/Server/Services/AlertReminderRuleService.js +72 -23
- package/build/dist/Server/Services/AlertReminderRuleService.js.map +1 -1
- package/build/dist/Server/Services/DatabaseServerEndpointService.js +694 -0
- package/build/dist/Server/Services/DatabaseServerEndpointService.js.map +1 -0
- package/build/dist/Server/Services/DatabaseServerFeedService.js +110 -0
- package/build/dist/Server/Services/DatabaseServerFeedService.js.map +1 -0
- package/build/dist/Server/Services/DatabaseServerLabelRuleEngineService.js +272 -0
- package/build/dist/Server/Services/DatabaseServerLabelRuleEngineService.js.map +1 -0
- package/build/dist/Server/Services/DatabaseServerLabelRuleService.js +13 -0
- package/build/dist/Server/Services/DatabaseServerLabelRuleService.js.map +1 -0
- package/build/dist/Server/Services/DatabaseServerOwnerRuleEngineService.js +340 -0
- package/build/dist/Server/Services/DatabaseServerOwnerRuleEngineService.js.map +1 -0
- package/build/dist/Server/Services/DatabaseServerOwnerRuleService.js +13 -0
- package/build/dist/Server/Services/DatabaseServerOwnerRuleService.js.map +1 -0
- package/build/dist/Server/Services/DatabaseServerOwnerTeamService.js +160 -0
- package/build/dist/Server/Services/DatabaseServerOwnerTeamService.js.map +1 -0
- package/build/dist/Server/Services/DatabaseServerOwnerUserService.js +153 -0
- package/build/dist/Server/Services/DatabaseServerOwnerUserService.js.map +1 -0
- package/build/dist/Server/Services/DatabaseServerService.js +3272 -0
- package/build/dist/Server/Services/DatabaseServerService.js.map +1 -0
- package/build/dist/Server/Services/DatabaseService.js +8 -4
- package/build/dist/Server/Services/DatabaseService.js.map +1 -1
- package/build/dist/Server/Services/DockerResourceService.js +30 -3
- package/build/dist/Server/Services/DockerResourceService.js.map +1 -1
- package/build/dist/Server/Services/IncidentReminderRuleService.js +73 -23
- package/build/dist/Server/Services/IncidentReminderRuleService.js.map +1 -1
- package/build/dist/Server/Services/Index.js +16 -0
- package/build/dist/Server/Services/Index.js.map +1 -1
- package/build/dist/Server/Services/InventoryItemService.js +88 -5
- package/build/dist/Server/Services/InventoryItemService.js.map +1 -1
- package/build/dist/Server/Services/IoTDeviceService.js +111 -24
- package/build/dist/Server/Services/IoTDeviceService.js.map +1 -1
- package/build/dist/Server/Services/OpenTelemetryIngestService.js +24 -9
- package/build/dist/Server/Services/OpenTelemetryIngestService.js.map +1 -1
- package/build/dist/Server/Services/PodmanResourceService.js +30 -3
- package/build/dist/Server/Services/PodmanResourceService.js.map +1 -1
- package/build/dist/Server/Services/ScheduledMaintenanceReminderRuleService.js +75 -23
- package/build/dist/Server/Services/ScheduledMaintenanceReminderRuleService.js.map +1 -1
- package/build/dist/Server/Services/ServiceService.js +1 -1
- package/build/dist/Server/Services/ServiceService.js.map +1 -1
- package/build/dist/Server/Services/TelemetryUsageBillingService.js +23 -2
- package/build/dist/Server/Services/TelemetryUsageBillingService.js.map +1 -1
- package/build/dist/Server/Services/UserProjectSsoConsentService.js +79 -0
- package/build/dist/Server/Services/UserProjectSsoConsentService.js.map +1 -0
- package/build/dist/Server/Services/UserService.js +13 -1
- package/build/dist/Server/Services/UserService.js.map +1 -1
- package/build/dist/Server/Types/Database/Permissions/OwnerTableRegistry.js +18 -0
- package/build/dist/Server/Types/Database/Permissions/OwnerTableRegistry.js.map +1 -1
- package/build/dist/Server/Types/Database/QueryHelper.js +26 -1
- package/build/dist/Server/Types/Database/QueryHelper.js.map +1 -1
- package/build/dist/Server/Utils/AI/Toolbox/ResourceTools.js +46 -0
- package/build/dist/Server/Utils/AI/Toolbox/ResourceTools.js.map +1 -1
- package/build/dist/Server/Utils/Database/AffectedResourceRelations.js +6 -0
- package/build/dist/Server/Utils/Database/AffectedResourceRelations.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.js +134 -13
- package/build/dist/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorAlert.js +22 -6
- package/build/dist/Server/Utils/Monitor/MonitorAlert.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +1489 -524
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorIncident.js +22 -6
- package/build/dist/Server/Utils/Monitor/MonitorIncident.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorMaintenanceSuppression.js +196 -6
- package/build/dist/Server/Utils/Monitor/MonitorMaintenanceSuppression.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorResource.js +36 -9
- package/build/dist/Server/Utils/Monitor/MonitorResource.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorResourceContext.js +1 -0
- package/build/dist/Server/Utils/Monitor/MonitorResourceContext.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorStepResourceIdentity.js +175 -17
- package/build/dist/Server/Utils/Monitor/MonitorStepResourceIdentity.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/PerSeriesResolutionRootCause.js +29 -0
- package/build/dist/Server/Utils/Monitor/PerSeriesResolutionRootCause.js.map +1 -0
- package/build/dist/Server/Utils/Monitor/SeriesResourceLabels.js +211 -0
- package/build/dist/Server/Utils/Monitor/SeriesResourceLabels.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/SeriesResourceLinker.js +82 -10
- package/build/dist/Server/Utils/Monitor/SeriesResourceLinker.js.map +1 -1
- package/build/dist/Server/Utils/Rules/RuleRun/RuleRunRegistry.js +25 -0
- package/build/dist/Server/Utils/Rules/RuleRun/RuleRunRegistry.js.map +1 -1
- package/build/dist/Server/Utils/StartServer.js +20 -4
- package/build/dist/Server/Utils/StartServer.js.map +1 -1
- package/build/dist/Server/Utils/TeamMember/ProjectLeaveResourceCleanup.js +2 -0
- package/build/dist/Server/Utils/TeamMember/ProjectLeaveResourceCleanup.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/DatabaseEndpointDiscovery.js +442 -0
- package/build/dist/Server/Utils/Telemetry/DatabaseEndpointDiscovery.js.map +1 -0
- package/build/dist/Server/Utils/Telemetry/EntityRegistry.js +5 -1
- package/build/dist/Server/Utils/Telemetry/EntityRegistry.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/LogExceptionExtractor.js +15 -1
- package/build/dist/Server/Utils/Telemetry/LogExceptionExtractor.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/ResourceEntityFilter.js +121 -5
- package/build/dist/Server/Utils/Telemetry/ResourceEntityFilter.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/ResourceFacetResolver.js +12 -0
- package/build/dist/Server/Utils/Telemetry/ResourceFacetResolver.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/ServiceDependencyDiscovery.js +438 -36
- package/build/dist/Server/Utils/Telemetry/ServiceDependencyDiscovery.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/Telemetry.js +12 -0
- package/build/dist/Server/Utils/Telemetry/Telemetry.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/TelemetryEntity.js +17 -18
- package/build/dist/Server/Utils/Telemetry/TelemetryEntity.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/TelemetryIngestionKeyGuard.js +16 -10
- package/build/dist/Server/Utils/Telemetry/TelemetryIngestionKeyGuard.js.map +1 -1
- package/build/dist/Types/AI/AIResourceContext.js +7 -0
- package/build/dist/Types/AI/AIResourceContext.js.map +1 -1
- package/build/dist/Types/Dashboard/DashboardTemplates.js +2 -1
- package/build/dist/Types/Dashboard/DashboardTemplates.js.map +1 -1
- package/build/dist/Types/DatabaseServer/DatabaseConnectionSpan.js +71 -0
- package/build/dist/Types/DatabaseServer/DatabaseConnectionSpan.js.map +1 -0
- package/build/dist/Types/DatabaseServer/DatabaseContainerClassifier.js +1462 -0
- package/build/dist/Types/DatabaseServer/DatabaseContainerClassifier.js.map +1 -0
- package/build/dist/Types/DatabaseServer/DatabaseContainerCommand.js +691 -0
- package/build/dist/Types/DatabaseServer/DatabaseContainerCommand.js.map +1 -0
- package/build/dist/Types/DatabaseServer/DatabaseEndpoint.js +1407 -0
- package/build/dist/Types/DatabaseServer/DatabaseEndpoint.js.map +1 -0
- package/build/dist/Types/DatabaseServer/DatabaseServerDiscoverySource.js +60 -0
- package/build/dist/Types/DatabaseServer/DatabaseServerDiscoverySource.js.map +1 -0
- package/build/dist/Types/DatabaseServer/DatabaseServerMetricCatalog.js +804 -0
- package/build/dist/Types/DatabaseServer/DatabaseServerMetricCatalog.js.map +1 -0
- package/build/dist/Types/DatabaseServer/DatabaseSystem.js +1860 -0
- package/build/dist/Types/DatabaseServer/DatabaseSystem.js.map +1 -0
- package/build/dist/Types/DatabaseServer/DatabaseTelemetryResolver.js +406 -0
- package/build/dist/Types/DatabaseServer/DatabaseTelemetryResolver.js.map +1 -0
- package/build/dist/Types/Email/EmailTemplateType.js +1 -0
- package/build/dist/Types/Email/EmailTemplateType.js.map +1 -1
- package/build/dist/Types/Monitor/CephAlertTemplates.js +18 -0
- package/build/dist/Types/Monitor/CephAlertTemplates.js.map +1 -1
- package/build/dist/Types/Monitor/DatabaseAlertTemplates.js +1518 -0
- package/build/dist/Types/Monitor/DatabaseAlertTemplates.js.map +1 -0
- package/build/dist/Types/Monitor/KubernetesAlertTemplates.js +57 -24
- package/build/dist/Types/Monitor/KubernetesAlertTemplates.js.map +1 -1
- package/build/dist/Types/Monitor/KubernetesMetricCatalog.js +104 -18
- package/build/dist/Types/Monitor/KubernetesMetricCatalog.js.map +1 -1
- package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationCatalog.js +80 -3
- package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationCatalog.js.map +1 -1
- package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationTypes.js +10 -3
- package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationTypes.js.map +1 -1
- package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationUtil.js +29 -0
- package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationUtil.js.map +1 -1
- package/build/dist/Types/Monitor/Recommendation/RecommendationCriteriaBuilder.js +21 -6
- package/build/dist/Types/Monitor/Recommendation/RecommendationCriteriaBuilder.js.map +1 -1
- package/build/dist/Types/Monitor/SeriesContext/SeriesLabelDisplay.js +25 -0
- package/build/dist/Types/Monitor/SeriesContext/SeriesLabelDisplay.js.map +1 -1
- package/build/dist/Types/NetworkDevice/DiscoveredHostNamingStatus.js +136 -0
- package/build/dist/Types/NetworkDevice/DiscoveredHostNamingStatus.js.map +1 -0
- package/build/dist/Types/Permission.js +234 -0
- package/build/dist/Types/Permission.js.map +1 -1
- package/build/dist/Types/Rules/RuleCriteriaFieldRegistry.js +10 -0
- package/build/dist/Types/Rules/RuleCriteriaFieldRegistry.js.map +1 -1
- package/build/dist/Types/Rules/RuleRun.js +4 -0
- package/build/dist/Types/Rules/RuleRun.js.map +1 -1
- package/build/dist/Types/Telemetry/EntityType.js +23 -0
- package/build/dist/Types/Telemetry/EntityType.js.map +1 -1
- package/build/dist/Types/Telemetry/ResourceFacetCatalog.js +14 -0
- package/build/dist/Types/Telemetry/ResourceFacetCatalog.js.map +1 -1
- package/build/dist/Types/Telemetry/ServiceType.js +8 -0
- package/build/dist/Types/Telemetry/ServiceType.js.map +1 -1
- package/build/dist/UI/Components/Banner/Banner.js +1 -1
- package/build/dist/UI/Components/Banner/Banner.js.map +1 -1
- package/build/dist/UI/Components/Card/Card.js +1 -1
- package/build/dist/UI/Components/Card/Card.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js +2 -2
- package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js.map +1 -1
- package/build/dist/UI/Components/Charts/Line/LineChart.js +1 -1
- package/build/dist/UI/Components/Charts/Line/LineChart.js.map +1 -1
- package/build/dist/UI/Components/CommandPalette/CommandPalette.js +1 -1
- package/build/dist/UI/Components/CommandPalette/CommandPalette.js.map +1 -1
- package/build/dist/UI/Components/Date/StartAndEndDate.js +5 -5
- package/build/dist/UI/Components/Date/StartAndEndDate.js.map +1 -1
- package/build/dist/UI/Components/Diagram/HorizontalStepChain.js +1 -1
- package/build/dist/UI/Components/Diagram/HorizontalStepChain.js.map +1 -1
- package/build/dist/UI/Components/Feed/FeedOptionsButton.js +1 -1
- package/build/dist/UI/Components/Feed/FeedOptionsButton.js.map +1 -1
- package/build/dist/UI/Components/Forms/BasicForm.js +1 -1
- package/build/dist/UI/Components/Forms/BasicForm.js.map +1 -1
- package/build/dist/UI/Components/Header/HeaderIconDropdownButton.js +1 -1
- package/build/dist/UI/Components/Header/HeaderIconDropdownButton.js.map +1 -1
- package/build/dist/UI/Components/HeaderAlert/HeaderAlert.js +1 -1
- package/build/dist/UI/Components/HeaderAlert/HeaderAlert.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/FacetValueRow.js +1 -1
- package/build/dist/UI/Components/LogsViewer/components/FacetValueRow.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsFacetSidebar.js +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsFacetSidebar.js.map +1 -1
- package/build/dist/UI/Components/Modal/ModalFooter.js +1 -1
- package/build/dist/UI/Components/Modal/ModalFooter.js.map +1 -1
- package/build/dist/UI/Components/ModelDetail/CardModelDetail.js +2 -2
- package/build/dist/UI/Components/ModelDetail/CardModelDetail.js.map +1 -1
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js +4 -4
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
- package/build/dist/UI/Components/Navbar/NavBar.js +9 -2
- package/build/dist/UI/Components/Navbar/NavBar.js.map +1 -1
- package/build/dist/UI/Components/Navbar/NavBarMenuModal.js +2 -2
- package/build/dist/UI/Components/Navbar/NavBarMenuModal.js.map +1 -1
- package/build/dist/UI/Components/Page/Page.js +1 -1
- package/build/dist/UI/Components/Page/Page.js.map +1 -1
- package/build/dist/UI/Components/Pagination/Pagination.js +3 -3
- package/build/dist/UI/Components/Pagination/Pagination.js.map +1 -1
- package/build/dist/UI/Components/ProgressButtons/ProgressButtonItem.js +1 -1
- package/build/dist/UI/Components/ProgressButtons/ProgressButtonItem.js.map +1 -1
- package/build/dist/UI/Components/SideMenu/SideMenu.js +4 -1
- package/build/dist/UI/Components/SideMenu/SideMenu.js.map +1 -1
- package/build/dist/UI/Components/StatusPage/ResourceGroupNavigator.js +3 -1
- package/build/dist/UI/Components/StatusPage/ResourceGroupNavigator.js.map +1 -1
- package/build/dist/UI/Components/StatusPage/ResourceList.js +6 -6
- package/build/dist/UI/Components/StatusPage/ResourceList.js.map +1 -1
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryFacetSidebar.js +1 -1
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryFacetSidebar.js.map +1 -1
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryFacetValueRow.js +1 -1
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryFacetValueRow.js.map +1 -1
- package/build/dist/UI/Components/Workflow/ColumnEditor/ModelQueryBuilder.js +1 -1
- package/build/dist/UI/Components/Workflow/ColumnEditor/ModelQueryBuilder.js.map +1 -1
- package/build/dist/UI/Components/Workflow/ComponentsModal.js +2 -2
- package/build/dist/UI/Components/Workflow/ComponentsModal.js.map +1 -1
- package/build/dist/UI/Components/Workflow/StepTraceViewer.js +1 -1
- package/build/dist/UI/Components/Workflow/StepTraceViewer.js.map +1 -1
- package/build/dist/UI/Utils/Navigation.js +66 -33
- package/build/dist/UI/Utils/Navigation.js.map +1 -1
- package/build/dist/UI/Utils/Telemetry/TelemetryEntityNames.js +7 -0
- package/build/dist/UI/Utils/Telemetry/TelemetryEntityNames.js.map +1 -1
- package/build/dist/UI/Utils/TelemetryService.js +3 -2
- package/build/dist/UI/Utils/TelemetryService.js.map +1 -1
- package/build/dist/Utils/LabelRuleImportExport.js +2 -0
- package/build/dist/Utils/LabelRuleImportExport.js.map +1 -1
- package/build/dist/Utils/MetricUnitUtil.js +77 -13
- package/build/dist/Utils/MetricUnitUtil.js.map +1 -1
- package/build/dist/Utils/Monitor/MetricValueFormatter.js +58 -13
- package/build/dist/Utils/Monitor/MetricValueFormatter.js.map +1 -1
- package/build/dist/Utils/Monitor/PlatformMetricUnitUtil.js +161 -0
- package/build/dist/Utils/Monitor/PlatformMetricUnitUtil.js.map +1 -0
- package/build/dist/Utils/Monitor/PlatformResourceIdentity.js +323 -0
- package/build/dist/Utils/Monitor/PlatformResourceIdentity.js.map +1 -0
- package/build/dist/Utils/NetworkDiscovery/DiscoveredHostNamingDiagnosis.js +270 -0
- package/build/dist/Utils/NetworkDiscovery/DiscoveredHostNamingDiagnosis.js.map +1 -0
- package/build/dist/Utils/NetworkDiscovery/DiscoveredHostUtil.js +23 -0
- package/build/dist/Utils/NetworkDiscovery/DiscoveredHostUtil.js.map +1 -1
- package/build/dist/Utils/NetworkDiscovery/NetbiosNameUtil.js +61 -0
- package/build/dist/Utils/NetworkDiscovery/NetbiosNameUtil.js.map +1 -1
- package/build/dist/Utils/Telemetry/DatabaseServerEntityKeys.js +178 -0
- package/build/dist/Utils/Telemetry/DatabaseServerEntityKeys.js.map +1 -0
- package/build/dist/Utils/Telemetry/EntityKey.js +133 -0
- package/build/dist/Utils/Telemetry/EntityKey.js.map +1 -1
- package/build/dist/Utils/ValueFormatter.js +15 -0
- package/build/dist/Utils/ValueFormatter.js.map +1 -1
- package/package.json +1 -1
- package/tsconfig.tests.json +7 -5
|
@@ -0,0 +1,2546 @@
|
|
|
1
|
+
import { DiscoveredNetworkDevice } from "../../../Models/DatabaseModels/NetworkDeviceDiscoveryScan";
|
|
2
|
+
import {
|
|
3
|
+
DiscoveredHostNetbiosStatus,
|
|
4
|
+
DiscoveredHostReverseDnsStatus,
|
|
5
|
+
} from "../../../Types/NetworkDevice/DiscoveredHostNamingStatus";
|
|
6
|
+
import { getDiscoveredHostFullName } from "../../../Utils/NetworkDiscovery/DiscoveredDeviceBuilder";
|
|
7
|
+
import DefaultExplainUnnamedDiscoveredHost, {
|
|
8
|
+
ASK_THE_DEVICE_NETBIOS_TIP,
|
|
9
|
+
ASK_THE_DEVICE_SNMP_TIP,
|
|
10
|
+
ASK_THE_DEVICE_TIP,
|
|
11
|
+
DiscoveredHostNamingExplanation,
|
|
12
|
+
DiscoveredHostNamingScan,
|
|
13
|
+
FAILED_SCAN_EXPLANATION,
|
|
14
|
+
IN_PROGRESS_EXPLANATION,
|
|
15
|
+
NETBIOS_NOT_RECORDED_SENTENCE,
|
|
16
|
+
NETBIOS_OFF_SENTENCE,
|
|
17
|
+
NOT_YET_NAMED_HOST_LABEL,
|
|
18
|
+
NO_RECORD_TIP,
|
|
19
|
+
REVERSE_DNS_NOT_RECORDED_SENTENCE,
|
|
20
|
+
SNMP_NOT_CHECKED_SENTENCE,
|
|
21
|
+
SNMP_NO_ANSWER_SENTENCE,
|
|
22
|
+
SNMP_NO_SYSNAME_SENTENCE,
|
|
23
|
+
TRANSIENT_FAILURE_TIP,
|
|
24
|
+
UNNAMED_HOST_LABEL,
|
|
25
|
+
explainUnnamedDiscoveredHost,
|
|
26
|
+
isDiscoveredHostNamed,
|
|
27
|
+
} from "../../../Utils/NetworkDiscovery/DiscoveredHostNamingDiagnosis";
|
|
28
|
+
import { DiscoveryScanStatus } from "../../../Utils/NetworkDiscovery/DiscoveryScanStatus";
|
|
29
|
+
import { describe, expect, test } from "@jest/globals";
|
|
30
|
+
|
|
31
|
+
/*
|
|
32
|
+
* The sentence beside a discovered host that is listed by its address
|
|
33
|
+
* (OneUptime issue #3916).
|
|
34
|
+
*
|
|
35
|
+
* The report: an ICMP-only scan of twelve kitchen displays named four of them
|
|
36
|
+
* by reverse DNS and listed the other eight by address. Nothing said why. The
|
|
37
|
+
* status message was silent, the probe logged at debug level, and the Review
|
|
38
|
+
* dialog showed a bare IP. So "the device has no PTR record", "the probe's
|
|
39
|
+
* DNS server timed out" and "NetBIOS was never asked" all looked the same,
|
|
40
|
+
* and an operator who KNEW the devices had names could only conclude the
|
|
41
|
+
* product was broken.
|
|
42
|
+
*
|
|
43
|
+
* These tests are about the WORDS: that each code the probe can stamp turns
|
|
44
|
+
* into the one sentence written for it, that a row with no code says no more
|
|
45
|
+
* than it can support, and that the text can never carry anything the
|
|
46
|
+
* scanned network chose. The sentences a code picks are private to the
|
|
47
|
+
* module, so they are written out here as literals. A copy change then shows
|
|
48
|
+
* up as a test diff, which is where it should be reviewed.
|
|
49
|
+
*
|
|
50
|
+
* Every case is a plain literal, the way the dialog hands a row over after
|
|
51
|
+
* normalizeDiscoveredHosts. Casts through `unknown` mark the rows the type
|
|
52
|
+
* forbids on purpose: the column is jsonb written verbatim from the probe's
|
|
53
|
+
* payload, so the runtime value does not honour the type.
|
|
54
|
+
*/
|
|
55
|
+
|
|
56
|
+
/*
|
|
57
|
+
* One of the reporter's addresses. The only digits anywhere in the copy are
|
|
58
|
+
* the "137" of UDP 137, which is what lets the leak tests below say "no
|
|
59
|
+
* digits".
|
|
60
|
+
*/
|
|
61
|
+
const ADDRESS: string = "10.16.42.51";
|
|
62
|
+
|
|
63
|
+
// A row with nothing on it but its address: no SNMP answer, no names, no codes.
|
|
64
|
+
function bareHost(
|
|
65
|
+
overrides: Partial<DiscoveredNetworkDevice> = {},
|
|
66
|
+
): DiscoveredNetworkDevice {
|
|
67
|
+
return { ipAddress: ADDRESS, ...overrides };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// A ping-only host, as the probe reports one on any scan.
|
|
71
|
+
function pingOnlyHost(
|
|
72
|
+
overrides: Partial<DiscoveredNetworkDevice> = {},
|
|
73
|
+
): DiscoveredNetworkDevice {
|
|
74
|
+
return { ipAddress: ADDRESS, snmpReachable: false, ...overrides };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/*
|
|
78
|
+
* The reporter's scan: ping only. The issue does not say whether NetBIOS
|
|
79
|
+
* names were on; they are here, as the wizard's default.
|
|
80
|
+
*/
|
|
81
|
+
const REPORTERS_SCAN: DiscoveredHostNamingScan = {
|
|
82
|
+
status: DiscoveryScanStatus.Completed,
|
|
83
|
+
isSnmpEnabled: false,
|
|
84
|
+
isNetbiosLookupEnabled: true,
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const ICMP_ONLY_SCAN: DiscoveredHostNamingScan = {
|
|
88
|
+
status: DiscoveryScanStatus.Completed,
|
|
89
|
+
isSnmpEnabled: false,
|
|
90
|
+
isNetbiosLookupEnabled: false,
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const SNMP_SCAN: DiscoveredHostNamingScan = {
|
|
94
|
+
status: DiscoveryScanStatus.Completed,
|
|
95
|
+
isSnmpEnabled: true,
|
|
96
|
+
isNetbiosLookupEnabled: false,
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const SNMP_AND_NETBIOS_SCAN: DiscoveredHostNamingScan = {
|
|
100
|
+
status: DiscoveryScanStatus.Completed,
|
|
101
|
+
isSnmpEnabled: true,
|
|
102
|
+
isNetbiosLookupEnabled: true,
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
/*
|
|
106
|
+
* What each reverse-DNS code says, and which tip follows it. Written out,
|
|
107
|
+
* not imported: the table in the module is private, and these strings are
|
|
108
|
+
* the product.
|
|
109
|
+
*/
|
|
110
|
+
const REVERSE_DNS_COPY: Array<
|
|
111
|
+
[DiscoveredHostReverseDnsStatus, string, string | undefined]
|
|
112
|
+
> = [
|
|
113
|
+
[
|
|
114
|
+
DiscoveredHostReverseDnsStatus.NoRecord,
|
|
115
|
+
"Reverse DNS: the probe's DNS server has no PTR record for this address.",
|
|
116
|
+
NO_RECORD_TIP,
|
|
117
|
+
],
|
|
118
|
+
[
|
|
119
|
+
DiscoveredHostReverseDnsStatus.UnusableName,
|
|
120
|
+
"Reverse DNS: a PTR record came back, but it is not a valid hostname, so it was not used.",
|
|
121
|
+
undefined,
|
|
122
|
+
],
|
|
123
|
+
[
|
|
124
|
+
DiscoveredHostReverseDnsStatus.Timeout,
|
|
125
|
+
"Reverse DNS: the probe's DNS server did not answer in time.",
|
|
126
|
+
TRANSIENT_FAILURE_TIP,
|
|
127
|
+
],
|
|
128
|
+
[
|
|
129
|
+
DiscoveredHostReverseDnsStatus.ServerFailure,
|
|
130
|
+
"Reverse DNS: the probe's DNS server answered with a server failure (SERVFAIL).",
|
|
131
|
+
TRANSIENT_FAILURE_TIP,
|
|
132
|
+
],
|
|
133
|
+
[
|
|
134
|
+
DiscoveredHostReverseDnsStatus.Refused,
|
|
135
|
+
"Reverse DNS: the probe's DNS server refused the query.",
|
|
136
|
+
TRANSIENT_FAILURE_TIP,
|
|
137
|
+
],
|
|
138
|
+
[
|
|
139
|
+
DiscoveredHostReverseDnsStatus.Unreachable,
|
|
140
|
+
"Reverse DNS: the probe could not reach its DNS server.",
|
|
141
|
+
TRANSIENT_FAILURE_TIP,
|
|
142
|
+
],
|
|
143
|
+
[
|
|
144
|
+
DiscoveredHostReverseDnsStatus.Failed,
|
|
145
|
+
"Reverse DNS: the lookup failed on the probe.",
|
|
146
|
+
TRANSIENT_FAILURE_TIP,
|
|
147
|
+
],
|
|
148
|
+
[
|
|
149
|
+
DiscoveredHostReverseDnsStatus.SkippedTimeBudget,
|
|
150
|
+
"Reverse DNS: not looked up. The scan ran out of time for name lookups before reaching this address.",
|
|
151
|
+
undefined,
|
|
152
|
+
],
|
|
153
|
+
[
|
|
154
|
+
DiscoveredHostReverseDnsStatus.SkippedNoResolver,
|
|
155
|
+
"Reverse DNS: not looked up. The probe's DNS server was not answering, so it stopped asking.",
|
|
156
|
+
TRANSIENT_FAILURE_TIP,
|
|
157
|
+
],
|
|
158
|
+
];
|
|
159
|
+
|
|
160
|
+
const NETBIOS_COPY: Array<[DiscoveredHostNetbiosStatus, string]> = [
|
|
161
|
+
[
|
|
162
|
+
DiscoveredHostNetbiosStatus.NoReply,
|
|
163
|
+
"NetBIOS: no reply on UDP 137, which is usual for devices that are not Windows, or when a firewall blocks it.",
|
|
164
|
+
],
|
|
165
|
+
[
|
|
166
|
+
DiscoveredHostNetbiosStatus.NoUsableName,
|
|
167
|
+
"NetBIOS: the device replied, but reported no usable name.",
|
|
168
|
+
],
|
|
169
|
+
[
|
|
170
|
+
DiscoveredHostNetbiosStatus.SendFailed,
|
|
171
|
+
"NetBIOS: the query could not be sent from the probe.",
|
|
172
|
+
],
|
|
173
|
+
[
|
|
174
|
+
DiscoveredHostNetbiosStatus.Skipped,
|
|
175
|
+
"NetBIOS: not asked. The lookup stopped before reaching this address.",
|
|
176
|
+
],
|
|
177
|
+
[
|
|
178
|
+
DiscoveredHostNetbiosStatus.SkippedHostCap,
|
|
179
|
+
"NetBIOS: not asked. The scan reached the probe's NetBIOS host limit.",
|
|
180
|
+
],
|
|
181
|
+
[
|
|
182
|
+
DiscoveredHostNetbiosStatus.SkippedIneligibleAddress,
|
|
183
|
+
"NetBIOS: not asked. Only private addresses are asked.",
|
|
184
|
+
],
|
|
185
|
+
[
|
|
186
|
+
DiscoveredHostNetbiosStatus.SkippedGlobalProbe,
|
|
187
|
+
"NetBIOS: not asked. This scan ran on a global probe, and global probes never send NetBIOS queries.",
|
|
188
|
+
],
|
|
189
|
+
];
|
|
190
|
+
|
|
191
|
+
const GLOBAL_PROBE_NETBIOS_SENTENCE: string =
|
|
192
|
+
"NetBIOS: not asked. This scan ran on a global probe, and global probes never send NetBIOS queries.";
|
|
193
|
+
|
|
194
|
+
function reverseDnsSentence(code: DiscoveredHostReverseDnsStatus): string {
|
|
195
|
+
return REVERSE_DNS_COPY.find(
|
|
196
|
+
(entry: [DiscoveredHostReverseDnsStatus, string, string | undefined]) => {
|
|
197
|
+
return entry[0] === code;
|
|
198
|
+
},
|
|
199
|
+
)![1];
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function netbiosSentence(code: DiscoveredHostNetbiosStatus): string {
|
|
203
|
+
return NETBIOS_COPY.find((entry: [DiscoveredHostNetbiosStatus, string]) => {
|
|
204
|
+
return entry[0] === code;
|
|
205
|
+
})![1];
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/*
|
|
209
|
+
* The explanation's text, failing the test if there is none. Most tests ask
|
|
210
|
+
* about the words, and a bare `!` would turn "no explanation" into a
|
|
211
|
+
* TypeError that names nothing.
|
|
212
|
+
*/
|
|
213
|
+
function textOf(data: {
|
|
214
|
+
host: DiscoveredNetworkDevice;
|
|
215
|
+
scan?: DiscoveredHostNamingScan | undefined;
|
|
216
|
+
isGlobalProbe?: boolean | undefined;
|
|
217
|
+
}): string {
|
|
218
|
+
const explanation: DiscoveredHostNamingExplanation | undefined =
|
|
219
|
+
explainUnnamedDiscoveredHost(data);
|
|
220
|
+
|
|
221
|
+
expect(explanation).toBeDefined();
|
|
222
|
+
|
|
223
|
+
return explanation!.text;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/*
|
|
227
|
+
* The sentences a text may be built from, in the order they must appear:
|
|
228
|
+
* SNMP, reverse DNS, NetBIOS (the order the sources win in), then the
|
|
229
|
+
* reverse-DNS tip, then the ask-the-device tip. At most one from each group.
|
|
230
|
+
*/
|
|
231
|
+
const SENTENCE_GROUPS: Array<Array<string>> = [
|
|
232
|
+
[
|
|
233
|
+
SNMP_NOT_CHECKED_SENTENCE,
|
|
234
|
+
SNMP_NO_ANSWER_SENTENCE,
|
|
235
|
+
SNMP_NO_SYSNAME_SENTENCE,
|
|
236
|
+
],
|
|
237
|
+
[
|
|
238
|
+
...REVERSE_DNS_COPY.map(
|
|
239
|
+
(
|
|
240
|
+
entry: [DiscoveredHostReverseDnsStatus, string, string | undefined],
|
|
241
|
+
): string => {
|
|
242
|
+
return entry[1];
|
|
243
|
+
},
|
|
244
|
+
),
|
|
245
|
+
REVERSE_DNS_NOT_RECORDED_SENTENCE,
|
|
246
|
+
],
|
|
247
|
+
[
|
|
248
|
+
...NETBIOS_COPY.map(
|
|
249
|
+
(entry: [DiscoveredHostNetbiosStatus, string]): string => {
|
|
250
|
+
return entry[1];
|
|
251
|
+
},
|
|
252
|
+
),
|
|
253
|
+
NETBIOS_OFF_SENTENCE,
|
|
254
|
+
NETBIOS_NOT_RECORDED_SENTENCE,
|
|
255
|
+
],
|
|
256
|
+
[NO_RECORD_TIP, TRANSIENT_FAILURE_TIP],
|
|
257
|
+
[ASK_THE_DEVICE_TIP, ASK_THE_DEVICE_SNMP_TIP, ASK_THE_DEVICE_NETBIOS_TIP],
|
|
258
|
+
];
|
|
259
|
+
|
|
260
|
+
const SNMP_GROUP: number = 0;
|
|
261
|
+
const REVERSE_DNS_GROUP: number = 1;
|
|
262
|
+
const NETBIOS_GROUP: number = 2;
|
|
263
|
+
const REVERSE_DNS_TIP_GROUP: number = 3;
|
|
264
|
+
const ASK_THE_DEVICE_GROUP: number = 4;
|
|
265
|
+
|
|
266
|
+
interface KnownSentence {
|
|
267
|
+
group: number;
|
|
268
|
+
sentence: string;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/*
|
|
272
|
+
* Reads a text back as a list of known sentences, or undefined when any part
|
|
273
|
+
* of it is not one of them. The longest match wins at each position, so a
|
|
274
|
+
* sentence that happened to be a prefix of another could not hide it.
|
|
275
|
+
*/
|
|
276
|
+
function splitIntoKnownSentences(
|
|
277
|
+
text: string,
|
|
278
|
+
): Array<KnownSentence> | undefined {
|
|
279
|
+
const found: Array<KnownSentence> = [];
|
|
280
|
+
let position: number = 0;
|
|
281
|
+
|
|
282
|
+
while (position < text.length) {
|
|
283
|
+
let bestGroup: number = -1;
|
|
284
|
+
let bestSentence: string = "";
|
|
285
|
+
|
|
286
|
+
for (let group: number = 0; group < SENTENCE_GROUPS.length; group++) {
|
|
287
|
+
for (const sentence of SENTENCE_GROUPS[group]!) {
|
|
288
|
+
const end: number = position + sentence.length;
|
|
289
|
+
const isWholeSentence: boolean =
|
|
290
|
+
text.startsWith(sentence, position) &&
|
|
291
|
+
(end === text.length || text.charAt(end) === " ");
|
|
292
|
+
|
|
293
|
+
if (isWholeSentence && sentence.length > bestSentence.length) {
|
|
294
|
+
bestGroup = group;
|
|
295
|
+
bestSentence = sentence;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
if (bestGroup < 0) {
|
|
301
|
+
return undefined;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
found.push({ group: bestGroup, sentence: bestSentence });
|
|
305
|
+
position += bestSentence.length + 1;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
return found;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/*
|
|
312
|
+
* Every input the function can be handed that changes its answer, crossed
|
|
313
|
+
* with every other. Nulls are included where jsonb or a partial select can
|
|
314
|
+
* produce them, and each boolean-ish input also gets one value of the wrong
|
|
315
|
+
* type, because the scan row is read from a column the type does not bind.
|
|
316
|
+
*/
|
|
317
|
+
interface NamingCase {
|
|
318
|
+
host: DiscoveredNetworkDevice;
|
|
319
|
+
scan: DiscoveredHostNamingScan | undefined;
|
|
320
|
+
isGlobalProbe: boolean | undefined;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
const REVERSE_DNS_CODE_OPTIONS: Array<
|
|
324
|
+
DiscoveredHostReverseDnsStatus | undefined
|
|
325
|
+
> = [undefined, ...Object.values(DiscoveredHostReverseDnsStatus)];
|
|
326
|
+
const NETBIOS_CODE_OPTIONS: Array<DiscoveredHostNetbiosStatus | undefined> = [
|
|
327
|
+
undefined,
|
|
328
|
+
...Object.values(DiscoveredHostNetbiosStatus),
|
|
329
|
+
];
|
|
330
|
+
const STATUS_OPTIONS: Array<string | null | undefined> = [
|
|
331
|
+
undefined,
|
|
332
|
+
null,
|
|
333
|
+
DiscoveryScanStatus.Pending,
|
|
334
|
+
DiscoveryScanStatus.InProgress,
|
|
335
|
+
DiscoveryScanStatus.Completed,
|
|
336
|
+
DiscoveryScanStatus.Failed,
|
|
337
|
+
];
|
|
338
|
+
const FLAG_OPTIONS: Array<boolean | null | undefined> = [
|
|
339
|
+
undefined,
|
|
340
|
+
null,
|
|
341
|
+
true,
|
|
342
|
+
false,
|
|
343
|
+
"true" as unknown as boolean,
|
|
344
|
+
];
|
|
345
|
+
const SNMP_REACHABLE_OPTIONS: Array<boolean | undefined> = [
|
|
346
|
+
undefined,
|
|
347
|
+
true,
|
|
348
|
+
false,
|
|
349
|
+
];
|
|
350
|
+
const GLOBAL_PROBE_OPTIONS: Array<boolean | undefined> = [
|
|
351
|
+
undefined,
|
|
352
|
+
true,
|
|
353
|
+
false,
|
|
354
|
+
];
|
|
355
|
+
|
|
356
|
+
function allNamingCases(): Array<NamingCase> {
|
|
357
|
+
const cases: Array<NamingCase> = [];
|
|
358
|
+
|
|
359
|
+
for (const dnsHostnameStatus of REVERSE_DNS_CODE_OPTIONS) {
|
|
360
|
+
for (const netbiosNameStatus of NETBIOS_CODE_OPTIONS) {
|
|
361
|
+
for (const snmpReachable of SNMP_REACHABLE_OPTIONS) {
|
|
362
|
+
for (const isGlobalProbe of GLOBAL_PROBE_OPTIONS) {
|
|
363
|
+
const host: DiscoveredNetworkDevice = {
|
|
364
|
+
ipAddress: ADDRESS,
|
|
365
|
+
snmpReachable: snmpReachable,
|
|
366
|
+
dnsHostnameStatus: dnsHostnameStatus,
|
|
367
|
+
netbiosNameStatus: netbiosNameStatus,
|
|
368
|
+
};
|
|
369
|
+
|
|
370
|
+
// No scan at all: a caller that has only the row.
|
|
371
|
+
cases.push({
|
|
372
|
+
host: host,
|
|
373
|
+
scan: undefined,
|
|
374
|
+
isGlobalProbe: isGlobalProbe,
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
for (const status of STATUS_OPTIONS) {
|
|
378
|
+
for (const isSnmpEnabled of FLAG_OPTIONS) {
|
|
379
|
+
for (const isNetbiosLookupEnabled of FLAG_OPTIONS) {
|
|
380
|
+
cases.push({
|
|
381
|
+
host: host,
|
|
382
|
+
scan: {
|
|
383
|
+
status: status,
|
|
384
|
+
isSnmpEnabled: isSnmpEnabled,
|
|
385
|
+
isNetbiosLookupEnabled: isNetbiosLookupEnabled,
|
|
386
|
+
},
|
|
387
|
+
isGlobalProbe: isGlobalProbe,
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
return cases;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
const ALL_NAMING_CASES: Array<NamingCase> = allNamingCases();
|
|
401
|
+
|
|
402
|
+
/*
|
|
403
|
+
* A tooltip is a few lines, not a page. The dialog's (i) is Tippy with a
|
|
404
|
+
* 350px maximum width, where 420 characters is already eight or nine lines.
|
|
405
|
+
* The longest text today is 415 characters (it grew from 401 when the
|
|
406
|
+
* no-record tip learnt to say "inside the probe's container or pod"), so a
|
|
407
|
+
* new sentence that pushes past this is a sign the copy needs trimming, not
|
|
408
|
+
* the limit raising.
|
|
409
|
+
*/
|
|
410
|
+
const MAX_EXPLANATION_LENGTH: number = 420;
|
|
411
|
+
|
|
412
|
+
describe("the fixed copy", () => {
|
|
413
|
+
/*
|
|
414
|
+
* Pinned once, here, as literals. Every other test composes its expected
|
|
415
|
+
* text from the exported constants, so a copy change is one edit in this
|
|
416
|
+
* block and nowhere else.
|
|
417
|
+
*/
|
|
418
|
+
test.each([
|
|
419
|
+
["UNNAMED_HOST_LABEL", UNNAMED_HOST_LABEL, "No name found"],
|
|
420
|
+
["NOT_YET_NAMED_HOST_LABEL", NOT_YET_NAMED_HOST_LABEL, "Not named yet"],
|
|
421
|
+
[
|
|
422
|
+
"IN_PROGRESS_EXPLANATION",
|
|
423
|
+
IN_PROGRESS_EXPLANATION,
|
|
424
|
+
"Names are looked up after the sweep finishes, so hosts found so far are listed by address. Open Review Results again when the scan completes.",
|
|
425
|
+
],
|
|
426
|
+
[
|
|
427
|
+
"FAILED_SCAN_EXPLANATION",
|
|
428
|
+
FAILED_SCAN_EXPLANATION,
|
|
429
|
+
"This scan stopped before it looked up names, so its hosts are listed by address. Run it again to name them.",
|
|
430
|
+
],
|
|
431
|
+
[
|
|
432
|
+
"REVERSE_DNS_NOT_RECORDED_SENTENCE",
|
|
433
|
+
REVERSE_DNS_NOT_RECORDED_SENTENCE,
|
|
434
|
+
"Reverse DNS: no name was recorded for this address.",
|
|
435
|
+
],
|
|
436
|
+
[
|
|
437
|
+
"NETBIOS_OFF_SENTENCE",
|
|
438
|
+
NETBIOS_OFF_SENTENCE,
|
|
439
|
+
"NetBIOS: off for this scan.",
|
|
440
|
+
],
|
|
441
|
+
[
|
|
442
|
+
"NETBIOS_NOT_RECORDED_SENTENCE",
|
|
443
|
+
NETBIOS_NOT_RECORDED_SENTENCE,
|
|
444
|
+
"NetBIOS: no name was recorded.",
|
|
445
|
+
],
|
|
446
|
+
[
|
|
447
|
+
"SNMP_NOT_CHECKED_SENTENCE",
|
|
448
|
+
SNMP_NOT_CHECKED_SENTENCE,
|
|
449
|
+
"SNMP: not checked by this scan.",
|
|
450
|
+
],
|
|
451
|
+
[
|
|
452
|
+
"SNMP_NO_ANSWER_SENTENCE",
|
|
453
|
+
SNMP_NO_ANSWER_SENTENCE,
|
|
454
|
+
"SNMP: no answer with this scan's credentials.",
|
|
455
|
+
],
|
|
456
|
+
[
|
|
457
|
+
"SNMP_NO_SYSNAME_SENTENCE",
|
|
458
|
+
SNMP_NO_SYSNAME_SENTENCE,
|
|
459
|
+
"SNMP: the device answered, but reported no name (sysName).",
|
|
460
|
+
],
|
|
461
|
+
[
|
|
462
|
+
"NO_RECORD_TIP",
|
|
463
|
+
NO_RECORD_TIP,
|
|
464
|
+
"The probe uses its own DNS server, which may not be the one you checked with. Run nslookup on this address inside the probe's container or pod to compare.",
|
|
465
|
+
],
|
|
466
|
+
[
|
|
467
|
+
"TRANSIENT_FAILURE_TIP",
|
|
468
|
+
TRANSIENT_FAILURE_TIP,
|
|
469
|
+
"Rescan to try again. If it keeps happening, check the DNS servers the probe uses, inside its container or pod.",
|
|
470
|
+
],
|
|
471
|
+
[
|
|
472
|
+
"ASK_THE_DEVICE_TIP",
|
|
473
|
+
ASK_THE_DEVICE_TIP,
|
|
474
|
+
"Checking SNMP, or NetBIOS lookup for Windows hosts, asks the device for its own name.",
|
|
475
|
+
],
|
|
476
|
+
[
|
|
477
|
+
"ASK_THE_DEVICE_SNMP_TIP",
|
|
478
|
+
ASK_THE_DEVICE_SNMP_TIP,
|
|
479
|
+
"Checking SNMP asks the device for its own name.",
|
|
480
|
+
],
|
|
481
|
+
[
|
|
482
|
+
"ASK_THE_DEVICE_NETBIOS_TIP",
|
|
483
|
+
ASK_THE_DEVICE_NETBIOS_TIP,
|
|
484
|
+
"NetBIOS lookup asks Windows hosts for their own name.",
|
|
485
|
+
],
|
|
486
|
+
])(
|
|
487
|
+
"%s reads exactly as written",
|
|
488
|
+
(_name: string, actual: string, expected: string) => {
|
|
489
|
+
expect(actual).toBe(expected);
|
|
490
|
+
},
|
|
491
|
+
);
|
|
492
|
+
|
|
493
|
+
test("the reverse-DNS table below covers every code, once", () => {
|
|
494
|
+
/*
|
|
495
|
+
* A code added to the enum without a row here would be tested by
|
|
496
|
+
* nothing, and the module's Record type is what makes sure it has copy.
|
|
497
|
+
*/
|
|
498
|
+
expect(
|
|
499
|
+
REVERSE_DNS_COPY.map(
|
|
500
|
+
(
|
|
501
|
+
entry: [DiscoveredHostReverseDnsStatus, string, string | undefined],
|
|
502
|
+
): string => {
|
|
503
|
+
return entry[0];
|
|
504
|
+
},
|
|
505
|
+
).sort(),
|
|
506
|
+
).toEqual(Object.values(DiscoveredHostReverseDnsStatus).sort());
|
|
507
|
+
});
|
|
508
|
+
|
|
509
|
+
test("the NetBIOS table below covers every code, once", () => {
|
|
510
|
+
expect(
|
|
511
|
+
NETBIOS_COPY.map(
|
|
512
|
+
(entry: [DiscoveredHostNetbiosStatus, string]): string => {
|
|
513
|
+
return entry[0];
|
|
514
|
+
},
|
|
515
|
+
).sort(),
|
|
516
|
+
).toEqual(Object.values(DiscoveredHostNetbiosStatus).sort());
|
|
517
|
+
});
|
|
518
|
+
|
|
519
|
+
test("no two codes share a sentence", () => {
|
|
520
|
+
/*
|
|
521
|
+
* The whole point of the codes: "no PTR record" and "timed out" used to
|
|
522
|
+
* look the same. Two codes with one sentence would bring that back.
|
|
523
|
+
*/
|
|
524
|
+
const all: Array<string> = [
|
|
525
|
+
...SENTENCE_GROUPS[REVERSE_DNS_GROUP]!,
|
|
526
|
+
...SENTENCE_GROUPS[NETBIOS_GROUP]!,
|
|
527
|
+
];
|
|
528
|
+
|
|
529
|
+
expect(new Set<string>(all).size).toBe(all.length);
|
|
530
|
+
});
|
|
531
|
+
|
|
532
|
+
test("every sentence names the source it is about first", () => {
|
|
533
|
+
for (const sentence of SENTENCE_GROUPS[SNMP_GROUP]!) {
|
|
534
|
+
expect(sentence.startsWith("SNMP: ")).toBe(true);
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
for (const sentence of SENTENCE_GROUPS[REVERSE_DNS_GROUP]!) {
|
|
538
|
+
expect(sentence.startsWith("Reverse DNS: ")).toBe(true);
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
for (const sentence of SENTENCE_GROUPS[NETBIOS_GROUP]!) {
|
|
542
|
+
expect(sentence.startsWith("NetBIOS: ")).toBe(true);
|
|
543
|
+
}
|
|
544
|
+
});
|
|
545
|
+
|
|
546
|
+
test("the default export is the named function", () => {
|
|
547
|
+
expect(DefaultExplainUnnamedDiscoveredHost).toBe(
|
|
548
|
+
explainUnnamedDiscoveredHost,
|
|
549
|
+
);
|
|
550
|
+
});
|
|
551
|
+
});
|
|
552
|
+
|
|
553
|
+
describe("isDiscoveredHostNamed", () => {
|
|
554
|
+
test.each([
|
|
555
|
+
["a sysName", { sysName: "kds-01" }],
|
|
556
|
+
["a sysName with padding", { sysName: " kds-01 " }],
|
|
557
|
+
["a PTR name", { dnsHostname: "wb0024kds02.corp.example.com" }],
|
|
558
|
+
["a PTR name with its root dot", { dnsHostname: "kds-01.example.com." }],
|
|
559
|
+
["a single-label PTR name", { dnsHostname: "WB0024KDS02" }],
|
|
560
|
+
["a NetBIOS name", { netbiosName: "WB0024KDS02" }],
|
|
561
|
+
["a space-padded NetBIOS name", { netbiosName: "WB0024KDS02 " }],
|
|
562
|
+
])(
|
|
563
|
+
"a host with %s is named",
|
|
564
|
+
(_label: string, fields: Partial<DiscoveredNetworkDevice>) => {
|
|
565
|
+
expect(isDiscoveredHostNamed(bareHost(fields))).toBe(true);
|
|
566
|
+
},
|
|
567
|
+
);
|
|
568
|
+
|
|
569
|
+
test.each([
|
|
570
|
+
["nothing at all", {}],
|
|
571
|
+
["a whitespace-only sysName", { sysName: " " }],
|
|
572
|
+
["a tab-and-newline sysName", { sysName: "\t\n" }],
|
|
573
|
+
["an empty sysName", { sysName: "" }],
|
|
574
|
+
["a numeric sysName", { sysName: 42 as unknown as string }],
|
|
575
|
+
["an object sysName", { sysName: {} as unknown as string }],
|
|
576
|
+
["an in-addr.arpa echo", { dnsHostname: "51.42.16.10.in-addr.arpa" }],
|
|
577
|
+
["a PTR name with a space", { dnsHostname: "kds 01.example.com" }],
|
|
578
|
+
["a PTR name that restates the address", { dnsHostname: ADDRESS }],
|
|
579
|
+
[
|
|
580
|
+
"a PTR name that is markup",
|
|
581
|
+
{ dnsHostname: "<img src=x onerror=1>.example" },
|
|
582
|
+
],
|
|
583
|
+
["a numeric PTR name", { dnsHostname: 51 as unknown as string }],
|
|
584
|
+
["a NetBIOS name with a space", { netbiosName: "WB0024 KDS02" }],
|
|
585
|
+
["the browser-election pseudo-name", { netbiosName: "__MSBROWSE__" }],
|
|
586
|
+
["an all-digit NetBIOS name", { netbiosName: "123456" }],
|
|
587
|
+
["a dotted NetBIOS name", { netbiosName: "kds.corp" }],
|
|
588
|
+
])(
|
|
589
|
+
"a host with %s is not named",
|
|
590
|
+
(_label: string, fields: Partial<DiscoveredNetworkDevice>) => {
|
|
591
|
+
expect(isDiscoveredHostNamed(bareHost(fields))).toBe(false);
|
|
592
|
+
},
|
|
593
|
+
);
|
|
594
|
+
|
|
595
|
+
test("agrees with the name line on every combination of name fields", () => {
|
|
596
|
+
/*
|
|
597
|
+
* The hint must sit on exactly the rows whose name line IS the address.
|
|
598
|
+
* If the two disagreed, a named row would get "No name found" beside its
|
|
599
|
+
* name, or a bare address would get no explanation, which is the bug
|
|
600
|
+
* this whole feature is for.
|
|
601
|
+
*
|
|
602
|
+
* The one row they are allowed to disagree on is a sysName that is the
|
|
603
|
+
* address itself. The name line cannot tell that from the address, but
|
|
604
|
+
* the device DID answer with a name, so "no name found" would be false.
|
|
605
|
+
* No sysName below is the address. A PTR name that is the address IS
|
|
606
|
+
* below, and both sides drop it.
|
|
607
|
+
*/
|
|
608
|
+
const sysNames: Array<unknown> = [undefined, "", " ", "kds-01", 42];
|
|
609
|
+
const dnsHostnames: Array<unknown> = [
|
|
610
|
+
undefined,
|
|
611
|
+
"wb0024kds02.corp.example.com",
|
|
612
|
+
"51.42.16.10.in-addr.arpa",
|
|
613
|
+
"kds 01",
|
|
614
|
+
ADDRESS,
|
|
615
|
+
{},
|
|
616
|
+
];
|
|
617
|
+
const netbiosNames: Array<unknown> = [
|
|
618
|
+
undefined,
|
|
619
|
+
"WB0024KDS02",
|
|
620
|
+
"WB0024 KDS02",
|
|
621
|
+
"__MSBROWSE__",
|
|
622
|
+
7,
|
|
623
|
+
];
|
|
624
|
+
|
|
625
|
+
let checked: number = 0;
|
|
626
|
+
|
|
627
|
+
for (const sysName of sysNames) {
|
|
628
|
+
for (const dnsHostname of dnsHostnames) {
|
|
629
|
+
for (const netbiosName of netbiosNames) {
|
|
630
|
+
const host: DiscoveredNetworkDevice = {
|
|
631
|
+
ipAddress: ADDRESS,
|
|
632
|
+
sysName: sysName as string,
|
|
633
|
+
dnsHostname: dnsHostname as string,
|
|
634
|
+
netbiosName: netbiosName as string,
|
|
635
|
+
};
|
|
636
|
+
|
|
637
|
+
expect(isDiscoveredHostNamed(host)).toBe(
|
|
638
|
+
getDiscoveredHostFullName(host) !== ADDRESS,
|
|
639
|
+
);
|
|
640
|
+
checked++;
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
expect(checked).toBe(150);
|
|
646
|
+
});
|
|
647
|
+
});
|
|
648
|
+
|
|
649
|
+
describe("explainUnnamedDiscoveredHost — rows it has nothing to say about", () => {
|
|
650
|
+
test.each([
|
|
651
|
+
["a sysName", { sysName: "kds-01" }],
|
|
652
|
+
["a PTR name", { dnsHostname: "wb0024kds02.corp.example.com" }],
|
|
653
|
+
["a NetBIOS name", { netbiosName: "WB0024KDS02" }],
|
|
654
|
+
])(
|
|
655
|
+
"a host named by %s gets no explanation",
|
|
656
|
+
(_label: string, fields: Partial<DiscoveredNetworkDevice>) => {
|
|
657
|
+
expect(
|
|
658
|
+
explainUnnamedDiscoveredHost({
|
|
659
|
+
host: pingOnlyHost(fields),
|
|
660
|
+
scan: REPORTERS_SCAN,
|
|
661
|
+
}),
|
|
662
|
+
).toBeUndefined();
|
|
663
|
+
},
|
|
664
|
+
);
|
|
665
|
+
|
|
666
|
+
test("a named host gets no explanation whatever codes it carries", () => {
|
|
667
|
+
/*
|
|
668
|
+
* A code is only stamped on a host that source left unnamed, but a host
|
|
669
|
+
* one source failed on can still be named by the next. The row's name
|
|
670
|
+
* line is then a real name, and "No name found" beside it would be a lie.
|
|
671
|
+
*/
|
|
672
|
+
for (const [dnsHostnameStatus] of REVERSE_DNS_COPY) {
|
|
673
|
+
for (const [netbiosNameStatus] of NETBIOS_COPY) {
|
|
674
|
+
expect(
|
|
675
|
+
explainUnnamedDiscoveredHost({
|
|
676
|
+
host: pingOnlyHost({
|
|
677
|
+
netbiosName: "WB0024KDS02",
|
|
678
|
+
dnsHostnameStatus: dnsHostnameStatus,
|
|
679
|
+
netbiosNameStatus: netbiosNameStatus,
|
|
680
|
+
}),
|
|
681
|
+
scan: REPORTERS_SCAN,
|
|
682
|
+
}),
|
|
683
|
+
).toBeUndefined();
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
});
|
|
687
|
+
|
|
688
|
+
test.each([
|
|
689
|
+
DiscoveryScanStatus.Pending,
|
|
690
|
+
DiscoveryScanStatus.InProgress,
|
|
691
|
+
DiscoveryScanStatus.Completed,
|
|
692
|
+
DiscoveryScanStatus.Failed,
|
|
693
|
+
])(
|
|
694
|
+
"a named host gets no explanation on a %s scan either",
|
|
695
|
+
(status: string) => {
|
|
696
|
+
expect(
|
|
697
|
+
explainUnnamedDiscoveredHost({
|
|
698
|
+
host: pingOnlyHost({ dnsHostname: "kds-01.example.com" }),
|
|
699
|
+
scan: { ...REPORTERS_SCAN, status: status },
|
|
700
|
+
}),
|
|
701
|
+
).toBeUndefined();
|
|
702
|
+
},
|
|
703
|
+
);
|
|
704
|
+
|
|
705
|
+
test.each([
|
|
706
|
+
["the empty string", ""],
|
|
707
|
+
["spaces", " "],
|
|
708
|
+
["a tab and a newline", "\t\n"],
|
|
709
|
+
["null", null],
|
|
710
|
+
["undefined", undefined],
|
|
711
|
+
])(
|
|
712
|
+
"a row whose address is %s gets no explanation",
|
|
713
|
+
(_label: string, ipAddress: unknown) => {
|
|
714
|
+
/*
|
|
715
|
+
* A row with no address cannot be imported, and its checkbox already
|
|
716
|
+
* says so. null is the case that matters: String(null) is "null", which
|
|
717
|
+
* would read as an address.
|
|
718
|
+
*/
|
|
719
|
+
expect(
|
|
720
|
+
explainUnnamedDiscoveredHost({
|
|
721
|
+
host: { ipAddress: ipAddress } as unknown as DiscoveredNetworkDevice,
|
|
722
|
+
scan: REPORTERS_SCAN,
|
|
723
|
+
}),
|
|
724
|
+
).toBeUndefined();
|
|
725
|
+
},
|
|
726
|
+
);
|
|
727
|
+
|
|
728
|
+
test("a row with no address key at all gets no explanation", () => {
|
|
729
|
+
expect(
|
|
730
|
+
explainUnnamedDiscoveredHost({
|
|
731
|
+
host: {} as unknown as DiscoveredNetworkDevice,
|
|
732
|
+
scan: REPORTERS_SCAN,
|
|
733
|
+
}),
|
|
734
|
+
).toBeUndefined();
|
|
735
|
+
});
|
|
736
|
+
|
|
737
|
+
test.each([
|
|
738
|
+
["null", null],
|
|
739
|
+
["undefined", undefined],
|
|
740
|
+
["a string", ADDRESS],
|
|
741
|
+
["a number", 10],
|
|
742
|
+
["a boolean", true],
|
|
743
|
+
["an empty array", []],
|
|
744
|
+
["an array holding a host", [{ ipAddress: ADDRESS }]],
|
|
745
|
+
])(
|
|
746
|
+
"%s instead of a host gets no explanation, and does not throw",
|
|
747
|
+
(_label: string, host: unknown) => {
|
|
748
|
+
expect(
|
|
749
|
+
explainUnnamedDiscoveredHost({
|
|
750
|
+
host: host as DiscoveredNetworkDevice,
|
|
751
|
+
scan: REPORTERS_SCAN,
|
|
752
|
+
}),
|
|
753
|
+
).toBeUndefined();
|
|
754
|
+
},
|
|
755
|
+
);
|
|
756
|
+
|
|
757
|
+
test("a numeric address, as an un-normalised row holds it, is still explained", () => {
|
|
758
|
+
/*
|
|
759
|
+
* normalizeDiscoveredHosts stringifies it, and the row is importable, so
|
|
760
|
+
* it is a real row with a bare address for a name.
|
|
761
|
+
*/
|
|
762
|
+
expect(
|
|
763
|
+
explainUnnamedDiscoveredHost({
|
|
764
|
+
host: { ipAddress: 10 } as unknown as DiscoveredNetworkDevice,
|
|
765
|
+
}),
|
|
766
|
+
).toStrictEqual({
|
|
767
|
+
label: UNNAMED_HOST_LABEL,
|
|
768
|
+
text: REVERSE_DNS_NOT_RECORDED_SENTENCE,
|
|
769
|
+
});
|
|
770
|
+
});
|
|
771
|
+
|
|
772
|
+
test("junk in the name fields of an un-normalised row does not throw", () => {
|
|
773
|
+
const junk: Array<unknown> = [42, {}, [], true, null, ["kds-01"]];
|
|
774
|
+
|
|
775
|
+
for (const value of junk) {
|
|
776
|
+
expect(() => {
|
|
777
|
+
return explainUnnamedDiscoveredHost({
|
|
778
|
+
host: {
|
|
779
|
+
ipAddress: ADDRESS,
|
|
780
|
+
sysName: value,
|
|
781
|
+
dnsHostname: value,
|
|
782
|
+
netbiosName: value,
|
|
783
|
+
snmpReachable: value,
|
|
784
|
+
} as unknown as DiscoveredNetworkDevice,
|
|
785
|
+
scan: REPORTERS_SCAN,
|
|
786
|
+
});
|
|
787
|
+
}).not.toThrow();
|
|
788
|
+
}
|
|
789
|
+
});
|
|
790
|
+
});
|
|
791
|
+
|
|
792
|
+
describe("explainUnnamedDiscoveredHost — each reverse-DNS code", () => {
|
|
793
|
+
/*
|
|
794
|
+
* With no scan and no SNMP flag on the row, the text is the reverse-DNS
|
|
795
|
+
* sentence and its tip and nothing else, so each case can be pinned whole.
|
|
796
|
+
*/
|
|
797
|
+
test.each(REVERSE_DNS_COPY)(
|
|
798
|
+
"%s reads as its own sentence, followed by its tip",
|
|
799
|
+
(
|
|
800
|
+
code: DiscoveredHostReverseDnsStatus,
|
|
801
|
+
sentence: string,
|
|
802
|
+
tip: string | undefined,
|
|
803
|
+
) => {
|
|
804
|
+
expect(
|
|
805
|
+
explainUnnamedDiscoveredHost({
|
|
806
|
+
host: bareHost({ dnsHostnameStatus: code }),
|
|
807
|
+
}),
|
|
808
|
+
).toStrictEqual({
|
|
809
|
+
label: UNNAMED_HOST_LABEL,
|
|
810
|
+
text: tip ? `${sentence} ${tip}` : sentence,
|
|
811
|
+
});
|
|
812
|
+
},
|
|
813
|
+
);
|
|
814
|
+
|
|
815
|
+
test.each(REVERSE_DNS_COPY)(
|
|
816
|
+
"%s on the reporter's scan sits between the SNMP and NetBIOS lines",
|
|
817
|
+
(
|
|
818
|
+
code: DiscoveredHostReverseDnsStatus,
|
|
819
|
+
sentence: string,
|
|
820
|
+
tip: string | undefined,
|
|
821
|
+
) => {
|
|
822
|
+
expect(
|
|
823
|
+
textOf({
|
|
824
|
+
host: pingOnlyHost({
|
|
825
|
+
dnsHostnameStatus: code,
|
|
826
|
+
netbiosNameStatus: DiscoveredHostNetbiosStatus.NoReply,
|
|
827
|
+
}),
|
|
828
|
+
scan: REPORTERS_SCAN,
|
|
829
|
+
}),
|
|
830
|
+
).toBe(
|
|
831
|
+
[
|
|
832
|
+
SNMP_NOT_CHECKED_SENTENCE,
|
|
833
|
+
sentence,
|
|
834
|
+
netbiosSentence(DiscoveredHostNetbiosStatus.NoReply),
|
|
835
|
+
...(tip ? [tip] : []),
|
|
836
|
+
ASK_THE_DEVICE_SNMP_TIP,
|
|
837
|
+
].join(" "),
|
|
838
|
+
);
|
|
839
|
+
},
|
|
840
|
+
);
|
|
841
|
+
|
|
842
|
+
test("only a code the DNS server answered with claims there is no PTR record", () => {
|
|
843
|
+
/*
|
|
844
|
+
* The heart of the issue. "No PTR record" is a fact about the address,
|
|
845
|
+
* and it is only true when the server ANSWERED that way. Every failure
|
|
846
|
+
* of the lookup itself used to be filed as exactly that. So no other
|
|
847
|
+
* code, and no row without a code, may say it.
|
|
848
|
+
*/
|
|
849
|
+
for (const [code] of REVERSE_DNS_COPY) {
|
|
850
|
+
const text: string = textOf({
|
|
851
|
+
host: bareHost({ dnsHostnameStatus: code }),
|
|
852
|
+
});
|
|
853
|
+
|
|
854
|
+
expect(text.includes("no PTR record")).toBe(
|
|
855
|
+
code === DiscoveredHostReverseDnsStatus.NoRecord,
|
|
856
|
+
);
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
expect(textOf({ host: bareHost() })).not.toContain("PTR");
|
|
860
|
+
});
|
|
861
|
+
|
|
862
|
+
test("a timeout and a missing record never read the same", () => {
|
|
863
|
+
expect(
|
|
864
|
+
textOf({
|
|
865
|
+
host: bareHost({
|
|
866
|
+
dnsHostnameStatus: DiscoveredHostReverseDnsStatus.Timeout,
|
|
867
|
+
}),
|
|
868
|
+
}),
|
|
869
|
+
).not.toBe(
|
|
870
|
+
textOf({
|
|
871
|
+
host: bareHost({
|
|
872
|
+
dnsHostnameStatus: DiscoveredHostReverseDnsStatus.NoRecord,
|
|
873
|
+
}),
|
|
874
|
+
}),
|
|
875
|
+
);
|
|
876
|
+
});
|
|
877
|
+
|
|
878
|
+
test("no code claims the probe retried", () => {
|
|
879
|
+
/*
|
|
880
|
+
* A retry has to fit in the naming pass's time budget, so a host that
|
|
881
|
+
* timed out near the deadline may have been asked only once. The copy
|
|
882
|
+
* must be true for that host too.
|
|
883
|
+
*/
|
|
884
|
+
for (const [code] of REVERSE_DNS_COPY) {
|
|
885
|
+
expect(
|
|
886
|
+
textOf({ host: bareHost({ dnsHostnameStatus: code }) }).toLowerCase(),
|
|
887
|
+
).not.toContain("retry");
|
|
888
|
+
}
|
|
889
|
+
});
|
|
890
|
+
});
|
|
891
|
+
|
|
892
|
+
describe("explainUnnamedDiscoveredHost — each NetBIOS code", () => {
|
|
893
|
+
test.each(NETBIOS_COPY)(
|
|
894
|
+
"%s reads as its own sentence, after the reverse-DNS line",
|
|
895
|
+
(code: DiscoveredHostNetbiosStatus, sentence: string) => {
|
|
896
|
+
expect(
|
|
897
|
+
explainUnnamedDiscoveredHost({
|
|
898
|
+
host: bareHost({ netbiosNameStatus: code }),
|
|
899
|
+
}),
|
|
900
|
+
).toStrictEqual({
|
|
901
|
+
label: UNNAMED_HOST_LABEL,
|
|
902
|
+
text: `${REVERSE_DNS_NOT_RECORDED_SENTENCE} ${sentence}`,
|
|
903
|
+
});
|
|
904
|
+
},
|
|
905
|
+
);
|
|
906
|
+
|
|
907
|
+
test.each(NETBIOS_COPY)(
|
|
908
|
+
"%s on an SNMP scan with NetBIOS on reads in full",
|
|
909
|
+
(code: DiscoveredHostNetbiosStatus, sentence: string) => {
|
|
910
|
+
expect(
|
|
911
|
+
textOf({
|
|
912
|
+
host: pingOnlyHost({
|
|
913
|
+
dnsHostnameStatus: DiscoveredHostReverseDnsStatus.NoRecord,
|
|
914
|
+
netbiosNameStatus: code,
|
|
915
|
+
}),
|
|
916
|
+
scan: SNMP_AND_NETBIOS_SCAN,
|
|
917
|
+
}),
|
|
918
|
+
).toBe(
|
|
919
|
+
[
|
|
920
|
+
SNMP_NO_ANSWER_SENTENCE,
|
|
921
|
+
reverseDnsSentence(DiscoveredHostReverseDnsStatus.NoRecord),
|
|
922
|
+
sentence,
|
|
923
|
+
NO_RECORD_TIP,
|
|
924
|
+
].join(" "),
|
|
925
|
+
);
|
|
926
|
+
},
|
|
927
|
+
);
|
|
928
|
+
|
|
929
|
+
test.each(NETBIOS_COPY)(
|
|
930
|
+
"%s is what the row says even on a global probe",
|
|
931
|
+
(code: DiscoveredHostNetbiosStatus, sentence: string) => {
|
|
932
|
+
/*
|
|
933
|
+
* The probe's code is a fact about what it did; the global-probe
|
|
934
|
+
* inference is only a fallback for rows that carry none.
|
|
935
|
+
*/
|
|
936
|
+
expect(
|
|
937
|
+
textOf({
|
|
938
|
+
host: pingOnlyHost({ netbiosNameStatus: code }),
|
|
939
|
+
scan: SNMP_AND_NETBIOS_SCAN,
|
|
940
|
+
isGlobalProbe: true,
|
|
941
|
+
}),
|
|
942
|
+
).toBe(
|
|
943
|
+
[
|
|
944
|
+
SNMP_NO_ANSWER_SENTENCE,
|
|
945
|
+
REVERSE_DNS_NOT_RECORDED_SENTENCE,
|
|
946
|
+
sentence,
|
|
947
|
+
].join(" "),
|
|
948
|
+
);
|
|
949
|
+
},
|
|
950
|
+
);
|
|
951
|
+
|
|
952
|
+
test.each(NETBIOS_COPY)(
|
|
953
|
+
"%s is what the row says after the scan's NetBIOS setting was turned off",
|
|
954
|
+
(code: DiscoveredHostNetbiosStatus, sentence: string) => {
|
|
955
|
+
/*
|
|
956
|
+
* The results stay on the scan when the setting changes. The row says
|
|
957
|
+
* what the probe did when it ran, and suggesting the setting that did
|
|
958
|
+
* run would be nonsense, so there is no tip.
|
|
959
|
+
*/
|
|
960
|
+
expect(
|
|
961
|
+
textOf({
|
|
962
|
+
host: pingOnlyHost({ netbiosNameStatus: code }),
|
|
963
|
+
scan: SNMP_SCAN,
|
|
964
|
+
}),
|
|
965
|
+
).toBe(
|
|
966
|
+
[
|
|
967
|
+
SNMP_NO_ANSWER_SENTENCE,
|
|
968
|
+
REVERSE_DNS_NOT_RECORDED_SENTENCE,
|
|
969
|
+
sentence,
|
|
970
|
+
].join(" "),
|
|
971
|
+
);
|
|
972
|
+
},
|
|
973
|
+
);
|
|
974
|
+
});
|
|
975
|
+
|
|
976
|
+
describe("explainUnnamedDiscoveredHost — rows with no codes (older probes)", () => {
|
|
977
|
+
/*
|
|
978
|
+
* Every row stored before the codes, and every row from a probe that has
|
|
979
|
+
* not been upgraded. The only facts are the scan's settings and the row's
|
|
980
|
+
* SNMP flag, so the reverse-DNS line says no name was recorded and claims
|
|
981
|
+
* no reason. In particular it must not say "no PTR record", which is the
|
|
982
|
+
* misdiagnosis the issue was about.
|
|
983
|
+
*/
|
|
984
|
+
test("the reporter's scan on a private probe", () => {
|
|
985
|
+
expect(
|
|
986
|
+
explainUnnamedDiscoveredHost({
|
|
987
|
+
host: pingOnlyHost(),
|
|
988
|
+
scan: REPORTERS_SCAN,
|
|
989
|
+
isGlobalProbe: false,
|
|
990
|
+
}),
|
|
991
|
+
).toStrictEqual({
|
|
992
|
+
label: UNNAMED_HOST_LABEL,
|
|
993
|
+
text: [
|
|
994
|
+
SNMP_NOT_CHECKED_SENTENCE,
|
|
995
|
+
REVERSE_DNS_NOT_RECORDED_SENTENCE,
|
|
996
|
+
NETBIOS_NOT_RECORDED_SENTENCE,
|
|
997
|
+
ASK_THE_DEVICE_SNMP_TIP,
|
|
998
|
+
].join(" "),
|
|
999
|
+
});
|
|
1000
|
+
});
|
|
1001
|
+
|
|
1002
|
+
test("the reporter's scan on a global probe says NetBIOS was never asked", () => {
|
|
1003
|
+
/*
|
|
1004
|
+
* The global-probe refusal predates the codes, so for a row from any
|
|
1005
|
+
* probe version on a global probe, "not asked" is the truth. The bundled
|
|
1006
|
+
* self-hosted probes register as global.
|
|
1007
|
+
*/
|
|
1008
|
+
expect(
|
|
1009
|
+
textOf({
|
|
1010
|
+
host: pingOnlyHost(),
|
|
1011
|
+
scan: REPORTERS_SCAN,
|
|
1012
|
+
isGlobalProbe: true,
|
|
1013
|
+
}),
|
|
1014
|
+
).toBe(
|
|
1015
|
+
[
|
|
1016
|
+
SNMP_NOT_CHECKED_SENTENCE,
|
|
1017
|
+
REVERSE_DNS_NOT_RECORDED_SENTENCE,
|
|
1018
|
+
GLOBAL_PROBE_NETBIOS_SENTENCE,
|
|
1019
|
+
ASK_THE_DEVICE_SNMP_TIP,
|
|
1020
|
+
].join(" "),
|
|
1021
|
+
);
|
|
1022
|
+
});
|
|
1023
|
+
|
|
1024
|
+
test("an unknown probe reads as not global", () => {
|
|
1025
|
+
expect(textOf({ host: pingOnlyHost(), scan: REPORTERS_SCAN })).toBe(
|
|
1026
|
+
textOf({
|
|
1027
|
+
host: pingOnlyHost(),
|
|
1028
|
+
scan: REPORTERS_SCAN,
|
|
1029
|
+
isGlobalProbe: false,
|
|
1030
|
+
}),
|
|
1031
|
+
);
|
|
1032
|
+
});
|
|
1033
|
+
|
|
1034
|
+
test.each([
|
|
1035
|
+
["the string true", "true"],
|
|
1036
|
+
["one", 1],
|
|
1037
|
+
["null", null],
|
|
1038
|
+
])(
|
|
1039
|
+
"isGlobalProbe of %s reads as not global",
|
|
1040
|
+
(_label: string, value: unknown) => {
|
|
1041
|
+
expect(
|
|
1042
|
+
textOf({
|
|
1043
|
+
host: pingOnlyHost(),
|
|
1044
|
+
scan: REPORTERS_SCAN,
|
|
1045
|
+
isGlobalProbe: value as boolean,
|
|
1046
|
+
}),
|
|
1047
|
+
).toContain(NETBIOS_NOT_RECORDED_SENTENCE);
|
|
1048
|
+
},
|
|
1049
|
+
);
|
|
1050
|
+
|
|
1051
|
+
test("a ping-only scan with NetBIOS off suggests both ways of asking the device", () => {
|
|
1052
|
+
expect(textOf({ host: pingOnlyHost(), scan: ICMP_ONLY_SCAN })).toBe(
|
|
1053
|
+
[
|
|
1054
|
+
SNMP_NOT_CHECKED_SENTENCE,
|
|
1055
|
+
REVERSE_DNS_NOT_RECORDED_SENTENCE,
|
|
1056
|
+
NETBIOS_OFF_SENTENCE,
|
|
1057
|
+
ASK_THE_DEVICE_TIP,
|
|
1058
|
+
].join(" "),
|
|
1059
|
+
);
|
|
1060
|
+
});
|
|
1061
|
+
|
|
1062
|
+
test("an SNMP scan the host did not answer, with NetBIOS off", () => {
|
|
1063
|
+
expect(textOf({ host: pingOnlyHost(), scan: SNMP_SCAN })).toBe(
|
|
1064
|
+
[
|
|
1065
|
+
SNMP_NO_ANSWER_SENTENCE,
|
|
1066
|
+
REVERSE_DNS_NOT_RECORDED_SENTENCE,
|
|
1067
|
+
NETBIOS_OFF_SENTENCE,
|
|
1068
|
+
ASK_THE_DEVICE_NETBIOS_TIP,
|
|
1069
|
+
].join(" "),
|
|
1070
|
+
);
|
|
1071
|
+
});
|
|
1072
|
+
|
|
1073
|
+
test("an SNMP scan the host answered without a sysName, with NetBIOS on", () => {
|
|
1074
|
+
expect(
|
|
1075
|
+
textOf({
|
|
1076
|
+
host: bareHost({ snmpReachable: true, sysName: " " }),
|
|
1077
|
+
scan: SNMP_AND_NETBIOS_SCAN,
|
|
1078
|
+
}),
|
|
1079
|
+
).toBe(
|
|
1080
|
+
[
|
|
1081
|
+
SNMP_NO_SYSNAME_SENTENCE,
|
|
1082
|
+
REVERSE_DNS_NOT_RECORDED_SENTENCE,
|
|
1083
|
+
NETBIOS_NOT_RECORDED_SENTENCE,
|
|
1084
|
+
].join(" "),
|
|
1085
|
+
);
|
|
1086
|
+
});
|
|
1087
|
+
|
|
1088
|
+
test("a row from before snmpReachable existed reads as an SNMP answer with no name", () => {
|
|
1089
|
+
/*
|
|
1090
|
+
* Back then every host listed had answered SNMP, since ping-only hosts
|
|
1091
|
+
* were not reported at all.
|
|
1092
|
+
*/
|
|
1093
|
+
expect(textOf({ host: bareHost(), scan: SNMP_SCAN })).toBe(
|
|
1094
|
+
[
|
|
1095
|
+
SNMP_NO_SYSNAME_SENTENCE,
|
|
1096
|
+
REVERSE_DNS_NOT_RECORDED_SENTENCE,
|
|
1097
|
+
NETBIOS_OFF_SENTENCE,
|
|
1098
|
+
ASK_THE_DEVICE_NETBIOS_TIP,
|
|
1099
|
+
].join(" "),
|
|
1100
|
+
);
|
|
1101
|
+
});
|
|
1102
|
+
|
|
1103
|
+
test("with no scan and no SNMP flag, the row says only what it can", () => {
|
|
1104
|
+
expect(explainUnnamedDiscoveredHost({ host: bareHost() })).toStrictEqual({
|
|
1105
|
+
label: UNNAMED_HOST_LABEL,
|
|
1106
|
+
text: REVERSE_DNS_NOT_RECORDED_SENTENCE,
|
|
1107
|
+
});
|
|
1108
|
+
});
|
|
1109
|
+
|
|
1110
|
+
test("a legacy row gets no reverse-DNS tip", () => {
|
|
1111
|
+
/*
|
|
1112
|
+
* Both tips are advice about a specific failure, and a row with no code
|
|
1113
|
+
* has no failure to advise on.
|
|
1114
|
+
*/
|
|
1115
|
+
for (const scan of [
|
|
1116
|
+
REPORTERS_SCAN,
|
|
1117
|
+
ICMP_ONLY_SCAN,
|
|
1118
|
+
SNMP_SCAN,
|
|
1119
|
+
SNMP_AND_NETBIOS_SCAN,
|
|
1120
|
+
undefined,
|
|
1121
|
+
]) {
|
|
1122
|
+
const text: string = textOf({ host: pingOnlyHost(), scan: scan });
|
|
1123
|
+
|
|
1124
|
+
expect(text).not.toContain(NO_RECORD_TIP);
|
|
1125
|
+
expect(text).not.toContain(TRANSIENT_FAILURE_TIP);
|
|
1126
|
+
}
|
|
1127
|
+
});
|
|
1128
|
+
});
|
|
1129
|
+
|
|
1130
|
+
describe("explainUnnamedDiscoveredHost — the scan's state", () => {
|
|
1131
|
+
test.each([
|
|
1132
|
+
["the reporter's scan", REPORTERS_SCAN],
|
|
1133
|
+
["a ping-only scan", ICMP_ONLY_SCAN],
|
|
1134
|
+
["an SNMP scan", SNMP_SCAN],
|
|
1135
|
+
["an SNMP scan with NetBIOS", SNMP_AND_NETBIOS_SCAN],
|
|
1136
|
+
])(
|
|
1137
|
+
"%s still sweeping says names come later",
|
|
1138
|
+
(_label: string, scan: DiscoveredHostNamingScan) => {
|
|
1139
|
+
/*
|
|
1140
|
+
* Names are looked up only after the sweep finishes, so every host a
|
|
1141
|
+
* running scan has reported is unnamed by construction. Anything about
|
|
1142
|
+
* the individual sources would be explaining a question not yet asked.
|
|
1143
|
+
*/
|
|
1144
|
+
for (const isGlobalProbe of [undefined, true, false]) {
|
|
1145
|
+
expect(
|
|
1146
|
+
explainUnnamedDiscoveredHost({
|
|
1147
|
+
host: pingOnlyHost(),
|
|
1148
|
+
scan: { ...scan, status: DiscoveryScanStatus.InProgress },
|
|
1149
|
+
isGlobalProbe: isGlobalProbe,
|
|
1150
|
+
}),
|
|
1151
|
+
).toStrictEqual({
|
|
1152
|
+
label: NOT_YET_NAMED_HOST_LABEL,
|
|
1153
|
+
text: IN_PROGRESS_EXPLANATION,
|
|
1154
|
+
});
|
|
1155
|
+
}
|
|
1156
|
+
},
|
|
1157
|
+
);
|
|
1158
|
+
|
|
1159
|
+
test("a scan still sweeping explains a row that carries a reverse-DNS code by its code", () => {
|
|
1160
|
+
/*
|
|
1161
|
+
* A recurring scan keeps its previous run's results until the new run
|
|
1162
|
+
* reports. Those rows went through the naming passes, and their codes
|
|
1163
|
+
* say what happened.
|
|
1164
|
+
*/
|
|
1165
|
+
expect(
|
|
1166
|
+
explainUnnamedDiscoveredHost({
|
|
1167
|
+
host: pingOnlyHost({
|
|
1168
|
+
dnsHostnameStatus: DiscoveredHostReverseDnsStatus.Timeout,
|
|
1169
|
+
}),
|
|
1170
|
+
scan: { ...REPORTERS_SCAN, status: DiscoveryScanStatus.InProgress },
|
|
1171
|
+
}),
|
|
1172
|
+
).toStrictEqual({
|
|
1173
|
+
label: UNNAMED_HOST_LABEL,
|
|
1174
|
+
text: [
|
|
1175
|
+
SNMP_NOT_CHECKED_SENTENCE,
|
|
1176
|
+
reverseDnsSentence(DiscoveredHostReverseDnsStatus.Timeout),
|
|
1177
|
+
NETBIOS_NOT_RECORDED_SENTENCE,
|
|
1178
|
+
TRANSIENT_FAILURE_TIP,
|
|
1179
|
+
ASK_THE_DEVICE_SNMP_TIP,
|
|
1180
|
+
].join(" "),
|
|
1181
|
+
});
|
|
1182
|
+
});
|
|
1183
|
+
|
|
1184
|
+
test("a scan still sweeping explains a row that carries only a NetBIOS code by its code", () => {
|
|
1185
|
+
expect(
|
|
1186
|
+
explainUnnamedDiscoveredHost({
|
|
1187
|
+
host: pingOnlyHost({
|
|
1188
|
+
netbiosNameStatus: DiscoveredHostNetbiosStatus.NoReply,
|
|
1189
|
+
}),
|
|
1190
|
+
scan: { ...REPORTERS_SCAN, status: DiscoveryScanStatus.InProgress },
|
|
1191
|
+
}),
|
|
1192
|
+
).toStrictEqual({
|
|
1193
|
+
label: UNNAMED_HOST_LABEL,
|
|
1194
|
+
text: [
|
|
1195
|
+
SNMP_NOT_CHECKED_SENTENCE,
|
|
1196
|
+
REVERSE_DNS_NOT_RECORDED_SENTENCE,
|
|
1197
|
+
netbiosSentence(DiscoveredHostNetbiosStatus.NoReply),
|
|
1198
|
+
ASK_THE_DEVICE_SNMP_TIP,
|
|
1199
|
+
].join(" "),
|
|
1200
|
+
});
|
|
1201
|
+
});
|
|
1202
|
+
|
|
1203
|
+
test("a failed scan with no codes says it stopped before naming", () => {
|
|
1204
|
+
for (const scan of [
|
|
1205
|
+
REPORTERS_SCAN,
|
|
1206
|
+
ICMP_ONLY_SCAN,
|
|
1207
|
+
SNMP_SCAN,
|
|
1208
|
+
SNMP_AND_NETBIOS_SCAN,
|
|
1209
|
+
]) {
|
|
1210
|
+
expect(
|
|
1211
|
+
explainUnnamedDiscoveredHost({
|
|
1212
|
+
host: pingOnlyHost(),
|
|
1213
|
+
scan: { ...scan, status: DiscoveryScanStatus.Failed },
|
|
1214
|
+
isGlobalProbe: true,
|
|
1215
|
+
}),
|
|
1216
|
+
).toStrictEqual({
|
|
1217
|
+
label: UNNAMED_HOST_LABEL,
|
|
1218
|
+
text: FAILED_SCAN_EXPLANATION,
|
|
1219
|
+
});
|
|
1220
|
+
}
|
|
1221
|
+
});
|
|
1222
|
+
|
|
1223
|
+
test("a failed scan whose rows carry codes is explained by the codes", () => {
|
|
1224
|
+
expect(
|
|
1225
|
+
textOf({
|
|
1226
|
+
host: pingOnlyHost({
|
|
1227
|
+
dnsHostnameStatus: DiscoveredHostReverseDnsStatus.NoRecord,
|
|
1228
|
+
netbiosNameStatus: DiscoveredHostNetbiosStatus.NoReply,
|
|
1229
|
+
}),
|
|
1230
|
+
scan: { ...REPORTERS_SCAN, status: DiscoveryScanStatus.Failed },
|
|
1231
|
+
}),
|
|
1232
|
+
).toBe(
|
|
1233
|
+
[
|
|
1234
|
+
SNMP_NOT_CHECKED_SENTENCE,
|
|
1235
|
+
reverseDnsSentence(DiscoveredHostReverseDnsStatus.NoRecord),
|
|
1236
|
+
netbiosSentence(DiscoveredHostNetbiosStatus.NoReply),
|
|
1237
|
+
NO_RECORD_TIP,
|
|
1238
|
+
ASK_THE_DEVICE_SNMP_TIP,
|
|
1239
|
+
].join(" "),
|
|
1240
|
+
);
|
|
1241
|
+
});
|
|
1242
|
+
|
|
1243
|
+
test("a pending recurring scan's previous results read like a completed scan's", () => {
|
|
1244
|
+
/*
|
|
1245
|
+
* A recurring scan goes back to Pending between runs and keeps the last
|
|
1246
|
+
* run's hosts. Those are finished results, not a sweep in progress.
|
|
1247
|
+
*/
|
|
1248
|
+
for (const host of [
|
|
1249
|
+
pingOnlyHost(),
|
|
1250
|
+
pingOnlyHost({
|
|
1251
|
+
dnsHostnameStatus: DiscoveredHostReverseDnsStatus.Refused,
|
|
1252
|
+
}),
|
|
1253
|
+
]) {
|
|
1254
|
+
expect(
|
|
1255
|
+
explainUnnamedDiscoveredHost({
|
|
1256
|
+
host: host,
|
|
1257
|
+
scan: { ...REPORTERS_SCAN, status: DiscoveryScanStatus.Pending },
|
|
1258
|
+
}),
|
|
1259
|
+
).toStrictEqual(
|
|
1260
|
+
explainUnnamedDiscoveredHost({ host: host, scan: REPORTERS_SCAN }),
|
|
1261
|
+
);
|
|
1262
|
+
}
|
|
1263
|
+
});
|
|
1264
|
+
|
|
1265
|
+
test.each([
|
|
1266
|
+
["no status", undefined],
|
|
1267
|
+
["a null status", null],
|
|
1268
|
+
["a lower-case in progress", "in progress"],
|
|
1269
|
+
["an upper-case IN PROGRESS", "IN PROGRESS"],
|
|
1270
|
+
["a padded In Progress", " In Progress "],
|
|
1271
|
+
["a lower-case failed", "failed"],
|
|
1272
|
+
["a status this build does not know", "Cancelled"],
|
|
1273
|
+
])(
|
|
1274
|
+
"%s reads like a completed scan",
|
|
1275
|
+
(_label: string, status: string | null | undefined) => {
|
|
1276
|
+
/*
|
|
1277
|
+
* The column holds exact strings. Anything else is not a state this
|
|
1278
|
+
* build can speak for, and the per-source lines are true whatever the
|
|
1279
|
+
* state is.
|
|
1280
|
+
*/
|
|
1281
|
+
expect(
|
|
1282
|
+
explainUnnamedDiscoveredHost({
|
|
1283
|
+
host: pingOnlyHost(),
|
|
1284
|
+
scan: { ...REPORTERS_SCAN, status: status },
|
|
1285
|
+
}),
|
|
1286
|
+
).toStrictEqual(
|
|
1287
|
+
explainUnnamedDiscoveredHost({
|
|
1288
|
+
host: pingOnlyHost(),
|
|
1289
|
+
scan: REPORTERS_SCAN,
|
|
1290
|
+
}),
|
|
1291
|
+
);
|
|
1292
|
+
},
|
|
1293
|
+
);
|
|
1294
|
+
});
|
|
1295
|
+
|
|
1296
|
+
describe("explainUnnamedDiscoveredHost — the SNMP line", () => {
|
|
1297
|
+
test.each([
|
|
1298
|
+
["no SNMP flag", undefined],
|
|
1299
|
+
["an SNMP answer", true],
|
|
1300
|
+
["no SNMP answer", false],
|
|
1301
|
+
])(
|
|
1302
|
+
"a ping-only scan says SNMP was not checked, for a row with %s",
|
|
1303
|
+
(_label: string, snmpReachable: boolean | undefined) => {
|
|
1304
|
+
expect(
|
|
1305
|
+
textOf({
|
|
1306
|
+
host: bareHost({ snmpReachable: snmpReachable }),
|
|
1307
|
+
scan: ICMP_ONLY_SCAN,
|
|
1308
|
+
}).startsWith(`${SNMP_NOT_CHECKED_SENTENCE} `),
|
|
1309
|
+
).toBe(true);
|
|
1310
|
+
},
|
|
1311
|
+
);
|
|
1312
|
+
|
|
1313
|
+
test.each([
|
|
1314
|
+
["true", true],
|
|
1315
|
+
["absent", undefined],
|
|
1316
|
+
["null", null],
|
|
1317
|
+
])(
|
|
1318
|
+
"an SNMP scan (isSnmpEnabled %s) says which way SNMP went",
|
|
1319
|
+
(_label: string, isSnmpEnabled: boolean | null | undefined) => {
|
|
1320
|
+
/*
|
|
1321
|
+
* Absent reads as SNMP on, as ScanModeUtil says: only an explicit false
|
|
1322
|
+
* turns it off.
|
|
1323
|
+
*/
|
|
1324
|
+
const scan: DiscoveredHostNamingScan = {
|
|
1325
|
+
...SNMP_SCAN,
|
|
1326
|
+
isSnmpEnabled: isSnmpEnabled,
|
|
1327
|
+
};
|
|
1328
|
+
|
|
1329
|
+
expect(
|
|
1330
|
+
textOf({ host: bareHost({ snmpReachable: false }), scan: scan }),
|
|
1331
|
+
).toContain(SNMP_NO_ANSWER_SENTENCE);
|
|
1332
|
+
expect(
|
|
1333
|
+
textOf({ host: bareHost({ snmpReachable: true }), scan: scan }),
|
|
1334
|
+
).toContain(SNMP_NO_SYSNAME_SENTENCE);
|
|
1335
|
+
expect(textOf({ host: bareHost(), scan: scan })).toContain(
|
|
1336
|
+
SNMP_NO_SYSNAME_SENTENCE,
|
|
1337
|
+
);
|
|
1338
|
+
},
|
|
1339
|
+
);
|
|
1340
|
+
|
|
1341
|
+
test("with no scan, only the row's own SNMP flag speaks", () => {
|
|
1342
|
+
expect(
|
|
1343
|
+
textOf({ host: bareHost({ snmpReachable: false }) }).startsWith(
|
|
1344
|
+
`${SNMP_NO_ANSWER_SENTENCE} `,
|
|
1345
|
+
),
|
|
1346
|
+
).toBe(true);
|
|
1347
|
+
expect(
|
|
1348
|
+
textOf({ host: bareHost({ snmpReachable: true }) }).startsWith(
|
|
1349
|
+
`${SNMP_NO_SYSNAME_SENTENCE} `,
|
|
1350
|
+
),
|
|
1351
|
+
).toBe(true);
|
|
1352
|
+
expect(
|
|
1353
|
+
textOf({ host: bareHost() }).startsWith(
|
|
1354
|
+
REVERSE_DNS_NOT_RECORDED_SENTENCE,
|
|
1355
|
+
),
|
|
1356
|
+
).toBe(true);
|
|
1357
|
+
});
|
|
1358
|
+
|
|
1359
|
+
test("a whitespace-only sysName is explained as no name", () => {
|
|
1360
|
+
/*
|
|
1361
|
+
* The name line trims it away and shows the address, so the row needs
|
|
1362
|
+
* the hint, and the SNMP line is right that no usable name came back.
|
|
1363
|
+
*/
|
|
1364
|
+
expect(
|
|
1365
|
+
textOf({
|
|
1366
|
+
host: bareHost({ snmpReachable: true, sysName: " \t " }),
|
|
1367
|
+
scan: SNMP_SCAN,
|
|
1368
|
+
}).startsWith(`${SNMP_NO_SYSNAME_SENTENCE} `),
|
|
1369
|
+
).toBe(true);
|
|
1370
|
+
});
|
|
1371
|
+
});
|
|
1372
|
+
|
|
1373
|
+
describe("explainUnnamedDiscoveredHost — the NetBIOS line", () => {
|
|
1374
|
+
test.each([
|
|
1375
|
+
["false", false],
|
|
1376
|
+
["absent", undefined],
|
|
1377
|
+
["null", null],
|
|
1378
|
+
["the string true", "true"],
|
|
1379
|
+
["one", 1],
|
|
1380
|
+
])(
|
|
1381
|
+
"a scan whose NetBIOS setting is %s says NetBIOS was off",
|
|
1382
|
+
(_label: string, value: unknown) => {
|
|
1383
|
+
/*
|
|
1384
|
+
* On only when exactly true, which is how the probe reads the column.
|
|
1385
|
+
* Saying anything else would describe a lookup the probe never ran.
|
|
1386
|
+
*/
|
|
1387
|
+
expect(
|
|
1388
|
+
textOf({
|
|
1389
|
+
host: pingOnlyHost(),
|
|
1390
|
+
scan: { ...SNMP_SCAN, isNetbiosLookupEnabled: value as boolean },
|
|
1391
|
+
}),
|
|
1392
|
+
).toContain(` ${NETBIOS_OFF_SENTENCE}`);
|
|
1393
|
+
},
|
|
1394
|
+
);
|
|
1395
|
+
|
|
1396
|
+
test("NetBIOS on with no code says no name was recorded", () => {
|
|
1397
|
+
expect(textOf({ host: pingOnlyHost(), scan: SNMP_AND_NETBIOS_SCAN })).toBe(
|
|
1398
|
+
[
|
|
1399
|
+
SNMP_NO_ANSWER_SENTENCE,
|
|
1400
|
+
REVERSE_DNS_NOT_RECORDED_SENTENCE,
|
|
1401
|
+
NETBIOS_NOT_RECORDED_SENTENCE,
|
|
1402
|
+
].join(" "),
|
|
1403
|
+
);
|
|
1404
|
+
});
|
|
1405
|
+
|
|
1406
|
+
test("NetBIOS on with no code, on a global probe, says it was never asked", () => {
|
|
1407
|
+
expect(
|
|
1408
|
+
textOf({
|
|
1409
|
+
host: pingOnlyHost(),
|
|
1410
|
+
scan: SNMP_AND_NETBIOS_SCAN,
|
|
1411
|
+
isGlobalProbe: true,
|
|
1412
|
+
}),
|
|
1413
|
+
).toBe(
|
|
1414
|
+
[
|
|
1415
|
+
SNMP_NO_ANSWER_SENTENCE,
|
|
1416
|
+
REVERSE_DNS_NOT_RECORDED_SENTENCE,
|
|
1417
|
+
GLOBAL_PROBE_NETBIOS_SENTENCE,
|
|
1418
|
+
].join(" "),
|
|
1419
|
+
);
|
|
1420
|
+
});
|
|
1421
|
+
|
|
1422
|
+
test("with no scan and no code, there is no NetBIOS line at all", () => {
|
|
1423
|
+
/*
|
|
1424
|
+
* Without the scan there is no telling whether NetBIOS was ever on, so
|
|
1425
|
+
* any sentence would be a guess, and "global probe" is not a reason to
|
|
1426
|
+
* say it was asked for.
|
|
1427
|
+
*/
|
|
1428
|
+
for (const isGlobalProbe of [undefined, true, false]) {
|
|
1429
|
+
expect(
|
|
1430
|
+
textOf({ host: pingOnlyHost(), isGlobalProbe: isGlobalProbe }),
|
|
1431
|
+
).not.toContain("NetBIOS");
|
|
1432
|
+
}
|
|
1433
|
+
});
|
|
1434
|
+
|
|
1435
|
+
test("with no scan, a NetBIOS code still speaks", () => {
|
|
1436
|
+
expect(
|
|
1437
|
+
textOf({
|
|
1438
|
+
host: bareHost({
|
|
1439
|
+
netbiosNameStatus: DiscoveredHostNetbiosStatus.SkippedHostCap,
|
|
1440
|
+
}),
|
|
1441
|
+
}),
|
|
1442
|
+
).toBe(
|
|
1443
|
+
[
|
|
1444
|
+
REVERSE_DNS_NOT_RECORDED_SENTENCE,
|
|
1445
|
+
netbiosSentence(DiscoveredHostNetbiosStatus.SkippedHostCap),
|
|
1446
|
+
].join(" "),
|
|
1447
|
+
);
|
|
1448
|
+
});
|
|
1449
|
+
});
|
|
1450
|
+
|
|
1451
|
+
describe("explainUnnamedDiscoveredHost — the tips", () => {
|
|
1452
|
+
test("a missing PTR record points at the probe's own DNS server", () => {
|
|
1453
|
+
/*
|
|
1454
|
+
* The advice that matters most for the report behind this: the probe
|
|
1455
|
+
* resolves through its own DNS server, which need not be the one the
|
|
1456
|
+
* operator checked with.
|
|
1457
|
+
*/
|
|
1458
|
+
expect(
|
|
1459
|
+
textOf({
|
|
1460
|
+
host: bareHost({
|
|
1461
|
+
dnsHostnameStatus: DiscoveredHostReverseDnsStatus.NoRecord,
|
|
1462
|
+
}),
|
|
1463
|
+
}),
|
|
1464
|
+
).toBe(
|
|
1465
|
+
`${reverseDnsSentence(DiscoveredHostReverseDnsStatus.NoRecord)} ${NO_RECORD_TIP}`,
|
|
1466
|
+
);
|
|
1467
|
+
});
|
|
1468
|
+
|
|
1469
|
+
test("the no-record tip sends the operator to where the probe resolves, not the machine under it", () => {
|
|
1470
|
+
/*
|
|
1471
|
+
* The probe runs in a container or a Kubernetes pod. A pod resolves
|
|
1472
|
+
* through the cluster DNS, not the node's resolv.conf, and a container's
|
|
1473
|
+
* resolver can differ from its host's, so nslookup "from the probe's
|
|
1474
|
+
* host" could name the device while the probe's own lookup could not —
|
|
1475
|
+
* the very mismatch this tip exists to expose (#3916).
|
|
1476
|
+
*/
|
|
1477
|
+
expect(NO_RECORD_TIP).toContain("nslookup");
|
|
1478
|
+
expect(NO_RECORD_TIP).toContain("inside the probe's container or pod");
|
|
1479
|
+
expect(NO_RECORD_TIP).not.toContain("probe's host");
|
|
1480
|
+
});
|
|
1481
|
+
|
|
1482
|
+
test.each([
|
|
1483
|
+
DiscoveredHostReverseDnsStatus.Timeout,
|
|
1484
|
+
DiscoveredHostReverseDnsStatus.ServerFailure,
|
|
1485
|
+
DiscoveredHostReverseDnsStatus.Refused,
|
|
1486
|
+
DiscoveredHostReverseDnsStatus.Unreachable,
|
|
1487
|
+
DiscoveredHostReverseDnsStatus.Failed,
|
|
1488
|
+
DiscoveredHostReverseDnsStatus.SkippedNoResolver,
|
|
1489
|
+
])(
|
|
1490
|
+
"%s is told to rescan and check the probe's DNS servers",
|
|
1491
|
+
(code: DiscoveredHostReverseDnsStatus) => {
|
|
1492
|
+
const text: string = textOf({
|
|
1493
|
+
host: bareHost({ dnsHostnameStatus: code }),
|
|
1494
|
+
});
|
|
1495
|
+
|
|
1496
|
+
expect(text.endsWith(` ${TRANSIENT_FAILURE_TIP}`)).toBe(true);
|
|
1497
|
+
expect(text).not.toContain(NO_RECORD_TIP);
|
|
1498
|
+
},
|
|
1499
|
+
);
|
|
1500
|
+
|
|
1501
|
+
test("a probe that stopped asking after every lookup failed points at its DNS servers", () => {
|
|
1502
|
+
/*
|
|
1503
|
+
* Its address was never looked up, but only because every lookup before
|
|
1504
|
+
* it failed. The DNS servers the probe uses are the thing to check, and
|
|
1505
|
+
* this row is where the operator is looking.
|
|
1506
|
+
*/
|
|
1507
|
+
expect(
|
|
1508
|
+
textOf({
|
|
1509
|
+
host: bareHost({
|
|
1510
|
+
dnsHostnameStatus: DiscoveredHostReverseDnsStatus.SkippedNoResolver,
|
|
1511
|
+
}),
|
|
1512
|
+
}),
|
|
1513
|
+
).toBe(
|
|
1514
|
+
`${reverseDnsSentence(DiscoveredHostReverseDnsStatus.SkippedNoResolver)} ${TRANSIENT_FAILURE_TIP}`,
|
|
1515
|
+
);
|
|
1516
|
+
});
|
|
1517
|
+
|
|
1518
|
+
test.each([
|
|
1519
|
+
DiscoveredHostReverseDnsStatus.UnusableName,
|
|
1520
|
+
DiscoveredHostReverseDnsStatus.SkippedTimeBudget,
|
|
1521
|
+
])("%s gets no reverse-DNS tip", (code: DiscoveredHostReverseDnsStatus) => {
|
|
1522
|
+
const text: string = textOf({
|
|
1523
|
+
host: bareHost({ dnsHostnameStatus: code }),
|
|
1524
|
+
});
|
|
1525
|
+
|
|
1526
|
+
expect(text).toBe(reverseDnsSentence(code));
|
|
1527
|
+
});
|
|
1528
|
+
|
|
1529
|
+
/*
|
|
1530
|
+
* [isSnmpEnabled, isNetbiosLookupEnabled, isGlobalProbe, the tip]. Only a
|
|
1531
|
+
* source that was off, and that would run if turned on, is suggested.
|
|
1532
|
+
*/
|
|
1533
|
+
test.each([
|
|
1534
|
+
[false, false, false, ASK_THE_DEVICE_TIP],
|
|
1535
|
+
[false, false, undefined, ASK_THE_DEVICE_TIP],
|
|
1536
|
+
[false, false, true, ASK_THE_DEVICE_SNMP_TIP],
|
|
1537
|
+
[false, true, false, ASK_THE_DEVICE_SNMP_TIP],
|
|
1538
|
+
[false, true, true, ASK_THE_DEVICE_SNMP_TIP],
|
|
1539
|
+
[true, false, false, ASK_THE_DEVICE_NETBIOS_TIP],
|
|
1540
|
+
[true, false, undefined, ASK_THE_DEVICE_NETBIOS_TIP],
|
|
1541
|
+
[true, false, true, undefined],
|
|
1542
|
+
[true, true, false, undefined],
|
|
1543
|
+
[true, true, true, undefined],
|
|
1544
|
+
])(
|
|
1545
|
+
"SNMP %p, NetBIOS %p, global probe %p: the ask-the-device tip is %p",
|
|
1546
|
+
(
|
|
1547
|
+
isSnmpEnabled: boolean,
|
|
1548
|
+
isNetbiosLookupEnabled: boolean,
|
|
1549
|
+
isGlobalProbe: boolean | undefined,
|
|
1550
|
+
tip: string | undefined,
|
|
1551
|
+
) => {
|
|
1552
|
+
const text: string = textOf({
|
|
1553
|
+
host: pingOnlyHost(),
|
|
1554
|
+
scan: {
|
|
1555
|
+
status: DiscoveryScanStatus.Completed,
|
|
1556
|
+
isSnmpEnabled: isSnmpEnabled,
|
|
1557
|
+
isNetbiosLookupEnabled: isNetbiosLookupEnabled,
|
|
1558
|
+
},
|
|
1559
|
+
isGlobalProbe: isGlobalProbe,
|
|
1560
|
+
});
|
|
1561
|
+
const sentences: Array<KnownSentence> = splitIntoKnownSentences(text)!;
|
|
1562
|
+
const askTheDevice: Array<string> = sentences
|
|
1563
|
+
.filter((entry: KnownSentence) => {
|
|
1564
|
+
return entry.group === ASK_THE_DEVICE_GROUP;
|
|
1565
|
+
})
|
|
1566
|
+
.map((entry: KnownSentence): string => {
|
|
1567
|
+
return entry.sentence;
|
|
1568
|
+
});
|
|
1569
|
+
|
|
1570
|
+
expect(askTheDevice).toEqual(tip ? [tip] : []);
|
|
1571
|
+
},
|
|
1572
|
+
);
|
|
1573
|
+
|
|
1574
|
+
test("a global probe with NetBIOS off is never told to turn NetBIOS on", () => {
|
|
1575
|
+
/*
|
|
1576
|
+
* It would not help: a global probe never sends NetBIOS queries. The
|
|
1577
|
+
* only effect would be a "not asked" line on the next scan.
|
|
1578
|
+
*/
|
|
1579
|
+
for (const [code] of REVERSE_DNS_COPY) {
|
|
1580
|
+
for (const scan of [ICMP_ONLY_SCAN, SNMP_SCAN]) {
|
|
1581
|
+
const text: string = textOf({
|
|
1582
|
+
host: pingOnlyHost({ dnsHostnameStatus: code }),
|
|
1583
|
+
scan: scan,
|
|
1584
|
+
isGlobalProbe: true,
|
|
1585
|
+
});
|
|
1586
|
+
|
|
1587
|
+
expect(text).not.toContain("NetBIOS lookup");
|
|
1588
|
+
expect(text).toContain(NETBIOS_OFF_SENTENCE);
|
|
1589
|
+
}
|
|
1590
|
+
}
|
|
1591
|
+
});
|
|
1592
|
+
|
|
1593
|
+
test("with no scan, no source is suggested", () => {
|
|
1594
|
+
for (const snmpReachable of [undefined, true, false]) {
|
|
1595
|
+
const text: string = textOf({
|
|
1596
|
+
host: bareHost({ snmpReachable: snmpReachable }),
|
|
1597
|
+
});
|
|
1598
|
+
|
|
1599
|
+
expect(text).not.toContain(ASK_THE_DEVICE_SNMP_TIP);
|
|
1600
|
+
expect(text).not.toContain("NetBIOS lookup");
|
|
1601
|
+
expect(text).not.toContain(ASK_THE_DEVICE_TIP);
|
|
1602
|
+
}
|
|
1603
|
+
});
|
|
1604
|
+
|
|
1605
|
+
test("the reverse-DNS tip comes before the ask-the-device tip, and both can appear", () => {
|
|
1606
|
+
expect(
|
|
1607
|
+
textOf({
|
|
1608
|
+
host: pingOnlyHost({
|
|
1609
|
+
dnsHostnameStatus: DiscoveredHostReverseDnsStatus.NoRecord,
|
|
1610
|
+
}),
|
|
1611
|
+
scan: ICMP_ONLY_SCAN,
|
|
1612
|
+
}),
|
|
1613
|
+
).toBe(
|
|
1614
|
+
[
|
|
1615
|
+
SNMP_NOT_CHECKED_SENTENCE,
|
|
1616
|
+
reverseDnsSentence(DiscoveredHostReverseDnsStatus.NoRecord),
|
|
1617
|
+
NETBIOS_OFF_SENTENCE,
|
|
1618
|
+
NO_RECORD_TIP,
|
|
1619
|
+
ASK_THE_DEVICE_TIP,
|
|
1620
|
+
].join(" "),
|
|
1621
|
+
);
|
|
1622
|
+
});
|
|
1623
|
+
});
|
|
1624
|
+
|
|
1625
|
+
describe("explainUnnamedDiscoveredHost — NetBIOS advice follows the address", () => {
|
|
1626
|
+
/*
|
|
1627
|
+
* The probe only ever sends a NetBIOS query to private and CGNAT IPv4
|
|
1628
|
+
* (isNetbiosQueryAddressAllowed, Probe/Utils/Discovery
|
|
1629
|
+
* /NetbiosNameResolver.ts); every other address is "not asked" whoever
|
|
1630
|
+
* asks (#3916). So "NetBIOS lookup asks Windows hosts for their own name"
|
|
1631
|
+
* on a public or IPv6 address sent the operator to turn NetBIOS on and
|
|
1632
|
+
* rescan, for nothing but a "not asked" line.
|
|
1633
|
+
*
|
|
1634
|
+
* Each address is annotated BY HAND with whether the probe would query it,
|
|
1635
|
+
* rather than asking the helper, so a wrong range cannot agree with itself
|
|
1636
|
+
* here. Every address before this block is 10.16.42.51, which is why none
|
|
1637
|
+
* of them caught it.
|
|
1638
|
+
*/
|
|
1639
|
+
const NETBIOS_ADDRESS_CASES: Array<[string, boolean]> = [
|
|
1640
|
+
["10.16.42.51", true],
|
|
1641
|
+
["10.0.0.0", true],
|
|
1642
|
+
["10.255.255.255", true],
|
|
1643
|
+
["172.16.0.1", true],
|
|
1644
|
+
["172.31.255.254", true],
|
|
1645
|
+
["192.168.1.20", true],
|
|
1646
|
+
["100.64.0.1", true],
|
|
1647
|
+
["100.127.255.254", true],
|
|
1648
|
+
["9.255.255.255", false],
|
|
1649
|
+
["11.0.0.1", false],
|
|
1650
|
+
["172.15.255.254", false],
|
|
1651
|
+
["172.32.0.1", false],
|
|
1652
|
+
["192.169.0.1", false],
|
|
1653
|
+
["100.63.255.254", false],
|
|
1654
|
+
["100.128.0.1", false],
|
|
1655
|
+
["203.0.113.7", false],
|
|
1656
|
+
["8.8.4.4", false],
|
|
1657
|
+
["127.0.0.1", false],
|
|
1658
|
+
["169.254.169.254", false],
|
|
1659
|
+
["2001:db8::5", false],
|
|
1660
|
+
["fd00::5", false],
|
|
1661
|
+
["::ffff:10.16.42.51", false],
|
|
1662
|
+
["010.16.42.51", false],
|
|
1663
|
+
[" 10.16.42.51", false],
|
|
1664
|
+
];
|
|
1665
|
+
|
|
1666
|
+
const INELIGIBLE_ADDRESS_SENTENCE: string = netbiosSentence(
|
|
1667
|
+
DiscoveredHostNetbiosStatus.SkippedIneligibleAddress,
|
|
1668
|
+
);
|
|
1669
|
+
|
|
1670
|
+
test("a public host on an SNMP scan with NetBIOS off is not told to turn NetBIOS on", () => {
|
|
1671
|
+
// The review's reproduction, word for word.
|
|
1672
|
+
expect(
|
|
1673
|
+
textOf({
|
|
1674
|
+
host: pingOnlyHost({
|
|
1675
|
+
ipAddress: "203.0.113.7",
|
|
1676
|
+
dnsHostnameStatus: DiscoveredHostReverseDnsStatus.NoRecord,
|
|
1677
|
+
}),
|
|
1678
|
+
scan: SNMP_SCAN,
|
|
1679
|
+
isGlobalProbe: false,
|
|
1680
|
+
}),
|
|
1681
|
+
).toBe(
|
|
1682
|
+
[
|
|
1683
|
+
SNMP_NO_ANSWER_SENTENCE,
|
|
1684
|
+
reverseDnsSentence(DiscoveredHostReverseDnsStatus.NoRecord),
|
|
1685
|
+
NETBIOS_OFF_SENTENCE,
|
|
1686
|
+
NO_RECORD_TIP,
|
|
1687
|
+
].join(" "),
|
|
1688
|
+
);
|
|
1689
|
+
});
|
|
1690
|
+
|
|
1691
|
+
test("a public host on a ping-only scan is told about SNMP only", () => {
|
|
1692
|
+
expect(
|
|
1693
|
+
textOf({
|
|
1694
|
+
host: pingOnlyHost({ ipAddress: "8.8.4.4" }),
|
|
1695
|
+
scan: ICMP_ONLY_SCAN,
|
|
1696
|
+
isGlobalProbe: false,
|
|
1697
|
+
}),
|
|
1698
|
+
).toBe(
|
|
1699
|
+
[
|
|
1700
|
+
SNMP_NOT_CHECKED_SENTENCE,
|
|
1701
|
+
REVERSE_DNS_NOT_RECORDED_SENTENCE,
|
|
1702
|
+
NETBIOS_OFF_SENTENCE,
|
|
1703
|
+
ASK_THE_DEVICE_SNMP_TIP,
|
|
1704
|
+
].join(" "),
|
|
1705
|
+
);
|
|
1706
|
+
});
|
|
1707
|
+
|
|
1708
|
+
test.each(NETBIOS_ADDRESS_CASES)(
|
|
1709
|
+
"%s on a scan with NetBIOS off: NetBIOS is suggested only if the probe would ask it (%p)",
|
|
1710
|
+
(ipAddress: string, isQueryable: boolean) => {
|
|
1711
|
+
expect(
|
|
1712
|
+
textOf({
|
|
1713
|
+
host: pingOnlyHost({ ipAddress: ipAddress }),
|
|
1714
|
+
scan: SNMP_SCAN,
|
|
1715
|
+
isGlobalProbe: false,
|
|
1716
|
+
}).endsWith(` ${ASK_THE_DEVICE_NETBIOS_TIP}`),
|
|
1717
|
+
).toBe(isQueryable);
|
|
1718
|
+
expect(
|
|
1719
|
+
textOf({
|
|
1720
|
+
host: pingOnlyHost({ ipAddress: ipAddress }),
|
|
1721
|
+
scan: ICMP_ONLY_SCAN,
|
|
1722
|
+
isGlobalProbe: false,
|
|
1723
|
+
}).endsWith(
|
|
1724
|
+
` ${isQueryable ? ASK_THE_DEVICE_TIP : ASK_THE_DEVICE_SNMP_TIP}`,
|
|
1725
|
+
),
|
|
1726
|
+
).toBe(true);
|
|
1727
|
+
},
|
|
1728
|
+
);
|
|
1729
|
+
|
|
1730
|
+
test("NetBIOS on with no code, on an address the probe never asks, says it was not asked", () => {
|
|
1731
|
+
/*
|
|
1732
|
+
* The probe's private-only rule shipped with the NetBIOS setting itself,
|
|
1733
|
+
* so for an older probe's row, too, "not asked" is the truth.
|
|
1734
|
+
*/
|
|
1735
|
+
expect(
|
|
1736
|
+
textOf({
|
|
1737
|
+
host: pingOnlyHost({ ipAddress: "203.0.113.7" }),
|
|
1738
|
+
scan: SNMP_AND_NETBIOS_SCAN,
|
|
1739
|
+
isGlobalProbe: false,
|
|
1740
|
+
}),
|
|
1741
|
+
).toBe(
|
|
1742
|
+
[
|
|
1743
|
+
SNMP_NO_ANSWER_SENTENCE,
|
|
1744
|
+
REVERSE_DNS_NOT_RECORDED_SENTENCE,
|
|
1745
|
+
INELIGIBLE_ADDRESS_SENTENCE,
|
|
1746
|
+
].join(" "),
|
|
1747
|
+
);
|
|
1748
|
+
});
|
|
1749
|
+
|
|
1750
|
+
test("on a global probe, the global-probe reason wins over the address", () => {
|
|
1751
|
+
/*
|
|
1752
|
+
* A global probe refuses NetBIOS for the whole scan before any address
|
|
1753
|
+
* is looked at, so that is the reason to give.
|
|
1754
|
+
*/
|
|
1755
|
+
expect(
|
|
1756
|
+
textOf({
|
|
1757
|
+
host: pingOnlyHost({ ipAddress: "203.0.113.7" }),
|
|
1758
|
+
scan: SNMP_AND_NETBIOS_SCAN,
|
|
1759
|
+
isGlobalProbe: true,
|
|
1760
|
+
}),
|
|
1761
|
+
).toContain(GLOBAL_PROBE_NETBIOS_SENTENCE);
|
|
1762
|
+
});
|
|
1763
|
+
|
|
1764
|
+
test("a code on the row always wins over what the address suggests", () => {
|
|
1765
|
+
// The code is what the probe saw. The address is only an inference.
|
|
1766
|
+
for (const [code, sentence] of NETBIOS_COPY) {
|
|
1767
|
+
const text: string = textOf({
|
|
1768
|
+
host: pingOnlyHost({
|
|
1769
|
+
ipAddress: "203.0.113.7",
|
|
1770
|
+
netbiosNameStatus: code,
|
|
1771
|
+
}),
|
|
1772
|
+
scan: SNMP_AND_NETBIOS_SCAN,
|
|
1773
|
+
isGlobalProbe: false,
|
|
1774
|
+
});
|
|
1775
|
+
|
|
1776
|
+
expect(text).toContain(sentence);
|
|
1777
|
+
expect(text).not.toContain(NETBIOS_NOT_RECORDED_SENTENCE);
|
|
1778
|
+
}
|
|
1779
|
+
});
|
|
1780
|
+
|
|
1781
|
+
test("every address, code, scan and probe: the NetBIOS line and the tip follow from first principles", () => {
|
|
1782
|
+
const wrong: Set<string> = new Set<string>();
|
|
1783
|
+
let casesChecked: number = 0;
|
|
1784
|
+
let netbiosSuggestedCount: number = 0;
|
|
1785
|
+
|
|
1786
|
+
for (const [ipAddress, isQueryable] of NETBIOS_ADDRESS_CASES) {
|
|
1787
|
+
for (const code of NETBIOS_CODE_OPTIONS) {
|
|
1788
|
+
const scans: Array<DiscoveredHostNamingScan | undefined> = [undefined];
|
|
1789
|
+
|
|
1790
|
+
for (const isSnmpEnabled of FLAG_OPTIONS) {
|
|
1791
|
+
for (const isNetbiosLookupEnabled of FLAG_OPTIONS) {
|
|
1792
|
+
scans.push({
|
|
1793
|
+
status: DiscoveryScanStatus.Completed,
|
|
1794
|
+
isSnmpEnabled: isSnmpEnabled,
|
|
1795
|
+
isNetbiosLookupEnabled: isNetbiosLookupEnabled,
|
|
1796
|
+
});
|
|
1797
|
+
}
|
|
1798
|
+
}
|
|
1799
|
+
|
|
1800
|
+
for (const scan of scans) {
|
|
1801
|
+
for (const isGlobalProbe of GLOBAL_PROBE_OPTIONS) {
|
|
1802
|
+
casesChecked++;
|
|
1803
|
+
|
|
1804
|
+
const text: string = textOf({
|
|
1805
|
+
host: pingOnlyHost({
|
|
1806
|
+
ipAddress: ipAddress,
|
|
1807
|
+
netbiosNameStatus: code,
|
|
1808
|
+
}),
|
|
1809
|
+
scan: scan,
|
|
1810
|
+
isGlobalProbe: isGlobalProbe,
|
|
1811
|
+
});
|
|
1812
|
+
const sentences: Array<KnownSentence> | undefined =
|
|
1813
|
+
splitIntoKnownSentences(text);
|
|
1814
|
+
|
|
1815
|
+
if (!sentences) {
|
|
1816
|
+
wrong.add(`not all fixed copy: ${text}`);
|
|
1817
|
+
continue;
|
|
1818
|
+
}
|
|
1819
|
+
|
|
1820
|
+
const netbiosLine: string | undefined = sentences.find(
|
|
1821
|
+
(entry: KnownSentence): boolean => {
|
|
1822
|
+
return entry.group === NETBIOS_GROUP;
|
|
1823
|
+
},
|
|
1824
|
+
)?.sentence;
|
|
1825
|
+
const tip: string | undefined = sentences.find(
|
|
1826
|
+
(entry: KnownSentence): boolean => {
|
|
1827
|
+
return entry.group === ASK_THE_DEVICE_GROUP;
|
|
1828
|
+
},
|
|
1829
|
+
)?.sentence;
|
|
1830
|
+
|
|
1831
|
+
let expectedNetbiosLine: string | undefined = undefined;
|
|
1832
|
+
|
|
1833
|
+
if (code) {
|
|
1834
|
+
expectedNetbiosLine = netbiosSentence(code);
|
|
1835
|
+
} else if (!scan) {
|
|
1836
|
+
expectedNetbiosLine = undefined;
|
|
1837
|
+
} else if (scan.isNetbiosLookupEnabled !== true) {
|
|
1838
|
+
expectedNetbiosLine = NETBIOS_OFF_SENTENCE;
|
|
1839
|
+
} else if (isGlobalProbe === true) {
|
|
1840
|
+
expectedNetbiosLine = GLOBAL_PROBE_NETBIOS_SENTENCE;
|
|
1841
|
+
} else if (!isQueryable) {
|
|
1842
|
+
expectedNetbiosLine = INELIGIBLE_ADDRESS_SENTENCE;
|
|
1843
|
+
} else {
|
|
1844
|
+
expectedNetbiosLine = NETBIOS_NOT_RECORDED_SENTENCE;
|
|
1845
|
+
}
|
|
1846
|
+
|
|
1847
|
+
const isSnmpOff: boolean =
|
|
1848
|
+
Boolean(scan) && scan!.isSnmpEnabled === false;
|
|
1849
|
+
const isNetbiosSuggested: boolean =
|
|
1850
|
+
Boolean(scan) &&
|
|
1851
|
+
scan!.isNetbiosLookupEnabled !== true &&
|
|
1852
|
+
!code &&
|
|
1853
|
+
isGlobalProbe !== true &&
|
|
1854
|
+
isQueryable;
|
|
1855
|
+
|
|
1856
|
+
let expectedTip: string | undefined = undefined;
|
|
1857
|
+
|
|
1858
|
+
if (isSnmpOff && isNetbiosSuggested) {
|
|
1859
|
+
expectedTip = ASK_THE_DEVICE_TIP;
|
|
1860
|
+
} else if (isSnmpOff) {
|
|
1861
|
+
expectedTip = ASK_THE_DEVICE_SNMP_TIP;
|
|
1862
|
+
} else if (isNetbiosSuggested) {
|
|
1863
|
+
expectedTip = ASK_THE_DEVICE_NETBIOS_TIP;
|
|
1864
|
+
}
|
|
1865
|
+
|
|
1866
|
+
if (isNetbiosSuggested) {
|
|
1867
|
+
netbiosSuggestedCount++;
|
|
1868
|
+
}
|
|
1869
|
+
|
|
1870
|
+
if (netbiosLine !== expectedNetbiosLine || tip !== expectedTip) {
|
|
1871
|
+
wrong.add(`${ipAddress}: ${text}`);
|
|
1872
|
+
}
|
|
1873
|
+
}
|
|
1874
|
+
}
|
|
1875
|
+
}
|
|
1876
|
+
}
|
|
1877
|
+
|
|
1878
|
+
expect(Array.from(wrong).slice(0, 5)).toEqual([]);
|
|
1879
|
+
// 24 addresses x 8 codes x (1 + 5 x 5) scans x 3 probes.
|
|
1880
|
+
expect(casesChecked).toBe(24 * 8 * (1 + 5 * 5) * 3);
|
|
1881
|
+
// A guard on the guard: the suggesting branch was reached.
|
|
1882
|
+
expect(netbiosSuggestedCount).toBeGreaterThan(0);
|
|
1883
|
+
});
|
|
1884
|
+
});
|
|
1885
|
+
|
|
1886
|
+
describe("explainUnnamedDiscoveredHost — codes it does not recognise", () => {
|
|
1887
|
+
/*
|
|
1888
|
+
* A newer probe's code, a code in the wrong field, or junk. Each reads as
|
|
1889
|
+
* "no code", so the row gets the copy an older probe's row gets, rather
|
|
1890
|
+
* than a guess at what an unknown code meant.
|
|
1891
|
+
*/
|
|
1892
|
+
const JUNK_CODES: Array<[string, unknown]> = [
|
|
1893
|
+
["an upper-case code", "NO-RECORD"],
|
|
1894
|
+
["a padded code", " timeout "],
|
|
1895
|
+
["an underscored code", "no_record"],
|
|
1896
|
+
["a code from a newer probe", "dnssec-bogus"],
|
|
1897
|
+
["a prototype key", "constructor"],
|
|
1898
|
+
["another prototype key", "__proto__"],
|
|
1899
|
+
["toString", "toString"],
|
|
1900
|
+
["markup", "<img src=x onerror=1>"],
|
|
1901
|
+
["a number", 7],
|
|
1902
|
+
["an object", { code: "timeout" }],
|
|
1903
|
+
["an array", ["timeout"]],
|
|
1904
|
+
["true", true],
|
|
1905
|
+
["null", null],
|
|
1906
|
+
];
|
|
1907
|
+
|
|
1908
|
+
test.each(JUNK_CODES)(
|
|
1909
|
+
"%s in the reverse-DNS field reads as no code",
|
|
1910
|
+
(_label: string, value: unknown) => {
|
|
1911
|
+
for (const scan of [REPORTERS_SCAN, SNMP_SCAN, undefined]) {
|
|
1912
|
+
expect(
|
|
1913
|
+
explainUnnamedDiscoveredHost({
|
|
1914
|
+
host: pingOnlyHost({
|
|
1915
|
+
dnsHostnameStatus: value as DiscoveredHostReverseDnsStatus,
|
|
1916
|
+
}),
|
|
1917
|
+
scan: scan,
|
|
1918
|
+
}),
|
|
1919
|
+
).toStrictEqual(
|
|
1920
|
+
explainUnnamedDiscoveredHost({ host: pingOnlyHost(), scan: scan }),
|
|
1921
|
+
);
|
|
1922
|
+
}
|
|
1923
|
+
},
|
|
1924
|
+
);
|
|
1925
|
+
|
|
1926
|
+
test.each(JUNK_CODES)(
|
|
1927
|
+
"%s in the NetBIOS field reads as no code",
|
|
1928
|
+
(_label: string, value: unknown) => {
|
|
1929
|
+
for (const scan of [REPORTERS_SCAN, SNMP_SCAN, undefined]) {
|
|
1930
|
+
expect(
|
|
1931
|
+
explainUnnamedDiscoveredHost({
|
|
1932
|
+
host: pingOnlyHost({
|
|
1933
|
+
netbiosNameStatus: value as DiscoveredHostNetbiosStatus,
|
|
1934
|
+
}),
|
|
1935
|
+
scan: scan,
|
|
1936
|
+
}),
|
|
1937
|
+
).toStrictEqual(
|
|
1938
|
+
explainUnnamedDiscoveredHost({ host: pingOnlyHost(), scan: scan }),
|
|
1939
|
+
);
|
|
1940
|
+
}
|
|
1941
|
+
},
|
|
1942
|
+
);
|
|
1943
|
+
|
|
1944
|
+
test.each(Object.values(DiscoveredHostNetbiosStatus))(
|
|
1945
|
+
"the NetBIOS code %p in the reverse-DNS field reads as no code",
|
|
1946
|
+
(code: string) => {
|
|
1947
|
+
expect(
|
|
1948
|
+
explainUnnamedDiscoveredHost({
|
|
1949
|
+
host: pingOnlyHost({
|
|
1950
|
+
dnsHostnameStatus: code as DiscoveredHostReverseDnsStatus,
|
|
1951
|
+
}),
|
|
1952
|
+
scan: REPORTERS_SCAN,
|
|
1953
|
+
}),
|
|
1954
|
+
).toStrictEqual(
|
|
1955
|
+
explainUnnamedDiscoveredHost({
|
|
1956
|
+
host: pingOnlyHost(),
|
|
1957
|
+
scan: REPORTERS_SCAN,
|
|
1958
|
+
}),
|
|
1959
|
+
);
|
|
1960
|
+
},
|
|
1961
|
+
);
|
|
1962
|
+
|
|
1963
|
+
test.each(Object.values(DiscoveredHostReverseDnsStatus))(
|
|
1964
|
+
"the reverse-DNS code %p in the NetBIOS field reads as no code",
|
|
1965
|
+
(code: string) => {
|
|
1966
|
+
expect(
|
|
1967
|
+
explainUnnamedDiscoveredHost({
|
|
1968
|
+
host: pingOnlyHost({
|
|
1969
|
+
netbiosNameStatus: code as DiscoveredHostNetbiosStatus,
|
|
1970
|
+
}),
|
|
1971
|
+
scan: REPORTERS_SCAN,
|
|
1972
|
+
}),
|
|
1973
|
+
).toStrictEqual(
|
|
1974
|
+
explainUnnamedDiscoveredHost({
|
|
1975
|
+
host: pingOnlyHost(),
|
|
1976
|
+
scan: REPORTERS_SCAN,
|
|
1977
|
+
}),
|
|
1978
|
+
);
|
|
1979
|
+
},
|
|
1980
|
+
);
|
|
1981
|
+
|
|
1982
|
+
test("junk codes do not stop a sweeping scan from saying names come later", () => {
|
|
1983
|
+
/*
|
|
1984
|
+
* Only a real code means the naming passes ran for the row. Junk is not
|
|
1985
|
+
* evidence of that.
|
|
1986
|
+
*/
|
|
1987
|
+
expect(
|
|
1988
|
+
explainUnnamedDiscoveredHost({
|
|
1989
|
+
host: pingOnlyHost({
|
|
1990
|
+
dnsHostnameStatus: "NO-RECORD" as DiscoveredHostReverseDnsStatus,
|
|
1991
|
+
netbiosNameStatus: 7 as unknown as DiscoveredHostNetbiosStatus,
|
|
1992
|
+
}),
|
|
1993
|
+
scan: { ...REPORTERS_SCAN, status: DiscoveryScanStatus.InProgress },
|
|
1994
|
+
}),
|
|
1995
|
+
).toStrictEqual({
|
|
1996
|
+
label: NOT_YET_NAMED_HOST_LABEL,
|
|
1997
|
+
text: IN_PROGRESS_EXPLANATION,
|
|
1998
|
+
});
|
|
1999
|
+
});
|
|
2000
|
+
});
|
|
2001
|
+
|
|
2002
|
+
describe("explainUnnamedDiscoveredHost — nothing the scanned network chose reaches the text", () => {
|
|
2003
|
+
/*
|
|
2004
|
+
* The text goes into a tooltip. Every field below was chosen by the
|
|
2005
|
+
* scanned host or by whoever runs DNS for its subnet, and a discovery scan
|
|
2006
|
+
* is routinely pointed at a subnet nobody in the project runs. None of
|
|
2007
|
+
* them, not even the address, may appear in it.
|
|
2008
|
+
*/
|
|
2009
|
+
const HOSTILE_ADDRESS: string = "10.99.88.77";
|
|
2010
|
+
|
|
2011
|
+
function hostileHost(
|
|
2012
|
+
overrides: Partial<DiscoveredNetworkDevice> = {},
|
|
2013
|
+
): DiscoveredNetworkDevice {
|
|
2014
|
+
return {
|
|
2015
|
+
ipAddress: HOSTILE_ADDRESS,
|
|
2016
|
+
sysName: " ",
|
|
2017
|
+
sysDescr: "PWNED-DESCR <script>alert(1)</script>",
|
|
2018
|
+
sysObjectId: "1.3.6.1.4.1.666",
|
|
2019
|
+
sysLocation: "PWNED-LOCATION",
|
|
2020
|
+
sysContact: "PWNED-CONTACT",
|
|
2021
|
+
// Normalises away, so the host is unnamed and the hint is shown.
|
|
2022
|
+
dnsHostname: "<img src=x onerror=1>.example",
|
|
2023
|
+
netbiosName: "EVIL NAME<b>",
|
|
2024
|
+
snmpConfigId: "PWNED-CONFIG",
|
|
2025
|
+
...overrides,
|
|
2026
|
+
// A key the type does not know, as a modified probe could send.
|
|
2027
|
+
...({
|
|
2028
|
+
note: "PWNED-EXTRA",
|
|
2029
|
+
} as unknown as Partial<DiscoveredNetworkDevice>),
|
|
2030
|
+
};
|
|
2031
|
+
}
|
|
2032
|
+
|
|
2033
|
+
const FORBIDDEN: Array<string> = [
|
|
2034
|
+
HOSTILE_ADDRESS,
|
|
2035
|
+
"99.88",
|
|
2036
|
+
"PWNED",
|
|
2037
|
+
"<",
|
|
2038
|
+
">",
|
|
2039
|
+
"&",
|
|
2040
|
+
"img",
|
|
2041
|
+
"onerror",
|
|
2042
|
+
"script",
|
|
2043
|
+
"EVIL",
|
|
2044
|
+
"evil",
|
|
2045
|
+
"example",
|
|
2046
|
+
"666",
|
|
2047
|
+
// Interpolation gone wrong.
|
|
2048
|
+
"undefined",
|
|
2049
|
+
"null",
|
|
2050
|
+
"[object",
|
|
2051
|
+
"NaN",
|
|
2052
|
+
"function",
|
|
2053
|
+
];
|
|
2054
|
+
|
|
2055
|
+
// What in the text should not be there, or an empty list.
|
|
2056
|
+
function hostChosenTextIn(text: string): Array<string> {
|
|
2057
|
+
const leaks: Array<string> = FORBIDDEN.filter((forbidden: string) => {
|
|
2058
|
+
return text.includes(forbidden);
|
|
2059
|
+
});
|
|
2060
|
+
|
|
2061
|
+
/*
|
|
2062
|
+
* No digits at all, other than the port number in "UDP 137". So no
|
|
2063
|
+
* address, whole or in part, can have been interpolated.
|
|
2064
|
+
*/
|
|
2065
|
+
const anyDigit: RegExp = /[0-9]/;
|
|
2066
|
+
|
|
2067
|
+
if (anyDigit.test(text.replace("UDP 137", ""))) {
|
|
2068
|
+
leaks.push("a digit");
|
|
2069
|
+
}
|
|
2070
|
+
|
|
2071
|
+
return leaks;
|
|
2072
|
+
}
|
|
2073
|
+
|
|
2074
|
+
test("the hostile host is unnamed, so the hint is shown for it", () => {
|
|
2075
|
+
expect(isDiscoveredHostNamed(hostileHost())).toBe(false);
|
|
2076
|
+
expect(
|
|
2077
|
+
explainUnnamedDiscoveredHost({
|
|
2078
|
+
host: hostileHost(),
|
|
2079
|
+
scan: REPORTERS_SCAN,
|
|
2080
|
+
}),
|
|
2081
|
+
).toBeDefined();
|
|
2082
|
+
});
|
|
2083
|
+
|
|
2084
|
+
test("for every code, scan and probe, the text holds no host-chosen value", () => {
|
|
2085
|
+
/*
|
|
2086
|
+
* Violations are collected rather than asserted one by one: there are
|
|
2087
|
+
* over a hundred thousand cases, and a failure should print the texts
|
|
2088
|
+
* that leaked, not stop at the first.
|
|
2089
|
+
*/
|
|
2090
|
+
const leaked: Array<{ text: string; leaks: Array<string> }> = [];
|
|
2091
|
+
let explained: number = 0;
|
|
2092
|
+
|
|
2093
|
+
for (const namingCase of ALL_NAMING_CASES) {
|
|
2094
|
+
const explanation: DiscoveredHostNamingExplanation | undefined =
|
|
2095
|
+
explainUnnamedDiscoveredHost({
|
|
2096
|
+
host: hostileHost({
|
|
2097
|
+
snmpReachable: namingCase.host.snmpReachable,
|
|
2098
|
+
dnsHostnameStatus: namingCase.host.dnsHostnameStatus,
|
|
2099
|
+
netbiosNameStatus: namingCase.host.netbiosNameStatus,
|
|
2100
|
+
}),
|
|
2101
|
+
scan: namingCase.scan,
|
|
2102
|
+
isGlobalProbe: namingCase.isGlobalProbe,
|
|
2103
|
+
});
|
|
2104
|
+
|
|
2105
|
+
if (!explanation) {
|
|
2106
|
+
continue;
|
|
2107
|
+
}
|
|
2108
|
+
|
|
2109
|
+
explained++;
|
|
2110
|
+
|
|
2111
|
+
const text: string = `${explanation.label} ${explanation.text}`;
|
|
2112
|
+
const leaks: Array<string> = hostChosenTextIn(text);
|
|
2113
|
+
|
|
2114
|
+
if (leaks.length > 0) {
|
|
2115
|
+
leaked.push({ text: text, leaks: leaks });
|
|
2116
|
+
}
|
|
2117
|
+
}
|
|
2118
|
+
|
|
2119
|
+
expect(explained).toBe(ALL_NAMING_CASES.length);
|
|
2120
|
+
expect(leaked.slice(0, 5)).toEqual([]);
|
|
2121
|
+
});
|
|
2122
|
+
|
|
2123
|
+
test("an address that is itself markup is not echoed", () => {
|
|
2124
|
+
const explanation: DiscoveredHostNamingExplanation | undefined =
|
|
2125
|
+
explainUnnamedDiscoveredHost({
|
|
2126
|
+
host: hostileHost({ ipAddress: "<b>10.99.88.77</b>" }),
|
|
2127
|
+
scan: REPORTERS_SCAN,
|
|
2128
|
+
});
|
|
2129
|
+
|
|
2130
|
+
expect(explanation).toBeDefined();
|
|
2131
|
+
expect(hostChosenTextIn(explanation!.text)).toEqual([]);
|
|
2132
|
+
});
|
|
2133
|
+
});
|
|
2134
|
+
|
|
2135
|
+
describe("explainUnnamedDiscoveredHost — every combination", () => {
|
|
2136
|
+
/*
|
|
2137
|
+
* Each test below walks every case and COLLECTS what is wrong, then
|
|
2138
|
+
* asserts the list is empty. With over a hundred thousand cases that is
|
|
2139
|
+
* both faster than an expect per case and a better failure: it prints the
|
|
2140
|
+
* offending texts rather than stopping at the first.
|
|
2141
|
+
*/
|
|
2142
|
+
test("the enumeration covers a meaningful space", () => {
|
|
2143
|
+
// 10 x 8 x 3 x 3 x (1 + 6 x 5 x 5) inputs.
|
|
2144
|
+
expect(ALL_NAMING_CASES.length).toBe(10 * 8 * 3 * 3 * (1 + 6 * 5 * 5));
|
|
2145
|
+
});
|
|
2146
|
+
|
|
2147
|
+
test("every unnamed row gets an explanation, and only one of the two labels", () => {
|
|
2148
|
+
const wrong: Array<NamingCase> = ALL_NAMING_CASES.filter(
|
|
2149
|
+
(namingCase: NamingCase): boolean => {
|
|
2150
|
+
const explanation: DiscoveredHostNamingExplanation | undefined =
|
|
2151
|
+
explainUnnamedDiscoveredHost(namingCase);
|
|
2152
|
+
|
|
2153
|
+
if (!explanation) {
|
|
2154
|
+
return true;
|
|
2155
|
+
}
|
|
2156
|
+
|
|
2157
|
+
const isNotYetNamed: boolean =
|
|
2158
|
+
explanation.label === NOT_YET_NAMED_HOST_LABEL;
|
|
2159
|
+
|
|
2160
|
+
return (
|
|
2161
|
+
(!isNotYetNamed && explanation.label !== UNNAMED_HOST_LABEL) ||
|
|
2162
|
+
isNotYetNamed !== (explanation.text === IN_PROGRESS_EXPLANATION)
|
|
2163
|
+
);
|
|
2164
|
+
},
|
|
2165
|
+
);
|
|
2166
|
+
|
|
2167
|
+
expect(wrong.slice(0, 5)).toEqual([]);
|
|
2168
|
+
});
|
|
2169
|
+
|
|
2170
|
+
test(`no text is longer than ${MAX_EXPLANATION_LENGTH} characters`, () => {
|
|
2171
|
+
const tooLong: Set<string> = new Set<string>();
|
|
2172
|
+
let longest: number = 0;
|
|
2173
|
+
|
|
2174
|
+
for (const namingCase of ALL_NAMING_CASES) {
|
|
2175
|
+
const text: string = explainUnnamedDiscoveredHost(namingCase)!.text;
|
|
2176
|
+
|
|
2177
|
+
longest = Math.max(longest, text.length);
|
|
2178
|
+
|
|
2179
|
+
if (text.length > MAX_EXPLANATION_LENGTH) {
|
|
2180
|
+
tooLong.add(text);
|
|
2181
|
+
}
|
|
2182
|
+
}
|
|
2183
|
+
|
|
2184
|
+
expect(Array.from(tooLong).slice(0, 5)).toEqual([]);
|
|
2185
|
+
/*
|
|
2186
|
+
* And the enumeration really reached the long combinations: the longest
|
|
2187
|
+
* text today is the reporter's scan with a missing PTR record and no
|
|
2188
|
+
* NetBIOS reply, at 415 characters.
|
|
2189
|
+
*/
|
|
2190
|
+
expect(longest).toBeGreaterThan(350);
|
|
2191
|
+
});
|
|
2192
|
+
|
|
2193
|
+
test("every text is fixed copy, one sentence per source, in the order the sources win", () => {
|
|
2194
|
+
/*
|
|
2195
|
+
* Each text is read back as a sequence of known sentences. That proves
|
|
2196
|
+
* three things at once: nothing but fixed copy is in it, no source is
|
|
2197
|
+
* described twice, and the order is SNMP, reverse DNS, NetBIOS, then the
|
|
2198
|
+
* reverse-DNS tip, then the ask-the-device tip. The reverse-DNS line is
|
|
2199
|
+
* always there, because reverse DNS is asked on every scan.
|
|
2200
|
+
*/
|
|
2201
|
+
const wrong: Set<string> = new Set<string>();
|
|
2202
|
+
|
|
2203
|
+
for (const namingCase of ALL_NAMING_CASES) {
|
|
2204
|
+
const text: string = explainUnnamedDiscoveredHost(namingCase)!.text;
|
|
2205
|
+
|
|
2206
|
+
if (
|
|
2207
|
+
text === IN_PROGRESS_EXPLANATION ||
|
|
2208
|
+
text === FAILED_SCAN_EXPLANATION
|
|
2209
|
+
) {
|
|
2210
|
+
continue;
|
|
2211
|
+
}
|
|
2212
|
+
|
|
2213
|
+
const sentences: Array<KnownSentence> | undefined =
|
|
2214
|
+
splitIntoKnownSentences(text);
|
|
2215
|
+
|
|
2216
|
+
if (!sentences) {
|
|
2217
|
+
wrong.add(`not all fixed copy: ${text}`);
|
|
2218
|
+
continue;
|
|
2219
|
+
}
|
|
2220
|
+
|
|
2221
|
+
const groups: Array<number> = sentences.map(
|
|
2222
|
+
(entry: KnownSentence): number => {
|
|
2223
|
+
return entry.group;
|
|
2224
|
+
},
|
|
2225
|
+
);
|
|
2226
|
+
const isInOrder: boolean = groups.every(
|
|
2227
|
+
(group: number, index: number): boolean => {
|
|
2228
|
+
return index === 0 || group > groups[index - 1]!;
|
|
2229
|
+
},
|
|
2230
|
+
);
|
|
2231
|
+
|
|
2232
|
+
if (!isInOrder) {
|
|
2233
|
+
wrong.add(`out of order: ${text}`);
|
|
2234
|
+
}
|
|
2235
|
+
|
|
2236
|
+
if (!groups.includes(REVERSE_DNS_GROUP)) {
|
|
2237
|
+
wrong.add(`no reverse-DNS line: ${text}`);
|
|
2238
|
+
}
|
|
2239
|
+
|
|
2240
|
+
if (
|
|
2241
|
+
sentences
|
|
2242
|
+
.map((entry: KnownSentence): string => {
|
|
2243
|
+
return entry.sentence;
|
|
2244
|
+
})
|
|
2245
|
+
.join(" ") !== text
|
|
2246
|
+
) {
|
|
2247
|
+
wrong.add(`not single-space joined: ${text}`);
|
|
2248
|
+
}
|
|
2249
|
+
}
|
|
2250
|
+
|
|
2251
|
+
expect(Array.from(wrong).slice(0, 5)).toEqual([]);
|
|
2252
|
+
});
|
|
2253
|
+
|
|
2254
|
+
test("the reverse-DNS tip matches the reverse-DNS code, and nothing else decides it", () => {
|
|
2255
|
+
const wrong: Set<string> = new Set<string>();
|
|
2256
|
+
|
|
2257
|
+
for (const namingCase of ALL_NAMING_CASES) {
|
|
2258
|
+
const text: string = explainUnnamedDiscoveredHost(namingCase)!.text;
|
|
2259
|
+
const sentences: Array<KnownSentence> | undefined =
|
|
2260
|
+
splitIntoKnownSentences(text);
|
|
2261
|
+
|
|
2262
|
+
if (!sentences) {
|
|
2263
|
+
// The two scan-level texts, pinned elsewhere.
|
|
2264
|
+
continue;
|
|
2265
|
+
}
|
|
2266
|
+
|
|
2267
|
+
const tip: string | undefined = sentences.find(
|
|
2268
|
+
(entry: KnownSentence): boolean => {
|
|
2269
|
+
return entry.group === REVERSE_DNS_TIP_GROUP;
|
|
2270
|
+
},
|
|
2271
|
+
)?.sentence;
|
|
2272
|
+
const code: DiscoveredHostReverseDnsStatus | undefined =
|
|
2273
|
+
namingCase.host.dnsHostnameStatus;
|
|
2274
|
+
const expectedTip: string | undefined = code
|
|
2275
|
+
? REVERSE_DNS_COPY.find(
|
|
2276
|
+
(
|
|
2277
|
+
entry: [
|
|
2278
|
+
DiscoveredHostReverseDnsStatus,
|
|
2279
|
+
string,
|
|
2280
|
+
string | undefined,
|
|
2281
|
+
],
|
|
2282
|
+
): boolean => {
|
|
2283
|
+
return entry[0] === code;
|
|
2284
|
+
},
|
|
2285
|
+
)![2]
|
|
2286
|
+
: undefined;
|
|
2287
|
+
|
|
2288
|
+
if (tip !== expectedTip) {
|
|
2289
|
+
wrong.add(`${String(code)}: ${text}`);
|
|
2290
|
+
}
|
|
2291
|
+
}
|
|
2292
|
+
|
|
2293
|
+
expect(Array.from(wrong).slice(0, 5)).toEqual([]);
|
|
2294
|
+
});
|
|
2295
|
+
|
|
2296
|
+
test("the ask-the-device tip follows the scan's settings and the probe, and nothing else", () => {
|
|
2297
|
+
const wrong: Set<string> = new Set<string>();
|
|
2298
|
+
|
|
2299
|
+
for (const namingCase of ALL_NAMING_CASES) {
|
|
2300
|
+
const text: string = explainUnnamedDiscoveredHost(namingCase)!.text;
|
|
2301
|
+
const sentences: Array<KnownSentence> | undefined =
|
|
2302
|
+
splitIntoKnownSentences(text);
|
|
2303
|
+
|
|
2304
|
+
if (!sentences) {
|
|
2305
|
+
continue;
|
|
2306
|
+
}
|
|
2307
|
+
|
|
2308
|
+
const tip: string | undefined = sentences.find(
|
|
2309
|
+
(entry: KnownSentence): boolean => {
|
|
2310
|
+
return entry.group === ASK_THE_DEVICE_GROUP;
|
|
2311
|
+
},
|
|
2312
|
+
)?.sentence;
|
|
2313
|
+
|
|
2314
|
+
/*
|
|
2315
|
+
* Worked out from first principles rather than from the text: SNMP is
|
|
2316
|
+
* suggested when the scan turned it off; NetBIOS when the scan turned
|
|
2317
|
+
* it off, the row does not say it ran, and the probe is not a global
|
|
2318
|
+
* one that would refuse it anyway.
|
|
2319
|
+
*/
|
|
2320
|
+
const scan: DiscoveredHostNamingScan | undefined = namingCase.scan;
|
|
2321
|
+
const isSnmpOff: boolean = Boolean(scan) && scan!.isSnmpEnabled === false;
|
|
2322
|
+
const isNetbiosSuggested: boolean =
|
|
2323
|
+
Boolean(scan) &&
|
|
2324
|
+
scan!.isNetbiosLookupEnabled !== true &&
|
|
2325
|
+
!namingCase.host.netbiosNameStatus &&
|
|
2326
|
+
namingCase.isGlobalProbe !== true;
|
|
2327
|
+
|
|
2328
|
+
let expectedTip: string | undefined = undefined;
|
|
2329
|
+
|
|
2330
|
+
if (isSnmpOff && isNetbiosSuggested) {
|
|
2331
|
+
expectedTip = ASK_THE_DEVICE_TIP;
|
|
2332
|
+
} else if (isSnmpOff) {
|
|
2333
|
+
expectedTip = ASK_THE_DEVICE_SNMP_TIP;
|
|
2334
|
+
} else if (isNetbiosSuggested) {
|
|
2335
|
+
expectedTip = ASK_THE_DEVICE_NETBIOS_TIP;
|
|
2336
|
+
}
|
|
2337
|
+
|
|
2338
|
+
if (tip !== expectedTip) {
|
|
2339
|
+
wrong.add(text);
|
|
2340
|
+
}
|
|
2341
|
+
}
|
|
2342
|
+
|
|
2343
|
+
expect(Array.from(wrong).slice(0, 5)).toEqual([]);
|
|
2344
|
+
});
|
|
2345
|
+
|
|
2346
|
+
test("every text is plain sentences: capitalised, full-stopped, single-spaced", () => {
|
|
2347
|
+
const wrong: Set<string> = new Set<string>();
|
|
2348
|
+
const startsWithCapital: RegExp = /^[A-Z]/;
|
|
2349
|
+
const doubleSpace: RegExp = /\s{2,}/;
|
|
2350
|
+
const lineBreakOrTab: RegExp = /[\n\t]/;
|
|
2351
|
+
|
|
2352
|
+
for (const namingCase of ALL_NAMING_CASES) {
|
|
2353
|
+
const text: string = explainUnnamedDiscoveredHost(namingCase)!.text;
|
|
2354
|
+
|
|
2355
|
+
if (
|
|
2356
|
+
!startsWithCapital.test(text) ||
|
|
2357
|
+
!text.endsWith(".") ||
|
|
2358
|
+
doubleSpace.test(text) ||
|
|
2359
|
+
lineBreakOrTab.test(text) ||
|
|
2360
|
+
text !== text.trim()
|
|
2361
|
+
) {
|
|
2362
|
+
wrong.add(text);
|
|
2363
|
+
}
|
|
2364
|
+
}
|
|
2365
|
+
|
|
2366
|
+
expect(Array.from(wrong).slice(0, 5)).toEqual([]);
|
|
2367
|
+
});
|
|
2368
|
+
});
|
|
2369
|
+
|
|
2370
|
+
describe("explainUnnamedDiscoveredHost — purity", () => {
|
|
2371
|
+
test("never changes the host or the scan it is handed", () => {
|
|
2372
|
+
const host: DiscoveredNetworkDevice = Object.freeze(
|
|
2373
|
+
pingOnlyHost({
|
|
2374
|
+
dnsHostnameStatus: DiscoveredHostReverseDnsStatus.Timeout,
|
|
2375
|
+
netbiosNameStatus: DiscoveredHostNetbiosStatus.NoReply,
|
|
2376
|
+
}),
|
|
2377
|
+
);
|
|
2378
|
+
const scan: DiscoveredHostNamingScan = Object.freeze({ ...REPORTERS_SCAN });
|
|
2379
|
+
const hostBefore: string = JSON.stringify(host);
|
|
2380
|
+
const scanBefore: string = JSON.stringify(scan);
|
|
2381
|
+
|
|
2382
|
+
// Frozen, so a write would throw in strict mode rather than pass quietly.
|
|
2383
|
+
const first: DiscoveredHostNamingExplanation | undefined =
|
|
2384
|
+
explainUnnamedDiscoveredHost({ host: host, scan: scan });
|
|
2385
|
+
const second: DiscoveredHostNamingExplanation | undefined =
|
|
2386
|
+
explainUnnamedDiscoveredHost({ host: host, scan: scan });
|
|
2387
|
+
|
|
2388
|
+
expect(JSON.stringify(host)).toBe(hostBefore);
|
|
2389
|
+
expect(JSON.stringify(scan)).toBe(scanBefore);
|
|
2390
|
+
expect(second).toStrictEqual(first);
|
|
2391
|
+
});
|
|
2392
|
+
|
|
2393
|
+
test("does not read the row's registration or import state", () => {
|
|
2394
|
+
/*
|
|
2395
|
+
* Whether a host is already in the inventory has nothing to do with why
|
|
2396
|
+
* it has no name.
|
|
2397
|
+
*/
|
|
2398
|
+
expect(
|
|
2399
|
+
explainUnnamedDiscoveredHost({
|
|
2400
|
+
host: pingOnlyHost({ isAlreadyRegistered: true }),
|
|
2401
|
+
scan: REPORTERS_SCAN,
|
|
2402
|
+
}),
|
|
2403
|
+
).toStrictEqual(
|
|
2404
|
+
explainUnnamedDiscoveredHost({
|
|
2405
|
+
host: pingOnlyHost(),
|
|
2406
|
+
scan: REPORTERS_SCAN,
|
|
2407
|
+
}),
|
|
2408
|
+
);
|
|
2409
|
+
});
|
|
2410
|
+
});
|
|
2411
|
+
|
|
2412
|
+
describe("explainUnnamedDiscoveredHost — the reporter's twelve kitchen displays", () => {
|
|
2413
|
+
/*
|
|
2414
|
+
* Issue #3916 as filed, with the codes a current probe would stamp. The
|
|
2415
|
+
* scan is ping only. The issue says neither whether NetBIOS was on nor
|
|
2416
|
+
* which probe ran it, so this takes the likeliest case: NetBIOS on (the
|
|
2417
|
+
* wizard's default) and one of the bundled self-hosted probes, which
|
|
2418
|
+
* register as global. Four displays have PTR names. The other eight used
|
|
2419
|
+
* to be eight identical bare addresses.
|
|
2420
|
+
*/
|
|
2421
|
+
const NAMED: Array<DiscoveredNetworkDevice> = [
|
|
2422
|
+
pingOnlyHost({
|
|
2423
|
+
ipAddress: "10.16.42.51",
|
|
2424
|
+
dnsHostname: "WB0024KDS02.corp.example.com",
|
|
2425
|
+
}),
|
|
2426
|
+
pingOnlyHost({
|
|
2427
|
+
ipAddress: "10.16.42.52",
|
|
2428
|
+
dnsHostname: "wb-0024-kds09.corp.example.com",
|
|
2429
|
+
}),
|
|
2430
|
+
pingOnlyHost({
|
|
2431
|
+
ipAddress: "10.16.42.53",
|
|
2432
|
+
dnsHostname: "WB0024KDS04.corp.example.com",
|
|
2433
|
+
}),
|
|
2434
|
+
pingOnlyHost({
|
|
2435
|
+
ipAddress: "10.16.42.54",
|
|
2436
|
+
dnsHostname: "WB0024KDS05.corp.example.com",
|
|
2437
|
+
}),
|
|
2438
|
+
];
|
|
2439
|
+
|
|
2440
|
+
const UNNAMED_CODES: Array<DiscoveredHostReverseDnsStatus> = [
|
|
2441
|
+
DiscoveredHostReverseDnsStatus.NoRecord,
|
|
2442
|
+
DiscoveredHostReverseDnsStatus.NoRecord,
|
|
2443
|
+
DiscoveredHostReverseDnsStatus.Timeout,
|
|
2444
|
+
DiscoveredHostReverseDnsStatus.Timeout,
|
|
2445
|
+
DiscoveredHostReverseDnsStatus.ServerFailure,
|
|
2446
|
+
DiscoveredHostReverseDnsStatus.Refused,
|
|
2447
|
+
DiscoveredHostReverseDnsStatus.Unreachable,
|
|
2448
|
+
DiscoveredHostReverseDnsStatus.SkippedTimeBudget,
|
|
2449
|
+
];
|
|
2450
|
+
|
|
2451
|
+
const UNNAMED: Array<DiscoveredNetworkDevice> = UNNAMED_CODES.map(
|
|
2452
|
+
(code: DiscoveredHostReverseDnsStatus, index: number) => {
|
|
2453
|
+
return pingOnlyHost({
|
|
2454
|
+
ipAddress: `10.16.42.${55 + index}`,
|
|
2455
|
+
dnsHostnameStatus: code,
|
|
2456
|
+
netbiosNameStatus: DiscoveredHostNetbiosStatus.SkippedGlobalProbe,
|
|
2457
|
+
});
|
|
2458
|
+
},
|
|
2459
|
+
);
|
|
2460
|
+
|
|
2461
|
+
test("the four named displays get no hint", () => {
|
|
2462
|
+
for (const host of NAMED) {
|
|
2463
|
+
expect(
|
|
2464
|
+
explainUnnamedDiscoveredHost({
|
|
2465
|
+
host: host,
|
|
2466
|
+
scan: REPORTERS_SCAN,
|
|
2467
|
+
isGlobalProbe: true,
|
|
2468
|
+
}),
|
|
2469
|
+
).toBeUndefined();
|
|
2470
|
+
}
|
|
2471
|
+
});
|
|
2472
|
+
|
|
2473
|
+
test("each of the other eight says what its own lookup came back with", () => {
|
|
2474
|
+
UNNAMED.forEach((host: DiscoveredNetworkDevice, index: number) => {
|
|
2475
|
+
const code: DiscoveredHostReverseDnsStatus = UNNAMED_CODES[index]!;
|
|
2476
|
+
const tip: string | undefined = REVERSE_DNS_COPY.find(
|
|
2477
|
+
(
|
|
2478
|
+
entry: [DiscoveredHostReverseDnsStatus, string, string | undefined],
|
|
2479
|
+
) => {
|
|
2480
|
+
return entry[0] === code;
|
|
2481
|
+
},
|
|
2482
|
+
)![2];
|
|
2483
|
+
|
|
2484
|
+
expect(
|
|
2485
|
+
explainUnnamedDiscoveredHost({
|
|
2486
|
+
host: host,
|
|
2487
|
+
scan: REPORTERS_SCAN,
|
|
2488
|
+
isGlobalProbe: true,
|
|
2489
|
+
}),
|
|
2490
|
+
).toStrictEqual({
|
|
2491
|
+
label: UNNAMED_HOST_LABEL,
|
|
2492
|
+
text: [
|
|
2493
|
+
SNMP_NOT_CHECKED_SENTENCE,
|
|
2494
|
+
reverseDnsSentence(code),
|
|
2495
|
+
GLOBAL_PROBE_NETBIOS_SENTENCE,
|
|
2496
|
+
...(tip ? [tip] : []),
|
|
2497
|
+
ASK_THE_DEVICE_SNMP_TIP,
|
|
2498
|
+
].join(" "),
|
|
2499
|
+
});
|
|
2500
|
+
});
|
|
2501
|
+
});
|
|
2502
|
+
|
|
2503
|
+
test("a missing record, a timeout and a server failure no longer look alike", () => {
|
|
2504
|
+
const texts: Set<string> = new Set<string>(
|
|
2505
|
+
UNNAMED.map((host: DiscoveredNetworkDevice): string => {
|
|
2506
|
+
return textOf({
|
|
2507
|
+
host: host,
|
|
2508
|
+
scan: REPORTERS_SCAN,
|
|
2509
|
+
isGlobalProbe: true,
|
|
2510
|
+
});
|
|
2511
|
+
}),
|
|
2512
|
+
);
|
|
2513
|
+
|
|
2514
|
+
// Eight hosts, six distinct codes, so six distinct explanations.
|
|
2515
|
+
expect(texts.size).toBe(new Set(UNNAMED_CODES).size);
|
|
2516
|
+
});
|
|
2517
|
+
|
|
2518
|
+
test("the same eight from an older probe claim no reason at all", () => {
|
|
2519
|
+
/*
|
|
2520
|
+
* The row an unupgraded probe stores has no codes. It must not guess
|
|
2521
|
+
* "no PTR record", which is the misdiagnosis that made the report.
|
|
2522
|
+
*/
|
|
2523
|
+
for (const host of UNNAMED) {
|
|
2524
|
+
const legacy: DiscoveredNetworkDevice = { ...host };
|
|
2525
|
+
delete legacy.dnsHostnameStatus;
|
|
2526
|
+
delete legacy.netbiosNameStatus;
|
|
2527
|
+
|
|
2528
|
+
const text: string = textOf({
|
|
2529
|
+
host: legacy,
|
|
2530
|
+
scan: REPORTERS_SCAN,
|
|
2531
|
+
isGlobalProbe: true,
|
|
2532
|
+
});
|
|
2533
|
+
|
|
2534
|
+
expect(text).toBe(
|
|
2535
|
+
[
|
|
2536
|
+
SNMP_NOT_CHECKED_SENTENCE,
|
|
2537
|
+
REVERSE_DNS_NOT_RECORDED_SENTENCE,
|
|
2538
|
+
GLOBAL_PROBE_NETBIOS_SENTENCE,
|
|
2539
|
+
ASK_THE_DEVICE_SNMP_TIP,
|
|
2540
|
+
].join(" "),
|
|
2541
|
+
);
|
|
2542
|
+
expect(text).not.toContain("PTR");
|
|
2543
|
+
expect(text).not.toContain("in time");
|
|
2544
|
+
}
|
|
2545
|
+
});
|
|
2546
|
+
});
|