@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,497 @@
1
+ import DeviceMacLearningUtil, {
2
+ MacLearningCandidateDevice,
3
+ MacLearningIpBinding,
4
+ MacLearningPlanEntry,
5
+ siteKeyOf,
6
+ } from "../../../Utils/Monitor/DeviceMacLearningUtil";
7
+
8
+ /*
9
+ * The planner decides which managed devices get a MAC written from a walked
10
+ * router's ARP table. Every rule in it is a refusal, and every refusal exists
11
+ * because the alternative draws a WRONG cable on the topology - a register
12
+ * hung off another store's switch, a handset flip-flopping between two
13
+ * routers' opinions. So the cases here are mostly about what is NOT planned,
14
+ * and about the one thing that is: exactly one device, at exactly one
15
+ * address, bound to exactly one MAC, in the router's own site, with nothing
16
+ * in its MAC column yet.
17
+ *
18
+ * Pure and deterministic, so it is pinned without a database; the server
19
+ * util that applies the plan is covered in
20
+ * Tests/Server/Utils/Monitor/NetworkDeviceMacLearningUtil.test.ts.
21
+ */
22
+
23
+ const SITE_A: string = "site-a";
24
+ const SITE_B: string = "site-b";
25
+
26
+ const REGISTER_IP: string = "10.0.0.5";
27
+ const REGISTER_MAC: string = "aa:bb:cc:dd:ee:01";
28
+ const HANDSET_IP: string = "10.0.0.6";
29
+ const HANDSET_MAC: string = "aa:bb:cc:dd:ee:02";
30
+
31
+ function binding(ipAddress: string, macAddress: string): MacLearningIpBinding {
32
+ return { ipAddress, macAddress };
33
+ }
34
+
35
+ function device(
36
+ id: string,
37
+ hostname: string | undefined,
38
+ overrides?: Partial<MacLearningCandidateDevice>,
39
+ ): MacLearningCandidateDevice {
40
+ return { id, hostname, ...overrides };
41
+ }
42
+
43
+ function plan(data: {
44
+ ipBindings: Array<MacLearningIpBinding>;
45
+ devices: Array<MacLearningCandidateDevice>;
46
+ observingSiteId?: string | null | undefined;
47
+ }): Array<MacLearningPlanEntry> {
48
+ return DeviceMacLearningUtil.planMacLearning(data);
49
+ }
50
+
51
+ describe("DeviceMacLearningUtil.siteKeyOf", () => {
52
+ it("reads null, undefined, empty and whitespace as the same no-site key", () => {
53
+ expect(siteKeyOf(null)).toBe("");
54
+ expect(siteKeyOf(undefined)).toBe("");
55
+ expect(siteKeyOf("")).toBe("");
56
+ expect(siteKeyOf(" ")).toBe("");
57
+ });
58
+
59
+ it("trims a real site id so a padded value still keys the same site", () => {
60
+ expect(siteKeyOf(SITE_A)).toBe(SITE_A);
61
+ expect(siteKeyOf(` ${SITE_A} `)).toBe(SITE_A);
62
+ });
63
+ });
64
+
65
+ describe("DeviceMacLearningUtil.planMacLearning", () => {
66
+ it("plans the one device at a bound address, in the router's site, with an empty MAC", () => {
67
+ const result: Array<MacLearningPlanEntry> = plan({
68
+ ipBindings: [binding(REGISTER_IP, REGISTER_MAC)],
69
+ devices: [device("register", REGISTER_IP, { siteId: SITE_A })],
70
+ observingSiteId: SITE_A,
71
+ });
72
+
73
+ expect(result).toEqual([
74
+ {
75
+ deviceId: "register",
76
+ macAddress: REGISTER_MAC,
77
+ previousMacAddress: null,
78
+ ipAddress: REGISTER_IP,
79
+ },
80
+ ]);
81
+ });
82
+
83
+ it("writes the MAC in normalized colon form whatever form the ARP table used", () => {
84
+ const result: Array<MacLearningPlanEntry> = plan({
85
+ ipBindings: [binding(REGISTER_IP, "AA-BB-CC-DD-EE-01")],
86
+ devices: [device("register", REGISTER_IP, { siteId: SITE_A })],
87
+ observingSiteId: SITE_A,
88
+ });
89
+
90
+ expect(result).toHaveLength(1);
91
+ expect(result[0]!.macAddress).toBe(REGISTER_MAC);
92
+ });
93
+
94
+ it("refuses a device in a different site from the router", () => {
95
+ /*
96
+ * Every branch has a 10.0.0.5. The router at site A binding that address
97
+ * says nothing about the register registered at site B.
98
+ */
99
+ const result: Array<MacLearningPlanEntry> = plan({
100
+ ipBindings: [binding(REGISTER_IP, REGISTER_MAC)],
101
+ devices: [device("register", REGISTER_IP, { siteId: SITE_B })],
102
+ observingSiteId: SITE_A,
103
+ });
104
+
105
+ expect(result).toEqual([]);
106
+ });
107
+
108
+ it("plans a device with no site when the router has no site either", () => {
109
+ // A project that never set up sites: everything shares the no-site key.
110
+ const result: Array<MacLearningPlanEntry> = plan({
111
+ ipBindings: [binding(REGISTER_IP, REGISTER_MAC)],
112
+ devices: [device("register", REGISTER_IP)],
113
+ observingSiteId: undefined,
114
+ });
115
+
116
+ expect(result).toHaveLength(1);
117
+ expect(result[0]!.deviceId).toBe("register");
118
+ });
119
+
120
+ it("refuses a device with a site when the router has none", () => {
121
+ const result: Array<MacLearningPlanEntry> = plan({
122
+ ipBindings: [binding(REGISTER_IP, REGISTER_MAC)],
123
+ devices: [device("register", REGISTER_IP, { siteId: SITE_A })],
124
+ observingSiteId: undefined,
125
+ });
126
+
127
+ expect(result).toEqual([]);
128
+ });
129
+
130
+ it("refuses a device with no site when the router has one", () => {
131
+ const result: Array<MacLearningPlanEntry> = plan({
132
+ ipBindings: [binding(REGISTER_IP, REGISTER_MAC)],
133
+ devices: [device("register", REGISTER_IP)],
134
+ observingSiteId: SITE_A,
135
+ });
136
+
137
+ expect(result).toEqual([]);
138
+ });
139
+
140
+ it("treats a null router site the same as an undefined one", () => {
141
+ const result: Array<MacLearningPlanEntry> = plan({
142
+ ipBindings: [binding(REGISTER_IP, REGISTER_MAC)],
143
+ devices: [device("register", REGISTER_IP, { siteId: "" })],
144
+ observingSiteId: null,
145
+ });
146
+
147
+ expect(result).toHaveLength(1);
148
+ });
149
+
150
+ it("refuses a device that already carries a MAC", () => {
151
+ /*
152
+ * What an operator typed is theirs; what an earlier walk learned is
153
+ * left for the topology to reconcile rather than flip-flopped between
154
+ * two routers' tables.
155
+ */
156
+ const result: Array<MacLearningPlanEntry> = plan({
157
+ ipBindings: [binding(REGISTER_IP, REGISTER_MAC)],
158
+ devices: [
159
+ device("register", REGISTER_IP, {
160
+ siteId: SITE_A,
161
+ macAddress: "00:11:22:33:44:55",
162
+ }),
163
+ ],
164
+ observingSiteId: SITE_A,
165
+ });
166
+
167
+ expect(result).toEqual([]);
168
+ });
169
+
170
+ it("reads an empty-string or whitespace MAC column as empty", () => {
171
+ const result: Array<MacLearningPlanEntry> = plan({
172
+ ipBindings: [
173
+ binding(REGISTER_IP, REGISTER_MAC),
174
+ binding(HANDSET_IP, HANDSET_MAC),
175
+ ],
176
+ devices: [
177
+ device("handset", HANDSET_IP, { siteId: SITE_A, macAddress: " " }),
178
+ device("register", REGISTER_IP, { siteId: SITE_A, macAddress: "" }),
179
+ ],
180
+ observingSiteId: SITE_A,
181
+ });
182
+
183
+ expect(
184
+ result.map((entry: MacLearningPlanEntry) => {
185
+ return entry.deviceId;
186
+ }),
187
+ ).toEqual(["handset", "register"]);
188
+ });
189
+
190
+ it("refuses an address two devices are registered at, and still plans the rest", () => {
191
+ /*
192
+ * Two devices at one address is a contradiction the planner cannot
193
+ * resolve - but it is a contradiction about THAT address only, so the
194
+ * handset at its own address is still planned in the same call.
195
+ */
196
+ const result: Array<MacLearningPlanEntry> = plan({
197
+ ipBindings: [
198
+ binding(REGISTER_IP, REGISTER_MAC),
199
+ binding(HANDSET_IP, HANDSET_MAC),
200
+ ],
201
+ devices: [
202
+ device("register-1", REGISTER_IP, { siteId: SITE_A }),
203
+ device("register-2", REGISTER_IP, { siteId: SITE_A }),
204
+ device("handset", HANDSET_IP, { siteId: SITE_A }),
205
+ ],
206
+ observingSiteId: SITE_A,
207
+ });
208
+
209
+ expect(result).toEqual([
210
+ {
211
+ deviceId: "handset",
212
+ macAddress: HANDSET_MAC,
213
+ previousMacAddress: null,
214
+ ipAddress: HANDSET_IP,
215
+ },
216
+ ]);
217
+ });
218
+
219
+ it("a second device at the address in ANOTHER site does not make the first ambiguous", () => {
220
+ // Site scoping runs before the one-device-per-address count.
221
+ const result: Array<MacLearningPlanEntry> = plan({
222
+ ipBindings: [binding(REGISTER_IP, REGISTER_MAC)],
223
+ devices: [
224
+ device("register-a", REGISTER_IP, { siteId: SITE_A }),
225
+ device("register-b", REGISTER_IP, { siteId: SITE_B }),
226
+ ],
227
+ observingSiteId: SITE_A,
228
+ });
229
+
230
+ expect(result).toHaveLength(1);
231
+ expect(result[0]!.deviceId).toBe("register-a");
232
+ });
233
+
234
+ it("refuses an address the ARP table binds to two different MACs", () => {
235
+ // Proxy ARP, or a stale duplicate: neither MAC can be trusted.
236
+ const result: Array<MacLearningPlanEntry> = plan({
237
+ ipBindings: [
238
+ binding(REGISTER_IP, REGISTER_MAC),
239
+ binding(REGISTER_IP, "aa:bb:cc:dd:ee:99"),
240
+ ],
241
+ devices: [device("register", REGISTER_IP, { siteId: SITE_A })],
242
+ observingSiteId: SITE_A,
243
+ });
244
+
245
+ expect(result).toEqual([]);
246
+ });
247
+
248
+ it("one MAC written in two spellings is still one MAC", () => {
249
+ const result: Array<MacLearningPlanEntry> = plan({
250
+ ipBindings: [
251
+ binding(REGISTER_IP, "AA:BB:CC:DD:EE:01"),
252
+ binding(REGISTER_IP, "aabb.ccdd.ee01"),
253
+ ],
254
+ devices: [device("register", REGISTER_IP, { siteId: SITE_A })],
255
+ observingSiteId: SITE_A,
256
+ });
257
+
258
+ expect(result).toHaveLength(1);
259
+ expect(result[0]!.macAddress).toBe(REGISTER_MAC);
260
+ });
261
+
262
+ it("tolerates whitespace around the device hostname and the bound address", () => {
263
+ const result: Array<MacLearningPlanEntry> = plan({
264
+ ipBindings: [binding(` ${REGISTER_IP} `, REGISTER_MAC)],
265
+ devices: [device("register", ` ${REGISTER_IP} `, { siteId: SITE_A })],
266
+ observingSiteId: SITE_A,
267
+ });
268
+
269
+ expect(result).toHaveLength(1);
270
+ expect(result[0]!.ipAddress).toBe(REGISTER_IP);
271
+ });
272
+
273
+ it("ignores a binding whose MAC cannot be parsed", () => {
274
+ const result: Array<MacLearningPlanEntry> = plan({
275
+ ipBindings: [binding(REGISTER_IP, "not-a-mac")],
276
+ devices: [device("register", REGISTER_IP, { siteId: SITE_A })],
277
+ observingSiteId: SITE_A,
278
+ });
279
+
280
+ expect(result).toEqual([]);
281
+ });
282
+
283
+ it("an unparseable MAC beside a good one does not make the address ambiguous", () => {
284
+ const result: Array<MacLearningPlanEntry> = plan({
285
+ ipBindings: [
286
+ binding(REGISTER_IP, "zz:zz:zz:zz:zz:zz"),
287
+ binding(REGISTER_IP, REGISTER_MAC),
288
+ ],
289
+ devices: [device("register", REGISTER_IP, { siteId: SITE_A })],
290
+ observingSiteId: SITE_A,
291
+ });
292
+
293
+ expect(result).toHaveLength(1);
294
+ expect(result[0]!.macAddress).toBe(REGISTER_MAC);
295
+ });
296
+
297
+ it("ignores a binding with a blank address and a device with no hostname", () => {
298
+ const result: Array<MacLearningPlanEntry> = plan({
299
+ ipBindings: [binding(" ", REGISTER_MAC), binding("", HANDSET_MAC)],
300
+ devices: [
301
+ device("nameless", undefined, { siteId: SITE_A }),
302
+ device("blank", " ", { siteId: SITE_A }),
303
+ ],
304
+ observingSiteId: SITE_A,
305
+ });
306
+
307
+ expect(result).toEqual([]);
308
+ });
309
+
310
+ it("a device at an address the ARP table never bound is not planned", () => {
311
+ const result: Array<MacLearningPlanEntry> = plan({
312
+ ipBindings: [binding(REGISTER_IP, REGISTER_MAC)],
313
+ devices: [device("handset", HANDSET_IP, { siteId: SITE_A })],
314
+ observingSiteId: SITE_A,
315
+ });
316
+
317
+ expect(result).toEqual([]);
318
+ });
319
+
320
+ it("plans nothing from empty bindings or an empty device list", () => {
321
+ expect(
322
+ plan({
323
+ ipBindings: [],
324
+ devices: [device("register", REGISTER_IP, { siteId: SITE_A })],
325
+ observingSiteId: SITE_A,
326
+ }),
327
+ ).toEqual([]);
328
+
329
+ expect(
330
+ plan({
331
+ ipBindings: [binding(REGISTER_IP, REGISTER_MAC)],
332
+ devices: [],
333
+ observingSiteId: SITE_A,
334
+ }),
335
+ ).toEqual([]);
336
+ });
337
+
338
+ it("orders the plan by device id regardless of input order", () => {
339
+ /*
340
+ * The same input must plan the same writes in the same order - the
341
+ * server util applies them one by one, and a log that reads differently
342
+ * on every walk is a log nobody can diff.
343
+ */
344
+ const bindings: Array<MacLearningIpBinding> = [
345
+ binding("10.0.0.3", "aa:bb:cc:dd:ee:03"),
346
+ binding("10.0.0.1", "aa:bb:cc:dd:ee:01"),
347
+ binding("10.0.0.2", "aa:bb:cc:dd:ee:02"),
348
+ ];
349
+
350
+ const forward: Array<MacLearningPlanEntry> = plan({
351
+ ipBindings: bindings,
352
+ devices: [
353
+ device("c", "10.0.0.3", { siteId: SITE_A }),
354
+ device("a", "10.0.0.1", { siteId: SITE_A }),
355
+ device("b", "10.0.0.2", { siteId: SITE_A }),
356
+ ],
357
+ observingSiteId: SITE_A,
358
+ });
359
+
360
+ const reversed: Array<MacLearningPlanEntry> = plan({
361
+ ipBindings: [...bindings].reverse(),
362
+ devices: [
363
+ device("b", "10.0.0.2", { siteId: SITE_A }),
364
+ device("a", "10.0.0.1", { siteId: SITE_A }),
365
+ device("c", "10.0.0.3", { siteId: SITE_A }),
366
+ ],
367
+ observingSiteId: SITE_A,
368
+ });
369
+
370
+ const ids: (entries: Array<MacLearningPlanEntry>) => Array<string> = (
371
+ entries: Array<MacLearningPlanEntry>,
372
+ ): Array<string> => {
373
+ return entries.map((entry: MacLearningPlanEntry) => {
374
+ return entry.deviceId;
375
+ });
376
+ };
377
+
378
+ expect(ids(forward)).toEqual(["a", "b", "c"]);
379
+ expect(ids(reversed)).toEqual(["a", "b", "c"]);
380
+ expect(reversed).toEqual(forward);
381
+ });
382
+
383
+ it("returns a fresh array and leaves both inputs untouched", () => {
384
+ const bindings: Array<MacLearningIpBinding> = [
385
+ binding(HANDSET_IP, "AA-BB-CC-DD-EE-02"),
386
+ binding(REGISTER_IP, REGISTER_MAC),
387
+ ];
388
+ const devices: Array<MacLearningCandidateDevice> = [
389
+ device("register", ` ${REGISTER_IP} `, { siteId: SITE_A }),
390
+ device("handset", HANDSET_IP, { siteId: SITE_A, macAddress: "" }),
391
+ ];
392
+ const bindingsBefore: Array<MacLearningIpBinding> = JSON.parse(
393
+ JSON.stringify(bindings),
394
+ );
395
+ const devicesBefore: Array<MacLearningCandidateDevice> = JSON.parse(
396
+ JSON.stringify(devices),
397
+ );
398
+
399
+ const result: Array<MacLearningPlanEntry> = plan({
400
+ ipBindings: bindings,
401
+ devices: devices,
402
+ observingSiteId: SITE_A,
403
+ });
404
+
405
+ expect(result).toHaveLength(2);
406
+ expect(result).not.toBe(bindings);
407
+ expect(result).not.toBe(devices);
408
+ // Normalization happened on the way out, never in place.
409
+ expect(bindings).toEqual(bindingsBefore);
410
+ expect(devices).toEqual(devicesBefore);
411
+ });
412
+ });
413
+
414
+ /*
415
+ * A learned value is the pass's own answer, and an ARP table is exactly
416
+ * the evidence that goes stale - so the pass may correct itself, and only
417
+ * itself. What an operator typed is never touched.
418
+ */
419
+ describe("DeviceMacLearningUtil.planMacLearning — correcting a learned MAC", () => {
420
+ const STALE_MAC: string = "de:ad:be:ef:00:01";
421
+
422
+ it("corrects a learned MAC the table now contradicts, compare-and-set on the old value", () => {
423
+ const result: Array<MacLearningPlanEntry> = plan({
424
+ ipBindings: [binding(REGISTER_IP, REGISTER_MAC)],
425
+ devices: [
426
+ device("register", REGISTER_IP, {
427
+ siteId: SITE_A,
428
+ macAddress: STALE_MAC,
429
+ isMacAddressLearned: true,
430
+ }),
431
+ ],
432
+ observingSiteId: SITE_A,
433
+ });
434
+
435
+ expect(result).toEqual([
436
+ {
437
+ deviceId: "register",
438
+ macAddress: REGISTER_MAC,
439
+ previousMacAddress: STALE_MAC,
440
+ ipAddress: REGISTER_IP,
441
+ },
442
+ ]);
443
+ });
444
+
445
+ it("leaves a typed MAC alone however much the table disagrees", () => {
446
+ const result: Array<MacLearningPlanEntry> = plan({
447
+ ipBindings: [binding(REGISTER_IP, REGISTER_MAC)],
448
+ devices: [
449
+ device("register", REGISTER_IP, {
450
+ siteId: SITE_A,
451
+ macAddress: STALE_MAC,
452
+ isMacAddressLearned: false,
453
+ }),
454
+ device("handset", "10.0.0.6", {
455
+ siteId: SITE_A,
456
+ macAddress: STALE_MAC,
457
+ }),
458
+ ],
459
+ observingSiteId: SITE_A,
460
+ });
461
+
462
+ expect(result).toEqual([]);
463
+ });
464
+
465
+ it("plans nothing for a learned MAC the table still agrees with, in any spelling", () => {
466
+ const result: Array<MacLearningPlanEntry> = plan({
467
+ ipBindings: [binding(REGISTER_IP, REGISTER_MAC)],
468
+ devices: [
469
+ device("register", REGISTER_IP, {
470
+ siteId: SITE_A,
471
+ macAddress: REGISTER_MAC.toUpperCase().replace(/:/g, "-"),
472
+ isMacAddressLearned: true,
473
+ }),
474
+ ],
475
+ observingSiteId: SITE_A,
476
+ });
477
+
478
+ expect(result).toEqual([]);
479
+ });
480
+
481
+ it("hands the writer the stored value verbatim, so its compare-and-set matches the column", () => {
482
+ const storedSpelling: string = "DE-AD-BE-EF-00-01";
483
+ const result: Array<MacLearningPlanEntry> = plan({
484
+ ipBindings: [binding(REGISTER_IP, REGISTER_MAC)],
485
+ devices: [
486
+ device("register", REGISTER_IP, {
487
+ siteId: SITE_A,
488
+ macAddress: storedSpelling,
489
+ isMacAddressLearned: true,
490
+ }),
491
+ ],
492
+ observingSiteId: SITE_A,
493
+ });
494
+
495
+ expect(result[0]?.previousMacAddress).toBe(storedSpelling);
496
+ });
497
+ });