@oneuptime/common 12.0.34 → 13.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Models/DatabaseModels/GlobalConfig.ts +12 -12
- package/Models/DatabaseModels/InventoryItem.ts +44 -1
- package/Models/DatabaseModels/NetworkDevice.ts +126 -0
- package/Server/EnvironmentConfig.ts +47 -16
- package/Server/Infrastructure/Postgres/SchemaMigrations/1791700000000-AddMacAddressToNetworkDevice.ts +25 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +2 -0
- package/Server/Infrastructure/Redis.ts +8 -8
- package/Server/Infrastructure/Status.ts +10 -4
- package/Server/Services/DatabaseService.ts +65 -13
- package/Server/Services/InventoryItemService.ts +31 -1
- package/Server/Services/MonitorTemplateService.ts +140 -19
- package/Server/Services/NetworkDeviceAutoImportRuleEngineService.ts +349 -41
- package/Server/Services/NetworkDeviceDiscoveryScanService.ts +87 -2
- package/Server/Services/NetworkDeviceService.ts +69 -0
- package/Server/Services/UserNotificationRuleService.ts +76 -25
- package/Server/Types/Database/Permissions/ReadPermission.ts +45 -5
- package/Server/Types/Database/QueryUtil.ts +56 -0
- package/Server/Types/Markdown.ts +93 -1
- package/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.ts +82 -17
- package/Server/Utils/EmailRollup/EmailRollupFlushRunner.ts +10 -1
- package/Server/Utils/EmailRollup/EmailRollupRenderer.ts +2 -2
- package/Server/Utils/Monitor/Criteria/CompareCriteria.ts +82 -35
- package/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.ts +31 -5
- package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +202 -37
- package/Server/Utils/Monitor/MonitorCriteriaExpectationBuilder.ts +82 -9
- package/Server/Utils/Monitor/MonitorCriteriaMessageBuilder.ts +16 -7
- package/Server/Utils/Monitor/MonitorCriteriaMessageFormatter.ts +29 -17
- package/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.ts +107 -48
- package/Server/Utils/Monitor/NetworkDeviceMacLearningUtil.ts +158 -0
- package/Server/Utils/Monitor/NetworkInventoryUtil.ts +27 -0
- package/Server/Utils/Telemetry/EntityRegistry.ts +11 -1
- package/Server/Utils/Telemetry/TelemetryEntity.ts +83 -9
- package/Tests/App/Dashboard/AddNeighborToMonitoringModal.test.tsx +32 -0
- package/Tests/App/Dashboard/AdoptedDeviceDrawer.test.tsx +374 -0
- package/Tests/App/Dashboard/DashboardLabelVariables.test.tsx +580 -0
- package/Tests/App/Dashboard/DashboardMonitorLabelVariable.test.tsx +438 -0
- package/Tests/App/Dashboard/DashboardVariableAllContract.test.tsx +5 -2
- package/Tests/App/Dashboard/DeviceAttachmentCard.test.tsx +273 -0
- package/Tests/App/Dashboard/DiscoveryReviewInventoryRefresh.test.tsx +505 -0
- package/Tests/App/Dashboard/EntityDetailPanel.test.tsx +163 -0
- package/Tests/App/Dashboard/InfrastructureExplorer.test.tsx +401 -0
- package/Tests/App/Dashboard/InfrastructureGraph.test.tsx +232 -0
- package/Tests/App/Dashboard/InventoryItems.test.tsx +201 -0
- package/Tests/App/Dashboard/InventoryTypeAndStatusFacets.test.tsx +798 -0
- package/Tests/App/Dashboard/NetworkTopologyLiveView.test.tsx +320 -0
- package/Tests/App/Dashboard/NetworkTopologyToolbar.test.tsx +275 -0
- package/Tests/App/Dashboard/OnCallCalendarFeedEmptyState.test.tsx +975 -0
- package/Tests/App/Dashboard/OnCallCalendarFeedPlanGate.test.tsx +63 -0
- package/Tests/App/Dashboard/ServiceMapGraph.test.tsx +626 -0
- package/Tests/App/Dashboard/TopologyDataLoading.test.tsx +348 -0
- package/Tests/App/Dashboard/TopologyPageNavigation.test.tsx +331 -0
- package/Tests/App/Dashboard/UserSettingsEmailPreferences.test.tsx +603 -0
- package/Tests/App/Dashboard/UserSettingsNotificationSettings.test.tsx +324 -0
- package/Tests/App/PublicDashboard/DashboardVariableSelector.test.tsx +13 -4
- package/Tests/App/StatusPage/StatusPageOidcOrigin.test.tsx +223 -0
- package/Tests/Models/InventoryItemStatus.test.ts +147 -0
- package/Tests/Models/NetworkDeviceMacAddressColumn.test.ts +294 -0
- package/Tests/Server/API/DashboardPublicResourceListAPI.test.ts +129 -0
- package/Tests/Server/EnvironmentConfigValkey.test.ts +227 -0
- package/Tests/Server/Infrastructure/Postgres/AddMacAddressToNetworkDeviceMigration.test.ts +365 -0
- package/Tests/Server/Infrastructure/Postgres/CustomFieldValueMappingMigration.test.ts +62 -23
- package/Tests/Server/Infrastructure/ValkeyDeploymentConfig.test.ts +265 -0
- package/Tests/Server/Middleware/HttpMetricsMiddleware.test.ts +382 -0
- package/Tests/Server/Middleware/SCIMAuthorization.test.ts +525 -0
- package/Tests/Server/Services/DatabaseServiceAggregateBy.test.ts +291 -56
- package/Tests/Server/Services/DatabaseServiceSortTiebreaker.test.ts +151 -0
- package/Tests/Server/Services/InventoryItemManualCreate.test.ts +105 -0
- package/Tests/Server/Services/InventoryItemPromotionGate.test.ts +178 -0
- package/Tests/Server/Services/MonitorTemplateServiceCustomFieldSync.test.ts +502 -0
- package/Tests/Server/Services/NetworkDeviceAutoImportRuleEngineService.test.ts +1133 -81
- package/Tests/Server/Services/NetworkDeviceDiscoveryScanRegistration.test.ts +520 -0
- package/Tests/Server/Services/NetworkDeviceMacAddressNormalization.test.ts +572 -0
- package/Tests/Server/Types/Database/InventoryStatusPostgres.test.ts +368 -0
- package/Tests/Server/Types/Database/Permissions/ReadBlockPermission.test.ts +261 -14
- package/Tests/Server/Types/Database/QueryUtilIncludesAnyOfGroups.test.ts +169 -0
- package/Tests/Server/Types/Database/QueryUtilIncludesAnyOfGroupsPostgres.test.ts +297 -0
- package/Tests/Server/Types/Markdown.test.ts +194 -1
- package/Tests/Server/Types/Workflow/Components/IncomingWebhookUtils.test.ts +279 -0
- package/Tests/Server/Types/Workflow/Components/TextToJson.test.ts +212 -0
- package/Tests/Server/Utils/CustomField/CustomFieldMappingRegistry.test.ts +436 -0
- package/Tests/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.test.ts +214 -0
- package/Tests/Server/Utils/EmailRollup/EmailRollupFlushRunnerBehaviour.test.ts +1 -1
- package/Tests/Server/Utils/EmailRollup/EmailRollupRenderer.test.ts +10 -3
- package/Tests/Server/Utils/EventLoop.test.ts +359 -0
- package/Tests/Server/Utils/JsonWebToken.test.ts +30 -0
- package/Tests/Server/Utils/Monitor/Criteria/CompareCriteria.test.ts +242 -0
- package/Tests/Server/Utils/Monitor/Criteria/CompareCriteriaAggregation.test.ts +13 -7
- package/Tests/Server/Utils/Monitor/Criteria/DatabaseMonitorCriteria.test.ts +29 -0
- package/Tests/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.test.ts +30 -6
- package/Tests/Server/Utils/Monitor/Criteria/MetricMonitorCriteriaAnomalyUnits.test.ts +246 -0
- package/Tests/Server/Utils/Monitor/MonitorCriteriaEmailEvalLogAgreement.test.ts +280 -0
- package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluator.test.ts +224 -10
- package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorMetricUnits.test.ts +570 -0
- package/Tests/Server/Utils/Monitor/MonitorCriteriaExpectationBuilderUnits.test.ts +30 -5
- package/Tests/Server/Utils/Monitor/MonitorCriteriaMessageBuilderUnits.test.ts +68 -21
- package/Tests/Server/Utils/Monitor/MonitorCriteriaObservationBuilderUnits.test.ts +36 -19
- package/Tests/Server/Utils/Monitor/NetworkDeviceMacLearningUtil.test.ts +573 -0
- package/Tests/Server/Utils/Monitor/NetworkInventoryUtil.test.ts +317 -0
- package/Tests/Server/Utils/Telemetry/ContextSpanProcessor.test.ts +281 -0
- package/Tests/Server/Utils/Telemetry/InventoryDuplicateIdentity.test.ts +316 -0
- package/Tests/Server/Utils/Telemetry/TelemetryEntity.test.ts +111 -8
- package/Tests/Types/Database/IncludesAnyOfGroups.test.ts +106 -0
- package/Tests/Types/JSONFunctions.test.ts +40 -0
- package/Tests/Types/NetworkAutomation/AutoImportRunMerge.test.ts +235 -0
- package/Tests/Types/NetworkAutomation/RuleRunResult.test.ts +66 -0
- package/Tests/Types/NetworkAutomation/RuleRunResultDescribe.test.ts +309 -1
- package/Tests/Types/Telemetry/InventoryLiveness.test.ts +99 -0
- package/Tests/UI/Components/CustomFields/CustomFieldsDetail.test.tsx +160 -0
- package/Tests/UI/ReactRouterSingletonBuild.test.ts +133 -0
- package/Tests/UI/Rum/ReplayHeader.test.tsx +10 -2
- package/Tests/UI/Rum/ReplayPlayerChrome.test.tsx +514 -0
- package/Tests/UI/Rum/ReplayTimeFormat.test.ts +94 -0
- package/Tests/UI/Rum/ReplayUi.test.tsx +559 -0
- package/Tests/UI/Utils/Breadcrumb/fixtures/RealBreadcrumbTrails.ts +5 -0
- package/Tests/UI/Utils/Breadcrumb/fixtures/RealRoutePatterns.ts +1 -0
- package/Tests/Utils/Dashboard/Components/DashboardMonitorListComponent.test.ts +11 -1
- package/Tests/Utils/Dashboard/LabelVariable.test.ts +257 -0
- package/Tests/Utils/Dashboard/VariableUrlState.test.ts +144 -14
- package/Tests/Utils/MetricUnitUtil.test.ts +81 -0
- package/Tests/Utils/Monitor/DeviceMacLearningUtil.test.ts +497 -0
- package/Tests/Utils/Monitor/MetricValueFormatter.test.ts +546 -0
- package/Tests/Utils/Monitor/MonitorTemplateCustomFieldUtil.test.ts +218 -0
- package/Tests/Utils/Monitor/NetworkTopologyEndpointAdoption.test.ts +1755 -0
- package/Tests/Utils/NetworkAutomation/AutoImportRunChain.test.ts +320 -0
- package/Types/BaseDatabase/IncludesAnyOfGroups.ts +77 -0
- package/Types/Dashboard/DashboardComponents/ComponentArgument.ts +1 -0
- package/Types/Dashboard/DashboardComponents/DashboardMonitorListComponent.ts +1 -0
- package/Types/Dashboard/DashboardVariable.ts +8 -0
- package/Types/JSON.ts +4 -0
- package/Types/JSONFunctions.ts +16 -0
- package/Types/Monitor/SnmpMonitor/NetworkTopology.ts +13 -0
- package/Types/NetworkAutomation/RuleRunResult.ts +402 -13
- package/Types/SerializableObjectDictionary.ts +4 -0
- package/Types/Telemetry/InventoryLiveness.ts +56 -0
- package/UI/Components/CSVFileUpload/CSVFileUpload.tsx +9 -2
- package/UI/Components/CustomFields/CustomFieldsDetail.tsx +46 -21
- package/UI/Components/Dashboard/DashboardVariableControl.tsx +285 -0
- package/UI/esbuild-config.js +6 -0
- package/Utils/Dashboard/Components/DashboardMonitorListComponent.ts +12 -1
- package/Utils/Dashboard/LabelVariable.ts +152 -0
- package/Utils/Dashboard/VariableUrlState.ts +27 -5
- package/Utils/MetricUnitUtil.ts +34 -0
- package/Utils/Monitor/DeviceMacLearningUtil.ts +177 -0
- package/Utils/Monitor/MetricValueFormatter.ts +342 -0
- package/Utils/Monitor/MonitorTemplateCustomFieldUtil.ts +124 -0
- package/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.ts +7 -2
- package/Utils/Monitor/NetworkTopologyUtil.ts +693 -0
- package/Utils/NetworkAutomation/AutoImportRunChain.ts +138 -0
- package/build/dist/Models/DatabaseModels/GlobalConfig.js +12 -12
- package/build/dist/Models/DatabaseModels/GlobalConfig.js.map +1 -1
- package/build/dist/Models/DatabaseModels/InventoryItem.js +34 -1
- package/build/dist/Models/DatabaseModels/InventoryItem.js.map +1 -1
- package/build/dist/Models/DatabaseModels/NetworkDevice.js +128 -0
- package/build/dist/Models/DatabaseModels/NetworkDevice.js.map +1 -1
- package/build/dist/Server/EnvironmentConfig.js +44 -12
- package/build/dist/Server/EnvironmentConfig.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1791700000000-AddMacAddressToNetworkDevice.js +14 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1791700000000-AddMacAddressToNetworkDevice.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +2 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Infrastructure/Redis.js +8 -8
- package/build/dist/Server/Infrastructure/Redis.js.map +1 -1
- package/build/dist/Server/Infrastructure/Status.js +10 -4
- package/build/dist/Server/Infrastructure/Status.js.map +1 -1
- package/build/dist/Server/Services/DatabaseService.js +58 -13
- package/build/dist/Server/Services/DatabaseService.js.map +1 -1
- package/build/dist/Server/Services/InventoryItemService.js +28 -1
- package/build/dist/Server/Services/InventoryItemService.js.map +1 -1
- package/build/dist/Server/Services/MonitorTemplateService.js +116 -17
- package/build/dist/Server/Services/MonitorTemplateService.js.map +1 -1
- package/build/dist/Server/Services/NetworkDeviceAutoImportRuleEngineService.js +270 -37
- package/build/dist/Server/Services/NetworkDeviceAutoImportRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/NetworkDeviceDiscoveryScanService.js +65 -0
- package/build/dist/Server/Services/NetworkDeviceDiscoveryScanService.js.map +1 -1
- package/build/dist/Server/Services/NetworkDeviceService.js +54 -0
- package/build/dist/Server/Services/NetworkDeviceService.js.map +1 -1
- package/build/dist/Server/Services/UserNotificationRuleService.js +60 -25
- package/build/dist/Server/Services/UserNotificationRuleService.js.map +1 -1
- package/build/dist/Server/Types/Database/Permissions/ReadPermission.js +29 -4
- package/build/dist/Server/Types/Database/Permissions/ReadPermission.js.map +1 -1
- package/build/dist/Server/Types/Database/QueryUtil.js +36 -0
- package/build/dist/Server/Types/Database/QueryUtil.js.map +1 -1
- package/build/dist/Server/Types/Markdown.js +75 -0
- package/build/dist/Server/Types/Markdown.js.map +1 -1
- package/build/dist/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.js +53 -18
- package/build/dist/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.js.map +1 -1
- package/build/dist/Server/Utils/EmailRollup/EmailRollupFlushRunner.js +10 -1
- package/build/dist/Server/Utils/EmailRollup/EmailRollupFlushRunner.js.map +1 -1
- package/build/dist/Server/Utils/EmailRollup/EmailRollupRenderer.js +2 -2
- package/build/dist/Server/Utils/EmailRollup/EmailRollupRenderer.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/CompareCriteria.js +58 -35
- package/build/dist/Server/Utils/Monitor/Criteria/CompareCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.js +29 -5
- package/build/dist/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +166 -35
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaExpectationBuilder.js +58 -9
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaExpectationBuilder.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaMessageBuilder.js +9 -3
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaMessageBuilder.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaMessageFormatter.js +26 -12
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaMessageFormatter.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.js +74 -33
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/NetworkDeviceMacLearningUtil.js +114 -0
- package/build/dist/Server/Utils/Monitor/NetworkDeviceMacLearningUtil.js.map +1 -0
- package/build/dist/Server/Utils/Monitor/NetworkInventoryUtil.js +25 -0
- package/build/dist/Server/Utils/Monitor/NetworkInventoryUtil.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/EntityRegistry.js +9 -1
- package/build/dist/Server/Utils/Telemetry/EntityRegistry.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/TelemetryEntity.js +70 -13
- package/build/dist/Server/Utils/Telemetry/TelemetryEntity.js.map +1 -1
- package/build/dist/Types/BaseDatabase/IncludesAnyOfGroups.js +54 -0
- package/build/dist/Types/BaseDatabase/IncludesAnyOfGroups.js.map +1 -0
- package/build/dist/Types/Dashboard/DashboardComponents/ComponentArgument.js +1 -0
- package/build/dist/Types/Dashboard/DashboardComponents/ComponentArgument.js.map +1 -1
- package/build/dist/Types/Dashboard/DashboardVariable.js +1 -0
- package/build/dist/Types/Dashboard/DashboardVariable.js.map +1 -1
- package/build/dist/Types/JSON.js +1 -0
- package/build/dist/Types/JSON.js.map +1 -1
- package/build/dist/Types/JSONFunctions.js +13 -0
- package/build/dist/Types/JSONFunctions.js.map +1 -1
- package/build/dist/Types/NetworkAutomation/RuleRunResult.js +291 -6
- package/build/dist/Types/NetworkAutomation/RuleRunResult.js.map +1 -1
- package/build/dist/Types/SerializableObjectDictionary.js +4 -0
- package/build/dist/Types/SerializableObjectDictionary.js.map +1 -1
- package/build/dist/Types/Telemetry/InventoryLiveness.js +37 -0
- package/build/dist/Types/Telemetry/InventoryLiveness.js.map +1 -0
- package/build/dist/UI/Components/CSVFileUpload/CSVFileUpload.js +7 -0
- package/build/dist/UI/Components/CSVFileUpload/CSVFileUpload.js.map +1 -1
- package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js +9 -2
- package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js.map +1 -1
- package/build/dist/UI/Components/Dashboard/DashboardVariableControl.js +137 -0
- package/build/dist/UI/Components/Dashboard/DashboardVariableControl.js.map +1 -0
- package/build/dist/Utils/Dashboard/Components/DashboardMonitorListComponent.js +9 -1
- package/build/dist/Utils/Dashboard/Components/DashboardMonitorListComponent.js.map +1 -1
- package/build/dist/Utils/Dashboard/LabelVariable.js +96 -0
- package/build/dist/Utils/Dashboard/LabelVariable.js.map +1 -0
- package/build/dist/Utils/Dashboard/VariableUrlState.js +19 -4
- package/build/dist/Utils/Dashboard/VariableUrlState.js.map +1 -1
- package/build/dist/Utils/MetricUnitUtil.js +26 -0
- package/build/dist/Utils/MetricUnitUtil.js.map +1 -1
- package/build/dist/Utils/Monitor/DeviceMacLearningUtil.js +112 -0
- package/build/dist/Utils/Monitor/DeviceMacLearningUtil.js.map +1 -0
- package/build/dist/Utils/Monitor/MetricValueFormatter.js +276 -0
- package/build/dist/Utils/Monitor/MetricValueFormatter.js.map +1 -0
- package/build/dist/Utils/Monitor/MonitorTemplateCustomFieldUtil.js +101 -0
- package/build/dist/Utils/Monitor/MonitorTemplateCustomFieldUtil.js.map +1 -0
- package/build/dist/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.js +7 -2
- package/build/dist/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.js.map +1 -1
- package/build/dist/Utils/Monitor/NetworkTopologyUtil.js +484 -0
- package/build/dist/Utils/Monitor/NetworkTopologyUtil.js.map +1 -1
- package/build/dist/Utils/NetworkAutomation/AutoImportRunChain.js +59 -0
- package/build/dist/Utils/NetworkAutomation/AutoImportRunChain.js.map +1 -0
- package/jest.config.json +2 -0
- package/package.json +1 -1
- package/test-setup.sh +9 -9
- package/tsconfig.json +51 -11
- package/Tests/App/Dashboard/NotificationEmailPreferences.test.tsx +0 -399
|
@@ -0,0 +1,572 @@
|
|
|
1
|
+
import NetworkSiteService from "../../../Server/Services/NetworkSiteService";
|
|
2
|
+
import { Service as NetworkDeviceServiceType } from "../../../Server/Services/NetworkDeviceService";
|
|
3
|
+
import CreateBy from "../../../Server/Types/Database/CreateBy";
|
|
4
|
+
import UpdateBy from "../../../Server/Types/Database/UpdateBy";
|
|
5
|
+
import { OnCreate, OnUpdate } from "../../../Server/Types/Database/Hooks";
|
|
6
|
+
import NetworkDevice from "../../../Models/DatabaseModels/NetworkDevice";
|
|
7
|
+
import BadDataException from "../../../Types/Exception/BadDataException";
|
|
8
|
+
import ObjectID from "../../../Types/ObjectID";
|
|
9
|
+
import { beforeEach, describe, expect, test } from "@jest/globals";
|
|
10
|
+
|
|
11
|
+
/*
|
|
12
|
+
* WHAT THIS FILE IS DEFENDING
|
|
13
|
+
*
|
|
14
|
+
* NetworkDevice.macAddress is what lets a ping-only device (issue #3489) be
|
|
15
|
+
* recognised in the forwarding tables of the switches around it: the
|
|
16
|
+
* topology builder compares the declared MAC against each NetworkEndpoint
|
|
17
|
+
* row's MAC as a plain string. Every endpoint row already holds the
|
|
18
|
+
* lowercase colon form, so the one thing that makes the comparison work is
|
|
19
|
+
* that the declared MAC is stored the same way whatever an operator pasted.
|
|
20
|
+
* A MAC stored as "AA-BB-CC-DD-EE-FF" is not wrong, it is just a MAC that
|
|
21
|
+
* matches nothing, forever, with no error anywhere.
|
|
22
|
+
*
|
|
23
|
+
* So these tests pin the write-side contract of the hook, through the real
|
|
24
|
+
* onBeforeCreate and onBeforeUpdate:
|
|
25
|
+
*
|
|
26
|
+
* - every accepted spelling lands as aa:bb:cc:dd:ee:ff;
|
|
27
|
+
* - blank clears the column (a form cannot post null);
|
|
28
|
+
* - anything that is not twelve hex digits is REFUSED, not stored;
|
|
29
|
+
* - a non-string is refused with its own message;
|
|
30
|
+
* - undefined, null and a SQL-expression function pass through untouched.
|
|
31
|
+
*
|
|
32
|
+
* And, on the update path, that the normalisation sits ABOVE the early
|
|
33
|
+
* return. A macAddress-only payload changes neither site nor identity, so
|
|
34
|
+
* it is precisely the shape that skips the snapshot read — the same trap
|
|
35
|
+
* the OID template guard once fell into (NetworkDeviceOidTemplateWriteGuards
|
|
36
|
+
* .test.ts). A normaliser placed below the return would be dead code on the
|
|
37
|
+
* only write the device settings form actually makes.
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
const PROJECT_ID: ObjectID = new ObjectID(
|
|
41
|
+
"11111111-1111-4111-8111-111111111111",
|
|
42
|
+
);
|
|
43
|
+
const DEVICE_ID: ObjectID = new ObjectID(
|
|
44
|
+
"44444444-4444-4444-8444-444444444444",
|
|
45
|
+
);
|
|
46
|
+
const BOGUS_SITE_ID: ObjectID = new ObjectID(
|
|
47
|
+
"77777777-7777-4777-8777-777777777777",
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
const STORED_FORM: string = "aa:bb:cc:dd:ee:ff";
|
|
51
|
+
|
|
52
|
+
/*
|
|
53
|
+
* The spellings an operator is likely to paste — from a switch CLI, a
|
|
54
|
+
* vendor label, a Windows ipconfig, a Cisco show mac address-table — and
|
|
55
|
+
* that normalizeMac is documented to accept. Each has to land as the ONE
|
|
56
|
+
* stored form, because equality against endpoint rows is the whole point.
|
|
57
|
+
*/
|
|
58
|
+
const ACCEPTED_SPELLINGS: Array<string> = [
|
|
59
|
+
"aa:bb:cc:dd:ee:ff",
|
|
60
|
+
"AA:BB:CC:DD:EE:FF",
|
|
61
|
+
"AA-BB-CC-DD-EE-FF",
|
|
62
|
+
"aabb.ccdd.eeff",
|
|
63
|
+
"AABB.CCDD.EEFF",
|
|
64
|
+
"aabbccddeeff",
|
|
65
|
+
"AABBCCDDEEFF",
|
|
66
|
+
"0xaabbccddeeff",
|
|
67
|
+
"0XAABBCCDDEEFF",
|
|
68
|
+
" aa:bb:cc:dd:ee:ff ",
|
|
69
|
+
];
|
|
70
|
+
|
|
71
|
+
const BLANK_SPELLINGS: Array<string> = ["", " ", "\t", "\n"];
|
|
72
|
+
|
|
73
|
+
/*
|
|
74
|
+
* Not a MAC, in the ways it usually goes wrong: five pairs, seven pairs,
|
|
75
|
+
* letters past f, eleven digits, prose.
|
|
76
|
+
*/
|
|
77
|
+
const REJECTED_SPELLINGS: Array<string> = [
|
|
78
|
+
"aa:bb:cc:dd:ee",
|
|
79
|
+
"aa:bb:cc:dd:ee:ff:00",
|
|
80
|
+
"gg:hh:ii:jj:kk:ll",
|
|
81
|
+
"aa:bb:cc:dd:ee:f",
|
|
82
|
+
"not-a-mac",
|
|
83
|
+
"aa:bb:cc:dd:ee:ff extra",
|
|
84
|
+
];
|
|
85
|
+
|
|
86
|
+
const NOT_A_MAC_MESSAGE: RegExp = /six pairs of hex digits/;
|
|
87
|
+
const NOT_TEXT_MESSAGE: RegExp = /must be text/;
|
|
88
|
+
|
|
89
|
+
type DeviceServiceInternals = {
|
|
90
|
+
onBeforeCreate: (
|
|
91
|
+
createBy: CreateBy<NetworkDevice>,
|
|
92
|
+
) => Promise<OnCreate<NetworkDevice>>;
|
|
93
|
+
onBeforeUpdate: (
|
|
94
|
+
updateBy: UpdateBy<NetworkDevice>,
|
|
95
|
+
) => Promise<OnUpdate<NetworkDevice>>;
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
function buildDeviceService(): {
|
|
99
|
+
service: NetworkDeviceServiceType;
|
|
100
|
+
internals: DeviceServiceInternals;
|
|
101
|
+
} {
|
|
102
|
+
const service: NetworkDeviceServiceType = new NetworkDeviceServiceType();
|
|
103
|
+
return {
|
|
104
|
+
service,
|
|
105
|
+
internals: service as unknown as DeviceServiceInternals,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/*
|
|
110
|
+
* The device the update matches, for the one write shape below that does
|
|
111
|
+
* reach the snapshot read.
|
|
112
|
+
*/
|
|
113
|
+
function matchedDevice(): NetworkDevice {
|
|
114
|
+
const device: NetworkDevice = new NetworkDevice(DEVICE_ID);
|
|
115
|
+
device.projectId = PROJECT_ID;
|
|
116
|
+
return device;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/*
|
|
120
|
+
* A create carrying the identity columns and a MAC, and nothing that would
|
|
121
|
+
* make the hook reach for the database: no site, no probe, no monitor, no
|
|
122
|
+
* template. `macAddress` is written through a loose record so a test can
|
|
123
|
+
* hand the hook a value the model's type would not allow.
|
|
124
|
+
*/
|
|
125
|
+
function createWithMac(macAddress: unknown): CreateBy<NetworkDevice> {
|
|
126
|
+
const device: NetworkDevice = new NetworkDevice();
|
|
127
|
+
device.projectId = PROJECT_ID;
|
|
128
|
+
device.name = "Register 4";
|
|
129
|
+
device.hostname = "10.20.30.44";
|
|
130
|
+
(device as unknown as Record<string, unknown>)["macAddress"] = macAddress;
|
|
131
|
+
|
|
132
|
+
return {
|
|
133
|
+
data: device,
|
|
134
|
+
props: { isRoot: true },
|
|
135
|
+
} as CreateBy<NetworkDevice>;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/*
|
|
139
|
+
* A payload that writes the MAC and NOTHING else — no site, no hostname, no
|
|
140
|
+
* name, no sysName. This is the shape the device settings form posts, and
|
|
141
|
+
* it is precisely the shape that trips onBeforeUpdate's early return.
|
|
142
|
+
*/
|
|
143
|
+
function macOnlyUpdate(macAddress: unknown): UpdateBy<NetworkDevice> {
|
|
144
|
+
return {
|
|
145
|
+
query: { _id: DEVICE_ID.toString() },
|
|
146
|
+
data: { macAddress: macAddress },
|
|
147
|
+
props: { isRoot: true },
|
|
148
|
+
} as unknown as UpdateBy<NetworkDevice>;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function storedMacOf(result: OnCreate<NetworkDevice>): unknown {
|
|
152
|
+
return (result.createBy.data as unknown as Record<string, unknown>)[
|
|
153
|
+
"macAddress"
|
|
154
|
+
];
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function writtenMacOf(result: OnUpdate<NetworkDevice>): unknown {
|
|
158
|
+
return (result.updateBy.data as unknown as Record<string, unknown>)[
|
|
159
|
+
"macAddress"
|
|
160
|
+
];
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
describe("the MAC address is normalised on create", () => {
|
|
164
|
+
beforeEach(() => {
|
|
165
|
+
jest.restoreAllMocks();
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
test.each(ACCEPTED_SPELLINGS)(
|
|
169
|
+
"stores %j as lowercase colon form",
|
|
170
|
+
async (spelling: string) => {
|
|
171
|
+
const { internals } = buildDeviceService();
|
|
172
|
+
|
|
173
|
+
const result: OnCreate<NetworkDevice> = await internals.onBeforeCreate(
|
|
174
|
+
createWithMac(spelling),
|
|
175
|
+
);
|
|
176
|
+
|
|
177
|
+
expect(storedMacOf(result)).toBe(STORED_FORM);
|
|
178
|
+
},
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
/*
|
|
182
|
+
* A form cannot post null, and "delete what I typed" has to work from
|
|
183
|
+
* one. Blank is the spelling of "no MAC" that reaches the hook.
|
|
184
|
+
*/
|
|
185
|
+
test.each(BLANK_SPELLINGS)("stores %j as null", async (spelling: string) => {
|
|
186
|
+
const { internals } = buildDeviceService();
|
|
187
|
+
|
|
188
|
+
const result: OnCreate<NetworkDevice> = await internals.onBeforeCreate(
|
|
189
|
+
createWithMac(spelling),
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
expect(storedMacOf(result)).toBeNull();
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
/*
|
|
196
|
+
* A model instance carries every column as an own property set to
|
|
197
|
+
* undefined, so `"macAddress" in data` is true on a create that never
|
|
198
|
+
* mentioned the MAC. That must NOT be read as "clear it": undefined has to
|
|
199
|
+
* come out as undefined, or every discovery import would write a null it
|
|
200
|
+
* never asked for.
|
|
201
|
+
*/
|
|
202
|
+
test("leaves an unset MAC undefined rather than turning it into null", async () => {
|
|
203
|
+
const { internals } = buildDeviceService();
|
|
204
|
+
const createBy: CreateBy<NetworkDevice> = createWithMac(undefined);
|
|
205
|
+
|
|
206
|
+
expect("macAddress" in createBy.data).toBe(true);
|
|
207
|
+
|
|
208
|
+
const result: OnCreate<NetworkDevice> =
|
|
209
|
+
await internals.onBeforeCreate(createBy);
|
|
210
|
+
|
|
211
|
+
expect(storedMacOf(result)).toBeUndefined();
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
/*
|
|
215
|
+
* THE assertion of the column. A malformed MAC stored as typed is not an
|
|
216
|
+
* error anyone sees — it is a device that never gets its cable drawn, with
|
|
217
|
+
* nothing to say why. Refuse it at the write.
|
|
218
|
+
*/
|
|
219
|
+
test.each(REJECTED_SPELLINGS)(
|
|
220
|
+
"refuses %j with the six-pairs message",
|
|
221
|
+
async (spelling: string) => {
|
|
222
|
+
const { internals } = buildDeviceService();
|
|
223
|
+
|
|
224
|
+
await expect(
|
|
225
|
+
internals.onBeforeCreate(createWithMac(spelling)),
|
|
226
|
+
).rejects.toThrow(NOT_A_MAC_MESSAGE);
|
|
227
|
+
},
|
|
228
|
+
);
|
|
229
|
+
|
|
230
|
+
test("the refusal is a BadDataException, so the API answers 400 and not 500", async () => {
|
|
231
|
+
const { internals } = buildDeviceService();
|
|
232
|
+
|
|
233
|
+
await expect(
|
|
234
|
+
internals.onBeforeCreate(createWithMac("not-a-mac")),
|
|
235
|
+
).rejects.toThrow(BadDataException);
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
/*
|
|
239
|
+
* A number is what a JSON client sends when it drops the quotes around
|
|
240
|
+
* twelve bare hex digits that happen to be all decimal. Its own message,
|
|
241
|
+
* because "six pairs of hex digits" would send the caller looking at the
|
|
242
|
+
* digits rather than the quotes.
|
|
243
|
+
*/
|
|
244
|
+
test("refuses a non-string with its own message", async () => {
|
|
245
|
+
const { internals } = buildDeviceService();
|
|
246
|
+
|
|
247
|
+
await expect(
|
|
248
|
+
internals.onBeforeCreate(createWithMac(112233445566)),
|
|
249
|
+
).rejects.toThrow(NOT_TEXT_MESSAGE);
|
|
250
|
+
await expect(
|
|
251
|
+
internals.onBeforeCreate(createWithMac(112233445566)),
|
|
252
|
+
).rejects.toThrow(BadDataException);
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
/*
|
|
256
|
+
* Ordering within the hook. The site tenancy guard is the first thing
|
|
257
|
+
* onBeforeCreate reaches for the database on, and a payload can be wrong
|
|
258
|
+
* in both ways at once. The MAC check runs first, so the operator hears
|
|
259
|
+
* about the field they just typed rather than about a site lookup — and,
|
|
260
|
+
* more to the point, a malformed MAC never costs a database round trip.
|
|
261
|
+
*
|
|
262
|
+
* The stub resolves null, which is what the guard sees for a site that
|
|
263
|
+
* does not exist; the companion test below proves the stub is live.
|
|
264
|
+
*/
|
|
265
|
+
test("normalises before the site guard runs, so a bad MAC wins over a bad site", async () => {
|
|
266
|
+
const { internals } = buildDeviceService();
|
|
267
|
+
|
|
268
|
+
const siteFindSpy: jest.SpyInstance = jest
|
|
269
|
+
.spyOn(NetworkSiteService, "findOneById")
|
|
270
|
+
.mockResolvedValue(null as never);
|
|
271
|
+
|
|
272
|
+
const createBy: CreateBy<NetworkDevice> = createWithMac("not-a-mac");
|
|
273
|
+
createBy.data.siteId = BOGUS_SITE_ID;
|
|
274
|
+
|
|
275
|
+
await expect(internals.onBeforeCreate(createBy)).rejects.toThrow(
|
|
276
|
+
NOT_A_MAC_MESSAGE,
|
|
277
|
+
);
|
|
278
|
+
expect(siteFindSpy).not.toHaveBeenCalled();
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
test("...and the site guard really would have refused that site", async () => {
|
|
282
|
+
const { internals } = buildDeviceService();
|
|
283
|
+
|
|
284
|
+
const siteFindSpy: jest.SpyInstance = jest
|
|
285
|
+
.spyOn(NetworkSiteService, "findOneById")
|
|
286
|
+
.mockResolvedValue(null as never);
|
|
287
|
+
|
|
288
|
+
const createBy: CreateBy<NetworkDevice> = createWithMac(STORED_FORM);
|
|
289
|
+
createBy.data.siteId = BOGUS_SITE_ID;
|
|
290
|
+
|
|
291
|
+
await expect(internals.onBeforeCreate(createBy)).rejects.toThrow(
|
|
292
|
+
/Network site not found/,
|
|
293
|
+
);
|
|
294
|
+
expect(siteFindSpy).toHaveBeenCalledTimes(1);
|
|
295
|
+
});
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
describe("the MAC address is normalised on update", () => {
|
|
299
|
+
beforeEach(() => {
|
|
300
|
+
jest.restoreAllMocks();
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
test.each(ACCEPTED_SPELLINGS)(
|
|
304
|
+
"writes %j as lowercase colon form",
|
|
305
|
+
async (spelling: string) => {
|
|
306
|
+
const { internals } = buildDeviceService();
|
|
307
|
+
|
|
308
|
+
const result: OnUpdate<NetworkDevice> = await internals.onBeforeUpdate(
|
|
309
|
+
macOnlyUpdate(spelling),
|
|
310
|
+
);
|
|
311
|
+
|
|
312
|
+
expect(writtenMacOf(result)).toBe(STORED_FORM);
|
|
313
|
+
},
|
|
314
|
+
);
|
|
315
|
+
|
|
316
|
+
test.each(BLANK_SPELLINGS)("writes %j as null", async (spelling: string) => {
|
|
317
|
+
const { internals } = buildDeviceService();
|
|
318
|
+
|
|
319
|
+
const result: OnUpdate<NetworkDevice> = await internals.onBeforeUpdate(
|
|
320
|
+
macOnlyUpdate(spelling),
|
|
321
|
+
);
|
|
322
|
+
|
|
323
|
+
expect(writtenMacOf(result)).toBeNull();
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
// Null is already "clear the column"; there is nothing to normalise.
|
|
327
|
+
test("leaves an explicit null as null", async () => {
|
|
328
|
+
const { internals } = buildDeviceService();
|
|
329
|
+
|
|
330
|
+
const result: OnUpdate<NetworkDevice> = await internals.onBeforeUpdate(
|
|
331
|
+
macOnlyUpdate(null),
|
|
332
|
+
);
|
|
333
|
+
|
|
334
|
+
expect(writtenMacOf(result)).toBeNull();
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
/*
|
|
338
|
+
* TypeORM drops an undefined column before it builds the SET list, so an
|
|
339
|
+
* undefined that reaches the hook is a column the write does not touch.
|
|
340
|
+
* Turning it into null would clear a MAC the caller never mentioned.
|
|
341
|
+
*/
|
|
342
|
+
test("leaves an undefined value undefined", async () => {
|
|
343
|
+
const { internals } = buildDeviceService();
|
|
344
|
+
|
|
345
|
+
const result: OnUpdate<NetworkDevice> = await internals.onBeforeUpdate(
|
|
346
|
+
macOnlyUpdate(undefined),
|
|
347
|
+
);
|
|
348
|
+
|
|
349
|
+
expect("macAddress" in result.updateBy.data).toBe(true);
|
|
350
|
+
expect(writtenMacOf(result)).toBeUndefined();
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
test.each(REJECTED_SPELLINGS)(
|
|
354
|
+
"refuses %j with the six-pairs message",
|
|
355
|
+
async (spelling: string) => {
|
|
356
|
+
const { internals } = buildDeviceService();
|
|
357
|
+
|
|
358
|
+
await expect(
|
|
359
|
+
internals.onBeforeUpdate(macOnlyUpdate(spelling)),
|
|
360
|
+
).rejects.toThrow(NOT_A_MAC_MESSAGE);
|
|
361
|
+
},
|
|
362
|
+
);
|
|
363
|
+
|
|
364
|
+
test("refuses a non-string with its own message", async () => {
|
|
365
|
+
const { internals } = buildDeviceService();
|
|
366
|
+
|
|
367
|
+
await expect(
|
|
368
|
+
internals.onBeforeUpdate(macOnlyUpdate(112233445566)),
|
|
369
|
+
).rejects.toThrow(NOT_TEXT_MESSAGE);
|
|
370
|
+
await expect(
|
|
371
|
+
internals.onBeforeUpdate(macOnlyUpdate(112233445566)),
|
|
372
|
+
).rejects.toThrow(BadDataException);
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
/*
|
|
376
|
+
* An update payload is a QueryDeepPartialEntity, so a column may hold a
|
|
377
|
+
* SQL-expression function instead of a value. Nothing in the tree writes
|
|
378
|
+
* this column that way, but refusing it would buy no safety and would
|
|
379
|
+
* make a future caller's life harder — so it passes through by reference,
|
|
380
|
+
* neither normalised nor rejected.
|
|
381
|
+
*/
|
|
382
|
+
test("leaves a raw SQL-expression function untouched", async () => {
|
|
383
|
+
const { internals } = buildDeviceService();
|
|
384
|
+
|
|
385
|
+
const expression: () => string = (): string => {
|
|
386
|
+
return `lower("macAddress")`;
|
|
387
|
+
};
|
|
388
|
+
|
|
389
|
+
const result: OnUpdate<NetworkDevice> = await internals.onBeforeUpdate(
|
|
390
|
+
macOnlyUpdate(expression),
|
|
391
|
+
);
|
|
392
|
+
|
|
393
|
+
expect(writtenMacOf(result)).toBe(expression);
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
/*
|
|
397
|
+
* THE regression test for placement. A macAddress-only write changes
|
|
398
|
+
* neither site nor identity and names none of the relations the early
|
|
399
|
+
* return watches for, so the hook bails out before the snapshot read —
|
|
400
|
+
* and the normalisation above still has to have run. If it drifts below
|
|
401
|
+
* the return, the spelling test above fails; this one pins the other
|
|
402
|
+
* half, that the early return is still taken (no snapshot read for a
|
|
403
|
+
* write that does not need one).
|
|
404
|
+
*/
|
|
405
|
+
test("a macAddress-only payload still takes the early return", async () => {
|
|
406
|
+
const { service, internals } = buildDeviceService();
|
|
407
|
+
|
|
408
|
+
const findBySpy: jest.SpyInstance = jest
|
|
409
|
+
.spyOn(service, "findBy")
|
|
410
|
+
.mockResolvedValue([matchedDevice()] as never);
|
|
411
|
+
|
|
412
|
+
const result: OnUpdate<NetworkDevice> = await internals.onBeforeUpdate(
|
|
413
|
+
macOnlyUpdate("AA-BB-CC-DD-EE-FF"),
|
|
414
|
+
);
|
|
415
|
+
|
|
416
|
+
expect(writtenMacOf(result)).toBe(STORED_FORM);
|
|
417
|
+
expect(result.carryForward).toBeNull();
|
|
418
|
+
expect(findBySpy).not.toHaveBeenCalled();
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
/*
|
|
422
|
+
* ...and a malformed MAC on that same shape is refused without a snapshot
|
|
423
|
+
* read either: the check costs nothing on the write it exists for.
|
|
424
|
+
*/
|
|
425
|
+
test("a malformed macAddress-only payload is refused before any read", async () => {
|
|
426
|
+
const { service, internals } = buildDeviceService();
|
|
427
|
+
|
|
428
|
+
const findBySpy: jest.SpyInstance = jest
|
|
429
|
+
.spyOn(service, "findBy")
|
|
430
|
+
.mockResolvedValue([matchedDevice()] as never);
|
|
431
|
+
|
|
432
|
+
await expect(
|
|
433
|
+
internals.onBeforeUpdate(macOnlyUpdate("not-a-mac")),
|
|
434
|
+
).rejects.toThrow(NOT_A_MAC_MESSAGE);
|
|
435
|
+
|
|
436
|
+
expect(findBySpy).not.toHaveBeenCalled();
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
/*
|
|
440
|
+
* The other write shape: the settings form saving the name and the MAC
|
|
441
|
+
* together. `name` is an identity column, so this one DOES read the
|
|
442
|
+
* snapshot — the MAC still has to come out normalised, and the snapshot
|
|
443
|
+
* read has to have happened, or this test is the early-return test again.
|
|
444
|
+
*/
|
|
445
|
+
test("a payload carrying other columns too still normalises", async () => {
|
|
446
|
+
const { service, internals } = buildDeviceService();
|
|
447
|
+
|
|
448
|
+
const findBySpy: jest.SpyInstance = jest
|
|
449
|
+
.spyOn(service, "findBy")
|
|
450
|
+
.mockResolvedValue([matchedDevice()] as never);
|
|
451
|
+
|
|
452
|
+
const result: OnUpdate<NetworkDevice> = await internals.onBeforeUpdate({
|
|
453
|
+
query: { _id: DEVICE_ID.toString() },
|
|
454
|
+
data: { name: "Register 4", macAddress: "AABB.CCDD.EEFF" },
|
|
455
|
+
props: { isRoot: true },
|
|
456
|
+
} as unknown as UpdateBy<NetworkDevice>);
|
|
457
|
+
|
|
458
|
+
expect(writtenMacOf(result)).toBe(STORED_FORM);
|
|
459
|
+
expect(result.updateBy.data.name).toBe("Register 4");
|
|
460
|
+
expect(findBySpy).toHaveBeenCalledTimes(1);
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
/*
|
|
464
|
+
* And on that shape a malformed MAC is refused ABOVE the snapshot read:
|
|
465
|
+
* the hook checks what it was handed before it goes looking for what is
|
|
466
|
+
* already stored.
|
|
467
|
+
*/
|
|
468
|
+
test("a malformed MAC alongside other columns is refused before the snapshot read", async () => {
|
|
469
|
+
const { service, internals } = buildDeviceService();
|
|
470
|
+
|
|
471
|
+
const findBySpy: jest.SpyInstance = jest
|
|
472
|
+
.spyOn(service, "findBy")
|
|
473
|
+
.mockResolvedValue([matchedDevice()] as never);
|
|
474
|
+
|
|
475
|
+
await expect(
|
|
476
|
+
internals.onBeforeUpdate({
|
|
477
|
+
query: { _id: DEVICE_ID.toString() },
|
|
478
|
+
data: { name: "Register 4", macAddress: "not-a-mac" },
|
|
479
|
+
props: { isRoot: true },
|
|
480
|
+
} as unknown as UpdateBy<NetworkDevice>),
|
|
481
|
+
).rejects.toThrow(NOT_A_MAC_MESSAGE);
|
|
482
|
+
|
|
483
|
+
expect(findBySpy).not.toHaveBeenCalled();
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
// A payload that never mentions the MAC is not the hook's business.
|
|
487
|
+
test("does nothing to a payload that does not carry the column", async () => {
|
|
488
|
+
const { service, internals } = buildDeviceService();
|
|
489
|
+
|
|
490
|
+
const findBySpy: jest.SpyInstance = jest
|
|
491
|
+
.spyOn(service, "findBy")
|
|
492
|
+
.mockResolvedValue([matchedDevice()] as never);
|
|
493
|
+
|
|
494
|
+
const result: OnUpdate<NetworkDevice> = await internals.onBeforeUpdate({
|
|
495
|
+
query: { _id: DEVICE_ID.toString() },
|
|
496
|
+
data: { pollingIntervalInMinutes: 10 },
|
|
497
|
+
props: { isRoot: true },
|
|
498
|
+
} as unknown as UpdateBy<NetworkDevice>);
|
|
499
|
+
|
|
500
|
+
expect("macAddress" in result.updateBy.data).toBe(false);
|
|
501
|
+
expect(findBySpy).not.toHaveBeenCalled();
|
|
502
|
+
});
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
/*
|
|
506
|
+
* Whatever comes through the API is the operator's. The ARP pass writes
|
|
507
|
+
* hook-free and may only correct a value it wrote itself, so every write
|
|
508
|
+
* that lands here clears the provenance flag beside the MAC - including a
|
|
509
|
+
* blank, which is "forget what you learned".
|
|
510
|
+
*/
|
|
511
|
+
describe("a MAC written through the API is the operator's", () => {
|
|
512
|
+
beforeEach(() => {
|
|
513
|
+
jest.restoreAllMocks();
|
|
514
|
+
});
|
|
515
|
+
|
|
516
|
+
test("create clears the learned flag beside a typed MAC", async () => {
|
|
517
|
+
const { internals } = buildDeviceService();
|
|
518
|
+
|
|
519
|
+
const result: OnCreate<NetworkDevice> = await internals.onBeforeCreate(
|
|
520
|
+
createWithMac("AA-BB-CC-DD-EE-FF"),
|
|
521
|
+
);
|
|
522
|
+
|
|
523
|
+
expect(
|
|
524
|
+
(result.createBy.data as unknown as Record<string, unknown>)[
|
|
525
|
+
"isMacAddressLearned"
|
|
526
|
+
],
|
|
527
|
+
).toBe(false);
|
|
528
|
+
});
|
|
529
|
+
|
|
530
|
+
test("update clears the learned flag beside a typed MAC", async () => {
|
|
531
|
+
const { internals } = buildDeviceService();
|
|
532
|
+
|
|
533
|
+
const result: OnUpdate<NetworkDevice> = await internals.onBeforeUpdate(
|
|
534
|
+
macOnlyUpdate("aabb.ccdd.eeff"),
|
|
535
|
+
);
|
|
536
|
+
|
|
537
|
+
expect(
|
|
538
|
+
(result.updateBy.data as unknown as Record<string, unknown>)[
|
|
539
|
+
"isMacAddressLearned"
|
|
540
|
+
],
|
|
541
|
+
).toBe(false);
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
test("blanking the MAC clears the learned flag too", async () => {
|
|
545
|
+
const { internals } = buildDeviceService();
|
|
546
|
+
|
|
547
|
+
const result: OnUpdate<NetworkDevice> = await internals.onBeforeUpdate(
|
|
548
|
+
macOnlyUpdate(" "),
|
|
549
|
+
);
|
|
550
|
+
|
|
551
|
+
const data: Record<string, unknown> = result.updateBy
|
|
552
|
+
.data as unknown as Record<string, unknown>;
|
|
553
|
+
expect(data["macAddress"]).toBeNull();
|
|
554
|
+
expect(data["isMacAddressLearned"]).toBe(false);
|
|
555
|
+
});
|
|
556
|
+
|
|
557
|
+
test("a payload that does not mention the MAC leaves the flag alone", async () => {
|
|
558
|
+
const { service, internals } = buildDeviceService();
|
|
559
|
+
jest.spyOn(service, "findBy").mockResolvedValue([matchedDevice()] as never);
|
|
560
|
+
|
|
561
|
+
const result: OnUpdate<NetworkDevice> = await internals.onBeforeUpdate({
|
|
562
|
+
query: { _id: DEVICE_ID.toString() },
|
|
563
|
+
data: { name: "renamed" },
|
|
564
|
+
props: { isRoot: true },
|
|
565
|
+
} as unknown as UpdateBy<NetworkDevice>);
|
|
566
|
+
|
|
567
|
+
expect(
|
|
568
|
+
"isMacAddressLearned" in
|
|
569
|
+
(result.updateBy.data as unknown as Record<string, unknown>),
|
|
570
|
+
).toBe(false);
|
|
571
|
+
});
|
|
572
|
+
});
|