@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.
Files changed (259) hide show
  1. package/Models/DatabaseModels/GlobalConfig.ts +12 -12
  2. package/Models/DatabaseModels/InventoryItem.ts +44 -1
  3. package/Models/DatabaseModels/NetworkDevice.ts +126 -0
  4. package/Server/EnvironmentConfig.ts +47 -16
  5. package/Server/Infrastructure/Postgres/SchemaMigrations/1791700000000-AddMacAddressToNetworkDevice.ts +25 -0
  6. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +2 -0
  7. package/Server/Infrastructure/Redis.ts +8 -8
  8. package/Server/Infrastructure/Status.ts +10 -4
  9. package/Server/Services/DatabaseService.ts +65 -13
  10. package/Server/Services/InventoryItemService.ts +31 -1
  11. package/Server/Services/MonitorTemplateService.ts +140 -19
  12. package/Server/Services/NetworkDeviceAutoImportRuleEngineService.ts +349 -41
  13. package/Server/Services/NetworkDeviceDiscoveryScanService.ts +87 -2
  14. package/Server/Services/NetworkDeviceService.ts +69 -0
  15. package/Server/Services/UserNotificationRuleService.ts +76 -25
  16. package/Server/Types/Database/Permissions/ReadPermission.ts +45 -5
  17. package/Server/Types/Database/QueryUtil.ts +56 -0
  18. package/Server/Types/Markdown.ts +93 -1
  19. package/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.ts +82 -17
  20. package/Server/Utils/EmailRollup/EmailRollupFlushRunner.ts +10 -1
  21. package/Server/Utils/EmailRollup/EmailRollupRenderer.ts +2 -2
  22. package/Server/Utils/Monitor/Criteria/CompareCriteria.ts +82 -35
  23. package/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.ts +31 -5
  24. package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +202 -37
  25. package/Server/Utils/Monitor/MonitorCriteriaExpectationBuilder.ts +82 -9
  26. package/Server/Utils/Monitor/MonitorCriteriaMessageBuilder.ts +16 -7
  27. package/Server/Utils/Monitor/MonitorCriteriaMessageFormatter.ts +29 -17
  28. package/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.ts +107 -48
  29. package/Server/Utils/Monitor/NetworkDeviceMacLearningUtil.ts +158 -0
  30. package/Server/Utils/Monitor/NetworkInventoryUtil.ts +27 -0
  31. package/Server/Utils/Telemetry/EntityRegistry.ts +11 -1
  32. package/Server/Utils/Telemetry/TelemetryEntity.ts +83 -9
  33. package/Tests/App/Dashboard/AddNeighborToMonitoringModal.test.tsx +32 -0
  34. package/Tests/App/Dashboard/AdoptedDeviceDrawer.test.tsx +374 -0
  35. package/Tests/App/Dashboard/DashboardLabelVariables.test.tsx +580 -0
  36. package/Tests/App/Dashboard/DashboardMonitorLabelVariable.test.tsx +438 -0
  37. package/Tests/App/Dashboard/DashboardVariableAllContract.test.tsx +5 -2
  38. package/Tests/App/Dashboard/DeviceAttachmentCard.test.tsx +273 -0
  39. package/Tests/App/Dashboard/DiscoveryReviewInventoryRefresh.test.tsx +505 -0
  40. package/Tests/App/Dashboard/EntityDetailPanel.test.tsx +163 -0
  41. package/Tests/App/Dashboard/InfrastructureExplorer.test.tsx +401 -0
  42. package/Tests/App/Dashboard/InfrastructureGraph.test.tsx +232 -0
  43. package/Tests/App/Dashboard/InventoryItems.test.tsx +201 -0
  44. package/Tests/App/Dashboard/InventoryTypeAndStatusFacets.test.tsx +798 -0
  45. package/Tests/App/Dashboard/NetworkTopologyLiveView.test.tsx +320 -0
  46. package/Tests/App/Dashboard/NetworkTopologyToolbar.test.tsx +275 -0
  47. package/Tests/App/Dashboard/OnCallCalendarFeedEmptyState.test.tsx +975 -0
  48. package/Tests/App/Dashboard/OnCallCalendarFeedPlanGate.test.tsx +63 -0
  49. package/Tests/App/Dashboard/ServiceMapGraph.test.tsx +626 -0
  50. package/Tests/App/Dashboard/TopologyDataLoading.test.tsx +348 -0
  51. package/Tests/App/Dashboard/TopologyPageNavigation.test.tsx +331 -0
  52. package/Tests/App/Dashboard/UserSettingsEmailPreferences.test.tsx +603 -0
  53. package/Tests/App/Dashboard/UserSettingsNotificationSettings.test.tsx +324 -0
  54. package/Tests/App/PublicDashboard/DashboardVariableSelector.test.tsx +13 -4
  55. package/Tests/App/StatusPage/StatusPageOidcOrigin.test.tsx +223 -0
  56. package/Tests/Models/InventoryItemStatus.test.ts +147 -0
  57. package/Tests/Models/NetworkDeviceMacAddressColumn.test.ts +294 -0
  58. package/Tests/Server/API/DashboardPublicResourceListAPI.test.ts +129 -0
  59. package/Tests/Server/EnvironmentConfigValkey.test.ts +227 -0
  60. package/Tests/Server/Infrastructure/Postgres/AddMacAddressToNetworkDeviceMigration.test.ts +365 -0
  61. package/Tests/Server/Infrastructure/Postgres/CustomFieldValueMappingMigration.test.ts +62 -23
  62. package/Tests/Server/Infrastructure/ValkeyDeploymentConfig.test.ts +265 -0
  63. package/Tests/Server/Middleware/HttpMetricsMiddleware.test.ts +382 -0
  64. package/Tests/Server/Middleware/SCIMAuthorization.test.ts +525 -0
  65. package/Tests/Server/Services/DatabaseServiceAggregateBy.test.ts +291 -56
  66. package/Tests/Server/Services/DatabaseServiceSortTiebreaker.test.ts +151 -0
  67. package/Tests/Server/Services/InventoryItemManualCreate.test.ts +105 -0
  68. package/Tests/Server/Services/InventoryItemPromotionGate.test.ts +178 -0
  69. package/Tests/Server/Services/MonitorTemplateServiceCustomFieldSync.test.ts +502 -0
  70. package/Tests/Server/Services/NetworkDeviceAutoImportRuleEngineService.test.ts +1133 -81
  71. package/Tests/Server/Services/NetworkDeviceDiscoveryScanRegistration.test.ts +520 -0
  72. package/Tests/Server/Services/NetworkDeviceMacAddressNormalization.test.ts +572 -0
  73. package/Tests/Server/Types/Database/InventoryStatusPostgres.test.ts +368 -0
  74. package/Tests/Server/Types/Database/Permissions/ReadBlockPermission.test.ts +261 -14
  75. package/Tests/Server/Types/Database/QueryUtilIncludesAnyOfGroups.test.ts +169 -0
  76. package/Tests/Server/Types/Database/QueryUtilIncludesAnyOfGroupsPostgres.test.ts +297 -0
  77. package/Tests/Server/Types/Markdown.test.ts +194 -1
  78. package/Tests/Server/Types/Workflow/Components/IncomingWebhookUtils.test.ts +279 -0
  79. package/Tests/Server/Types/Workflow/Components/TextToJson.test.ts +212 -0
  80. package/Tests/Server/Utils/CustomField/CustomFieldMappingRegistry.test.ts +436 -0
  81. package/Tests/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.test.ts +214 -0
  82. package/Tests/Server/Utils/EmailRollup/EmailRollupFlushRunnerBehaviour.test.ts +1 -1
  83. package/Tests/Server/Utils/EmailRollup/EmailRollupRenderer.test.ts +10 -3
  84. package/Tests/Server/Utils/EventLoop.test.ts +359 -0
  85. package/Tests/Server/Utils/JsonWebToken.test.ts +30 -0
  86. package/Tests/Server/Utils/Monitor/Criteria/CompareCriteria.test.ts +242 -0
  87. package/Tests/Server/Utils/Monitor/Criteria/CompareCriteriaAggregation.test.ts +13 -7
  88. package/Tests/Server/Utils/Monitor/Criteria/DatabaseMonitorCriteria.test.ts +29 -0
  89. package/Tests/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.test.ts +30 -6
  90. package/Tests/Server/Utils/Monitor/Criteria/MetricMonitorCriteriaAnomalyUnits.test.ts +246 -0
  91. package/Tests/Server/Utils/Monitor/MonitorCriteriaEmailEvalLogAgreement.test.ts +280 -0
  92. package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluator.test.ts +224 -10
  93. package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorMetricUnits.test.ts +570 -0
  94. package/Tests/Server/Utils/Monitor/MonitorCriteriaExpectationBuilderUnits.test.ts +30 -5
  95. package/Tests/Server/Utils/Monitor/MonitorCriteriaMessageBuilderUnits.test.ts +68 -21
  96. package/Tests/Server/Utils/Monitor/MonitorCriteriaObservationBuilderUnits.test.ts +36 -19
  97. package/Tests/Server/Utils/Monitor/NetworkDeviceMacLearningUtil.test.ts +573 -0
  98. package/Tests/Server/Utils/Monitor/NetworkInventoryUtil.test.ts +317 -0
  99. package/Tests/Server/Utils/Telemetry/ContextSpanProcessor.test.ts +281 -0
  100. package/Tests/Server/Utils/Telemetry/InventoryDuplicateIdentity.test.ts +316 -0
  101. package/Tests/Server/Utils/Telemetry/TelemetryEntity.test.ts +111 -8
  102. package/Tests/Types/Database/IncludesAnyOfGroups.test.ts +106 -0
  103. package/Tests/Types/JSONFunctions.test.ts +40 -0
  104. package/Tests/Types/NetworkAutomation/AutoImportRunMerge.test.ts +235 -0
  105. package/Tests/Types/NetworkAutomation/RuleRunResult.test.ts +66 -0
  106. package/Tests/Types/NetworkAutomation/RuleRunResultDescribe.test.ts +309 -1
  107. package/Tests/Types/Telemetry/InventoryLiveness.test.ts +99 -0
  108. package/Tests/UI/Components/CustomFields/CustomFieldsDetail.test.tsx +160 -0
  109. package/Tests/UI/ReactRouterSingletonBuild.test.ts +133 -0
  110. package/Tests/UI/Rum/ReplayHeader.test.tsx +10 -2
  111. package/Tests/UI/Rum/ReplayPlayerChrome.test.tsx +514 -0
  112. package/Tests/UI/Rum/ReplayTimeFormat.test.ts +94 -0
  113. package/Tests/UI/Rum/ReplayUi.test.tsx +559 -0
  114. package/Tests/UI/Utils/Breadcrumb/fixtures/RealBreadcrumbTrails.ts +5 -0
  115. package/Tests/UI/Utils/Breadcrumb/fixtures/RealRoutePatterns.ts +1 -0
  116. package/Tests/Utils/Dashboard/Components/DashboardMonitorListComponent.test.ts +11 -1
  117. package/Tests/Utils/Dashboard/LabelVariable.test.ts +257 -0
  118. package/Tests/Utils/Dashboard/VariableUrlState.test.ts +144 -14
  119. package/Tests/Utils/MetricUnitUtil.test.ts +81 -0
  120. package/Tests/Utils/Monitor/DeviceMacLearningUtil.test.ts +497 -0
  121. package/Tests/Utils/Monitor/MetricValueFormatter.test.ts +546 -0
  122. package/Tests/Utils/Monitor/MonitorTemplateCustomFieldUtil.test.ts +218 -0
  123. package/Tests/Utils/Monitor/NetworkTopologyEndpointAdoption.test.ts +1755 -0
  124. package/Tests/Utils/NetworkAutomation/AutoImportRunChain.test.ts +320 -0
  125. package/Types/BaseDatabase/IncludesAnyOfGroups.ts +77 -0
  126. package/Types/Dashboard/DashboardComponents/ComponentArgument.ts +1 -0
  127. package/Types/Dashboard/DashboardComponents/DashboardMonitorListComponent.ts +1 -0
  128. package/Types/Dashboard/DashboardVariable.ts +8 -0
  129. package/Types/JSON.ts +4 -0
  130. package/Types/JSONFunctions.ts +16 -0
  131. package/Types/Monitor/SnmpMonitor/NetworkTopology.ts +13 -0
  132. package/Types/NetworkAutomation/RuleRunResult.ts +402 -13
  133. package/Types/SerializableObjectDictionary.ts +4 -0
  134. package/Types/Telemetry/InventoryLiveness.ts +56 -0
  135. package/UI/Components/CSVFileUpload/CSVFileUpload.tsx +9 -2
  136. package/UI/Components/CustomFields/CustomFieldsDetail.tsx +46 -21
  137. package/UI/Components/Dashboard/DashboardVariableControl.tsx +285 -0
  138. package/UI/esbuild-config.js +6 -0
  139. package/Utils/Dashboard/Components/DashboardMonitorListComponent.ts +12 -1
  140. package/Utils/Dashboard/LabelVariable.ts +152 -0
  141. package/Utils/Dashboard/VariableUrlState.ts +27 -5
  142. package/Utils/MetricUnitUtil.ts +34 -0
  143. package/Utils/Monitor/DeviceMacLearningUtil.ts +177 -0
  144. package/Utils/Monitor/MetricValueFormatter.ts +342 -0
  145. package/Utils/Monitor/MonitorTemplateCustomFieldUtil.ts +124 -0
  146. package/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.ts +7 -2
  147. package/Utils/Monitor/NetworkTopologyUtil.ts +693 -0
  148. package/Utils/NetworkAutomation/AutoImportRunChain.ts +138 -0
  149. package/build/dist/Models/DatabaseModels/GlobalConfig.js +12 -12
  150. package/build/dist/Models/DatabaseModels/GlobalConfig.js.map +1 -1
  151. package/build/dist/Models/DatabaseModels/InventoryItem.js +34 -1
  152. package/build/dist/Models/DatabaseModels/InventoryItem.js.map +1 -1
  153. package/build/dist/Models/DatabaseModels/NetworkDevice.js +128 -0
  154. package/build/dist/Models/DatabaseModels/NetworkDevice.js.map +1 -1
  155. package/build/dist/Server/EnvironmentConfig.js +44 -12
  156. package/build/dist/Server/EnvironmentConfig.js.map +1 -1
  157. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1791700000000-AddMacAddressToNetworkDevice.js +14 -0
  158. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1791700000000-AddMacAddressToNetworkDevice.js.map +1 -0
  159. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +2 -0
  160. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  161. package/build/dist/Server/Infrastructure/Redis.js +8 -8
  162. package/build/dist/Server/Infrastructure/Redis.js.map +1 -1
  163. package/build/dist/Server/Infrastructure/Status.js +10 -4
  164. package/build/dist/Server/Infrastructure/Status.js.map +1 -1
  165. package/build/dist/Server/Services/DatabaseService.js +58 -13
  166. package/build/dist/Server/Services/DatabaseService.js.map +1 -1
  167. package/build/dist/Server/Services/InventoryItemService.js +28 -1
  168. package/build/dist/Server/Services/InventoryItemService.js.map +1 -1
  169. package/build/dist/Server/Services/MonitorTemplateService.js +116 -17
  170. package/build/dist/Server/Services/MonitorTemplateService.js.map +1 -1
  171. package/build/dist/Server/Services/NetworkDeviceAutoImportRuleEngineService.js +270 -37
  172. package/build/dist/Server/Services/NetworkDeviceAutoImportRuleEngineService.js.map +1 -1
  173. package/build/dist/Server/Services/NetworkDeviceDiscoveryScanService.js +65 -0
  174. package/build/dist/Server/Services/NetworkDeviceDiscoveryScanService.js.map +1 -1
  175. package/build/dist/Server/Services/NetworkDeviceService.js +54 -0
  176. package/build/dist/Server/Services/NetworkDeviceService.js.map +1 -1
  177. package/build/dist/Server/Services/UserNotificationRuleService.js +60 -25
  178. package/build/dist/Server/Services/UserNotificationRuleService.js.map +1 -1
  179. package/build/dist/Server/Types/Database/Permissions/ReadPermission.js +29 -4
  180. package/build/dist/Server/Types/Database/Permissions/ReadPermission.js.map +1 -1
  181. package/build/dist/Server/Types/Database/QueryUtil.js +36 -0
  182. package/build/dist/Server/Types/Database/QueryUtil.js.map +1 -1
  183. package/build/dist/Server/Types/Markdown.js +75 -0
  184. package/build/dist/Server/Types/Markdown.js.map +1 -1
  185. package/build/dist/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.js +53 -18
  186. package/build/dist/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.js.map +1 -1
  187. package/build/dist/Server/Utils/EmailRollup/EmailRollupFlushRunner.js +10 -1
  188. package/build/dist/Server/Utils/EmailRollup/EmailRollupFlushRunner.js.map +1 -1
  189. package/build/dist/Server/Utils/EmailRollup/EmailRollupRenderer.js +2 -2
  190. package/build/dist/Server/Utils/EmailRollup/EmailRollupRenderer.js.map +1 -1
  191. package/build/dist/Server/Utils/Monitor/Criteria/CompareCriteria.js +58 -35
  192. package/build/dist/Server/Utils/Monitor/Criteria/CompareCriteria.js.map +1 -1
  193. package/build/dist/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.js +29 -5
  194. package/build/dist/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.js.map +1 -1
  195. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +166 -35
  196. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
  197. package/build/dist/Server/Utils/Monitor/MonitorCriteriaExpectationBuilder.js +58 -9
  198. package/build/dist/Server/Utils/Monitor/MonitorCriteriaExpectationBuilder.js.map +1 -1
  199. package/build/dist/Server/Utils/Monitor/MonitorCriteriaMessageBuilder.js +9 -3
  200. package/build/dist/Server/Utils/Monitor/MonitorCriteriaMessageBuilder.js.map +1 -1
  201. package/build/dist/Server/Utils/Monitor/MonitorCriteriaMessageFormatter.js +26 -12
  202. package/build/dist/Server/Utils/Monitor/MonitorCriteriaMessageFormatter.js.map +1 -1
  203. package/build/dist/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.js +74 -33
  204. package/build/dist/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.js.map +1 -1
  205. package/build/dist/Server/Utils/Monitor/NetworkDeviceMacLearningUtil.js +114 -0
  206. package/build/dist/Server/Utils/Monitor/NetworkDeviceMacLearningUtil.js.map +1 -0
  207. package/build/dist/Server/Utils/Monitor/NetworkInventoryUtil.js +25 -0
  208. package/build/dist/Server/Utils/Monitor/NetworkInventoryUtil.js.map +1 -1
  209. package/build/dist/Server/Utils/Telemetry/EntityRegistry.js +9 -1
  210. package/build/dist/Server/Utils/Telemetry/EntityRegistry.js.map +1 -1
  211. package/build/dist/Server/Utils/Telemetry/TelemetryEntity.js +70 -13
  212. package/build/dist/Server/Utils/Telemetry/TelemetryEntity.js.map +1 -1
  213. package/build/dist/Types/BaseDatabase/IncludesAnyOfGroups.js +54 -0
  214. package/build/dist/Types/BaseDatabase/IncludesAnyOfGroups.js.map +1 -0
  215. package/build/dist/Types/Dashboard/DashboardComponents/ComponentArgument.js +1 -0
  216. package/build/dist/Types/Dashboard/DashboardComponents/ComponentArgument.js.map +1 -1
  217. package/build/dist/Types/Dashboard/DashboardVariable.js +1 -0
  218. package/build/dist/Types/Dashboard/DashboardVariable.js.map +1 -1
  219. package/build/dist/Types/JSON.js +1 -0
  220. package/build/dist/Types/JSON.js.map +1 -1
  221. package/build/dist/Types/JSONFunctions.js +13 -0
  222. package/build/dist/Types/JSONFunctions.js.map +1 -1
  223. package/build/dist/Types/NetworkAutomation/RuleRunResult.js +291 -6
  224. package/build/dist/Types/NetworkAutomation/RuleRunResult.js.map +1 -1
  225. package/build/dist/Types/SerializableObjectDictionary.js +4 -0
  226. package/build/dist/Types/SerializableObjectDictionary.js.map +1 -1
  227. package/build/dist/Types/Telemetry/InventoryLiveness.js +37 -0
  228. package/build/dist/Types/Telemetry/InventoryLiveness.js.map +1 -0
  229. package/build/dist/UI/Components/CSVFileUpload/CSVFileUpload.js +7 -0
  230. package/build/dist/UI/Components/CSVFileUpload/CSVFileUpload.js.map +1 -1
  231. package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js +9 -2
  232. package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js.map +1 -1
  233. package/build/dist/UI/Components/Dashboard/DashboardVariableControl.js +137 -0
  234. package/build/dist/UI/Components/Dashboard/DashboardVariableControl.js.map +1 -0
  235. package/build/dist/Utils/Dashboard/Components/DashboardMonitorListComponent.js +9 -1
  236. package/build/dist/Utils/Dashboard/Components/DashboardMonitorListComponent.js.map +1 -1
  237. package/build/dist/Utils/Dashboard/LabelVariable.js +96 -0
  238. package/build/dist/Utils/Dashboard/LabelVariable.js.map +1 -0
  239. package/build/dist/Utils/Dashboard/VariableUrlState.js +19 -4
  240. package/build/dist/Utils/Dashboard/VariableUrlState.js.map +1 -1
  241. package/build/dist/Utils/MetricUnitUtil.js +26 -0
  242. package/build/dist/Utils/MetricUnitUtil.js.map +1 -1
  243. package/build/dist/Utils/Monitor/DeviceMacLearningUtil.js +112 -0
  244. package/build/dist/Utils/Monitor/DeviceMacLearningUtil.js.map +1 -0
  245. package/build/dist/Utils/Monitor/MetricValueFormatter.js +276 -0
  246. package/build/dist/Utils/Monitor/MetricValueFormatter.js.map +1 -0
  247. package/build/dist/Utils/Monitor/MonitorTemplateCustomFieldUtil.js +101 -0
  248. package/build/dist/Utils/Monitor/MonitorTemplateCustomFieldUtil.js.map +1 -0
  249. package/build/dist/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.js +7 -2
  250. package/build/dist/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.js.map +1 -1
  251. package/build/dist/Utils/Monitor/NetworkTopologyUtil.js +484 -0
  252. package/build/dist/Utils/Monitor/NetworkTopologyUtil.js.map +1 -1
  253. package/build/dist/Utils/NetworkAutomation/AutoImportRunChain.js +59 -0
  254. package/build/dist/Utils/NetworkAutomation/AutoImportRunChain.js.map +1 -0
  255. package/jest.config.json +2 -0
  256. package/package.json +1 -1
  257. package/test-setup.sh +9 -9
  258. package/tsconfig.json +51 -11
  259. 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
+ });