@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,520 @@
1
+ import NetworkDeviceDiscoveryScanService from "../../../Server/Services/NetworkDeviceDiscoveryScanService";
2
+ import NetworkDeviceService from "../../../Server/Services/NetworkDeviceService";
3
+ import NetworkDeviceDiscoveryScan, {
4
+ DiscoveredNetworkDevice,
5
+ } from "../../../Models/DatabaseModels/NetworkDeviceDiscoveryScan";
6
+ import NetworkDevice from "../../../Models/DatabaseModels/NetworkDevice";
7
+ import { OnFind } from "../../../Server/Types/Database/Hooks";
8
+ import FindBy from "../../../Server/Types/Database/FindBy";
9
+ import DatabaseCommonInteractionProps from "../../../Types/BaseDatabase/DatabaseCommonInteractionProps";
10
+ import ObjectID from "../../../Types/ObjectID";
11
+ import { FindOperator } from "typeorm";
12
+ import { afterEach, beforeEach, describe, expect, it } from "@jest/globals";
13
+
14
+ const PROJECT_ID: ObjectID = new ObjectID(
15
+ "11111111-1111-4111-8111-111111111111",
16
+ );
17
+ const OTHER_PROJECT_ID: ObjectID = new ObjectID(
18
+ "22222222-2222-4222-8222-222222222222",
19
+ );
20
+
21
+ interface FindSuccessHook {
22
+ onFindSuccess(
23
+ onFind: OnFind<NetworkDeviceDiscoveryScan>,
24
+ items: Array<NetworkDeviceDiscoveryScan>,
25
+ ): Promise<OnFind<NetworkDeviceDiscoveryScan>>;
26
+ }
27
+
28
+ function context(
29
+ props: DatabaseCommonInteractionProps = { isRoot: true },
30
+ ): OnFind<NetworkDeviceDiscoveryScan> {
31
+ return {
32
+ findBy: {
33
+ query: {},
34
+ select: { discoveredDevices: true },
35
+ skip: 0,
36
+ limit: 100,
37
+ props,
38
+ },
39
+ carryForward: undefined,
40
+ };
41
+ }
42
+
43
+ function host(
44
+ ipAddress: string,
45
+ isAlreadyRegistered?: boolean,
46
+ ): DiscoveredNetworkDevice {
47
+ return { ipAddress, isAlreadyRegistered };
48
+ }
49
+
50
+ // JSONB can contain older or malformed payloads, so accept unknown here.
51
+ function scanWith(
52
+ discoveredDevices: unknown,
53
+ projectId: ObjectID | undefined = PROJECT_ID,
54
+ ): NetworkDeviceDiscoveryScan {
55
+ const scan: NetworkDeviceDiscoveryScan = new NetworkDeviceDiscoveryScan();
56
+ scan.projectId = projectId;
57
+ (scan as unknown as Record<string, unknown>)["discoveredDevices"] =
58
+ discoveredDevices;
59
+ return scan;
60
+ }
61
+
62
+ async function readScans(
63
+ items: Array<NetworkDeviceDiscoveryScan>,
64
+ onFind: OnFind<NetworkDeviceDiscoveryScan> = context(),
65
+ ): Promise<Array<NetworkDeviceDiscoveryScan>> {
66
+ const result: OnFind<NetworkDeviceDiscoveryScan> = await (
67
+ NetworkDeviceDiscoveryScanService as unknown as FindSuccessHook
68
+ ).onFindSuccess(onFind, items);
69
+
70
+ expect(result.findBy).toBe(onFind.findBy);
71
+ return result.carryForward as Array<NetworkDeviceDiscoveryScan>;
72
+ }
73
+
74
+ function discoveredAt(
75
+ scans: Array<NetworkDeviceDiscoveryScan>,
76
+ scanIndex: number = 0,
77
+ ): Array<DiscoveredNetworkDevice> {
78
+ return scans[scanIndex]!.discoveredDevices!;
79
+ }
80
+
81
+ afterEach(() => {
82
+ jest.restoreAllMocks();
83
+ });
84
+
85
+ describe("NetworkDeviceDiscoveryScanService current registration on read", () => {
86
+ let registrationSpy: jest.SpyInstance;
87
+ let updateSpy: jest.SpyInstance;
88
+
89
+ beforeEach(() => {
90
+ registrationSpy = jest
91
+ .spyOn(NetworkDeviceService, "getRegisteredHostnames")
92
+ .mockResolvedValue(new Set<string>());
93
+ updateSpy = jest
94
+ .spyOn(NetworkDeviceDiscoveryScanService, "updateBy")
95
+ .mockResolvedValue(0);
96
+ });
97
+
98
+ it("clears a stored registered flag when the device has been deleted", async () => {
99
+ const result: Array<NetworkDeviceDiscoveryScan> = await readScans([
100
+ scanWith([host("10.0.0.5", true)]),
101
+ ]);
102
+
103
+ expect(discoveredAt(result)).toEqual([host("10.0.0.5", false)]);
104
+ expect(registrationSpy).toHaveBeenCalledWith({
105
+ projectId: PROJECT_ID,
106
+ hostnames: ["10.0.0.5"],
107
+ props: { isRoot: true },
108
+ });
109
+ });
110
+
111
+ it("marks a stored unregistered host registered after an import", async () => {
112
+ registrationSpy.mockResolvedValue(new Set(["10.0.0.5"]));
113
+
114
+ const result: Array<NetworkDeviceDiscoveryScan> = await readScans([
115
+ scanWith([host("10.0.0.5", false)]),
116
+ ]);
117
+
118
+ expect(discoveredAt(result)).toEqual([host("10.0.0.5", true)]);
119
+ });
120
+
121
+ it("derives registration for results written before the flag existed", async () => {
122
+ registrationSpy.mockResolvedValue(new Set(["10.0.0.5"]));
123
+
124
+ const result: Array<NetworkDeviceDiscoveryScan> = await readScans([
125
+ scanWith([{ ipAddress: "10.0.0.5" }, { ipAddress: "10.0.0.6" }]),
126
+ ]);
127
+
128
+ expect(discoveredAt(result)).toEqual([
129
+ host("10.0.0.5", true),
130
+ host("10.0.0.6", false),
131
+ ]);
132
+ });
133
+
134
+ it("keeps both registered and unregistered hosts accurate in the same result", async () => {
135
+ registrationSpy.mockResolvedValue(new Set(["10.0.0.5", "10.0.0.7"]));
136
+
137
+ const result: Array<NetworkDeviceDiscoveryScan> = await readScans([
138
+ scanWith([
139
+ host("10.0.0.5", true),
140
+ host("10.0.0.6", true),
141
+ host("10.0.0.7", false),
142
+ host("10.0.0.8", false),
143
+ ]),
144
+ ]);
145
+
146
+ expect(discoveredAt(result)).toEqual([
147
+ host("10.0.0.5", true),
148
+ host("10.0.0.6", false),
149
+ host("10.0.0.7", true),
150
+ host("10.0.0.8", false),
151
+ ]);
152
+ });
153
+
154
+ it("rechecks inventory on every read across import, deletion and reimport", async () => {
155
+ const saved: NetworkDeviceDiscoveryScan = scanWith([
156
+ host("10.0.0.5", true),
157
+ ]);
158
+ registrationSpy
159
+ .mockResolvedValueOnce(new Set(["10.0.0.5"]))
160
+ .mockResolvedValueOnce(new Set<string>())
161
+ .mockResolvedValueOnce(new Set(["10.0.0.5"]));
162
+
163
+ expect(discoveredAt(await readScans([saved]))[0]!.isAlreadyRegistered).toBe(
164
+ true,
165
+ );
166
+ expect(discoveredAt(await readScans([saved]))[0]!.isAlreadyRegistered).toBe(
167
+ false,
168
+ );
169
+ expect(discoveredAt(await readScans([saved]))[0]!.isAlreadyRegistered).toBe(
170
+ true,
171
+ );
172
+ expect(registrationSpy).toHaveBeenCalledTimes(3);
173
+ expect(saved.discoveredDevices).toEqual([host("10.0.0.5", true)]);
174
+ expect(updateSpy).not.toHaveBeenCalled();
175
+ });
176
+
177
+ it("copies results without changing persisted scan data or model methods", async () => {
178
+ const originalHost: DiscoveredNetworkDevice = {
179
+ ipAddress: "10.0.0.5",
180
+ isAlreadyRegistered: true,
181
+ sysName: "Core switch",
182
+ sysDescr: "A managed switch",
183
+ sysObjectId: "1.3.6.1.4.1.9",
184
+ sysLocation: "Rack 2",
185
+ sysContact: "Network team",
186
+ sysUpTimeSeconds: 1234,
187
+ dnsHostname: "switch.example.test",
188
+ snmpReachable: true,
189
+ snmpConfigId: "office-snmp",
190
+ };
191
+ const originalHosts: Array<DiscoveredNetworkDevice> = [originalHost];
192
+ const saved: NetworkDeviceDiscoveryScan = scanWith(originalHosts);
193
+ saved.name = "Office discovery";
194
+ saved.status = "Completed";
195
+ saved.completedAt = new Date("2026-09-01T10:00:00Z");
196
+ saved.scannedHostCount = 256;
197
+ saved.respondedHostCount = 1;
198
+ Object.freeze(originalHost);
199
+ Object.freeze(originalHosts);
200
+ Object.freeze(saved);
201
+ const originalItems: Array<NetworkDeviceDiscoveryScan> = [saved];
202
+ Object.freeze(originalItems);
203
+
204
+ const result: Array<NetworkDeviceDiscoveryScan> =
205
+ await readScans(originalItems);
206
+
207
+ expect(result).not.toBe(originalItems);
208
+ expect(result[0]).not.toBe(saved);
209
+ expect(result[0]).toBeInstanceOf(NetworkDeviceDiscoveryScan);
210
+ expect(Object.getPrototypeOf(result[0])).toBe(Object.getPrototypeOf(saved));
211
+ expect(result[0]).toEqual({
212
+ ...saved,
213
+ discoveredDevices: [{ ...originalHost, isAlreadyRegistered: false }],
214
+ });
215
+ expect(discoveredAt(result)).not.toBe(originalHosts);
216
+ expect(discoveredAt(result)[0]).not.toBe(originalHost);
217
+ expect(originalHost.isAlreadyRegistered).toBe(true);
218
+ expect(updateSpy).not.toHaveBeenCalled();
219
+ });
220
+
221
+ it("batches and deduplicates all hosts for the same project across scans", async () => {
222
+ registrationSpy.mockResolvedValue(new Set(["10.0.0.6"]));
223
+ const result: Array<NetworkDeviceDiscoveryScan> = await readScans([
224
+ scanWith([host("10.0.0.5"), host("10.0.0.5"), host("10.0.0.6")]),
225
+ scanWith(
226
+ [host("10.0.0.6"), host("10.0.0.7")],
227
+ new ObjectID(PROJECT_ID.toString()),
228
+ ),
229
+ ]);
230
+
231
+ expect(registrationSpy).toHaveBeenCalledTimes(1);
232
+ expect(registrationSpy).toHaveBeenCalledWith({
233
+ projectId: PROJECT_ID,
234
+ hostnames: ["10.0.0.5", "10.0.0.6", "10.0.0.7"],
235
+ props: { isRoot: true },
236
+ });
237
+ expect(discoveredAt(result, 0)).toEqual([
238
+ host("10.0.0.5", false),
239
+ host("10.0.0.5", false),
240
+ host("10.0.0.6", true),
241
+ ]);
242
+ expect(discoveredAt(result, 1)).toEqual([
243
+ host("10.0.0.6", true),
244
+ host("10.0.0.7", false),
245
+ ]);
246
+ });
247
+
248
+ it("keeps the same address independent between projects", async () => {
249
+ registrationSpy.mockImplementation(
250
+ async (data: { projectId: ObjectID }): Promise<Set<string>> => {
251
+ return data.projectId.toString() === PROJECT_ID.toString()
252
+ ? new Set(["10.0.0.5"])
253
+ : new Set<string>();
254
+ },
255
+ );
256
+
257
+ const result: Array<NetworkDeviceDiscoveryScan> = await readScans([
258
+ scanWith([host("10.0.0.5", false)], PROJECT_ID),
259
+ scanWith([host("10.0.0.5", true)], OTHER_PROJECT_ID),
260
+ ]);
261
+
262
+ expect(registrationSpy).toHaveBeenCalledTimes(2);
263
+ expect(registrationSpy).toHaveBeenCalledWith({
264
+ projectId: OTHER_PROJECT_ID,
265
+ hostnames: ["10.0.0.5"],
266
+ props: { isRoot: true },
267
+ });
268
+ expect(discoveredAt(result, 0)[0]!.isAlreadyRegistered).toBe(true);
269
+ expect(discoveredAt(result, 1)[0]!.isAlreadyRegistered).toBe(false);
270
+ });
271
+
272
+ it("uses the request tenant when projectId was not selected", async () => {
273
+ const saved: NetworkDeviceDiscoveryScan = scanWith([
274
+ host("10.0.0.5", true),
275
+ ]);
276
+ delete saved.projectId;
277
+ const result: Array<NetworkDeviceDiscoveryScan> = await readScans(
278
+ [saved],
279
+ context({ tenantId: PROJECT_ID }),
280
+ );
281
+
282
+ expect(discoveredAt(result)[0]!.isAlreadyRegistered).toBe(false);
283
+ expect(registrationSpy).toHaveBeenCalledWith({
284
+ projectId: PROJECT_ID,
285
+ hostnames: ["10.0.0.5"],
286
+ props: { isRoot: true },
287
+ });
288
+ expect(result[0]!.projectId).toBeUndefined();
289
+ });
290
+
291
+ it("uses the scan project before the request tenant", async () => {
292
+ await readScans(
293
+ [scanWith([host("10.0.0.5", true)], OTHER_PROJECT_ID)],
294
+ context({ tenantId: PROJECT_ID }),
295
+ );
296
+
297
+ expect(registrationSpy).toHaveBeenCalledTimes(1);
298
+ expect(registrationSpy).toHaveBeenCalledWith({
299
+ projectId: OTHER_PROJECT_ID,
300
+ hostnames: ["10.0.0.5"],
301
+ props: { isRoot: true },
302
+ });
303
+ });
304
+
305
+ it("leaves results unchanged when neither project nor tenant is available", async () => {
306
+ const saved: NetworkDeviceDiscoveryScan = scanWith([
307
+ host("10.0.0.5", true),
308
+ ]);
309
+ delete saved.projectId;
310
+
311
+ const result: Array<NetworkDeviceDiscoveryScan> = await readScans([saved]);
312
+
313
+ expect(result[0]).toBe(saved);
314
+ expect(registrationSpy).not.toHaveBeenCalled();
315
+ });
316
+
317
+ it("normalizes whitespace for lookup without rewriting the stored address", async () => {
318
+ registrationSpy.mockResolvedValue(new Set(["10.0.0.5"]));
319
+
320
+ const result: Array<NetworkDeviceDiscoveryScan> = await readScans([
321
+ scanWith([host(" 10.0.0.5 \t", false), host("10.0.0.5", false)]),
322
+ ]);
323
+
324
+ expect(registrationSpy).toHaveBeenCalledWith({
325
+ projectId: PROJECT_ID,
326
+ hostnames: ["10.0.0.5"],
327
+ props: { isRoot: true },
328
+ });
329
+ expect(discoveredAt(result)).toEqual([
330
+ host(" 10.0.0.5 \t", true),
331
+ host("10.0.0.5", true),
332
+ ]);
333
+ });
334
+
335
+ it.each([
336
+ ["omitted", undefined],
337
+ ["null", null],
338
+ ["empty", []],
339
+ ["object", { ipAddress: "10.0.0.5" }],
340
+ ["string", "10.0.0.5"],
341
+ ["number", 42],
342
+ ])(
343
+ "does not query inventory for %s results",
344
+ async (_label: string, payload: unknown) => {
345
+ const saved: NetworkDeviceDiscoveryScan = scanWith(payload);
346
+
347
+ const result: Array<NetworkDeviceDiscoveryScan> = await readScans([
348
+ saved,
349
+ ]);
350
+
351
+ expect(result[0]).toBe(saved);
352
+ expect(registrationSpy).not.toHaveBeenCalled();
353
+ },
354
+ );
355
+
356
+ it("does not query inventory for an empty scan page", async () => {
357
+ expect(await readScans([])).toEqual([]);
358
+ expect(registrationSpy).not.toHaveBeenCalled();
359
+ });
360
+
361
+ it("does not query inventory for only malformed host entries", async () => {
362
+ const junk: Array<unknown> = [
363
+ null,
364
+ false,
365
+ 42,
366
+ "10.0.0.5",
367
+ [],
368
+ {},
369
+ { ipAddress: null },
370
+ { ipAddress: 10 },
371
+ { ipAddress: "", isAlreadyRegistered: true },
372
+ { ipAddress: " \t\n ", isAlreadyRegistered: true },
373
+ ];
374
+ const saved: NetworkDeviceDiscoveryScan = scanWith(junk);
375
+
376
+ const result: Array<NetworkDeviceDiscoveryScan> = await readScans([saved]);
377
+
378
+ expect(result[0]).toBe(saved);
379
+ expect(result[0]!.discoveredDevices).toBe(junk);
380
+ expect(registrationSpy).not.toHaveBeenCalled();
381
+ });
382
+
383
+ it("preserves malformed entries among valid hosts", async () => {
384
+ const junkObject: Record<string, unknown> = {
385
+ ipAddress: 10,
386
+ isAlreadyRegistered: true,
387
+ };
388
+ const result: Array<NetworkDeviceDiscoveryScan> = await readScans([
389
+ scanWith([null, junkObject, host("10.0.0.5", true), "invalid"]),
390
+ ]);
391
+
392
+ expect(discoveredAt(result)).toEqual([
393
+ null,
394
+ junkObject,
395
+ host("10.0.0.5", false),
396
+ "invalid",
397
+ ]);
398
+ expect(discoveredAt(result)[1]).toBe(junkObject);
399
+ expect(registrationSpy).toHaveBeenCalledWith({
400
+ projectId: PROJECT_ID,
401
+ hostnames: ["10.0.0.5"],
402
+ props: { isRoot: true },
403
+ });
404
+ });
405
+
406
+ it("preserves unselected rows beside rows that need registration refresh", async () => {
407
+ const unselected: NetworkDeviceDiscoveryScan = scanWith(undefined);
408
+ const result: Array<NetworkDeviceDiscoveryScan> = await readScans([
409
+ unselected,
410
+ scanWith([host("10.0.0.5", true)]),
411
+ ]);
412
+
413
+ expect(result[0]).toBe(unselected);
414
+ expect(discoveredAt(result, 1)[0]!.isAlreadyRegistered).toBe(false);
415
+ expect(registrationSpy).toHaveBeenCalledTimes(1);
416
+ });
417
+
418
+ it("propagates lookup failure instead of reporting all hosts as importable", async () => {
419
+ const error: Error = new Error("inventory lookup unavailable");
420
+ registrationSpy.mockRejectedValue(error);
421
+ const saved: NetworkDeviceDiscoveryScan = scanWith([
422
+ host("10.0.0.5", true),
423
+ ]);
424
+
425
+ await expect(readScans([saved])).rejects.toBe(error);
426
+
427
+ expect(saved.discoveredDevices).toEqual([host("10.0.0.5", true)]);
428
+ expect(updateSpy).not.toHaveBeenCalled();
429
+ });
430
+ });
431
+
432
+ describe("discovery registration with the inventory lookup service", () => {
433
+ it("keeps a live device registered after its monitor has been deleted", async () => {
434
+ const device: NetworkDevice = new NetworkDevice();
435
+ device.hostname = "10.0.0.5";
436
+ delete device.monitorId;
437
+ const findSpy: jest.SpyInstance = jest
438
+ .spyOn(NetworkDeviceService, "findBy")
439
+ .mockResolvedValue([device]);
440
+
441
+ const result: Array<NetworkDeviceDiscoveryScan> = await readScans([
442
+ scanWith([host("10.0.0.5", false)]),
443
+ ]);
444
+
445
+ expect(discoveredAt(result)[0]!.isAlreadyRegistered).toBe(true);
446
+ expect(findSpy).toHaveBeenCalledTimes(1);
447
+ expect(findSpy.mock.calls[0]![0].query).toEqual({
448
+ projectId: PROJECT_ID,
449
+ hostname: expect.any(FindOperator),
450
+ });
451
+ });
452
+
453
+ it("refreshes more than 900 hosts through bounded inventory lookup chunks", async () => {
454
+ const hosts: Array<DiscoveredNetworkDevice> = Array.from(
455
+ { length: 1001 },
456
+ (_value: unknown, index: number): DiscoveredNetworkDevice => {
457
+ return host(`10.99.${Math.floor(index / 256)}.${index % 256}`, true);
458
+ },
459
+ );
460
+ const liveHostnames: Set<string> = new Set([
461
+ hosts[0]!.ipAddress,
462
+ hosts[500]!.ipAddress,
463
+ hosts[1000]!.ipAddress,
464
+ ]);
465
+ const requestedChunks: Array<Array<string>> = [];
466
+ const findSpy: jest.SpyInstance = jest
467
+ .spyOn(NetworkDeviceService, "findBy")
468
+ .mockImplementation(
469
+ async (
470
+ findBy: FindBy<NetworkDevice>,
471
+ ): Promise<Array<NetworkDevice>> => {
472
+ expect(findBy.query.projectId).toEqual(PROJECT_ID);
473
+ expect(findBy.props).toEqual({ isRoot: true });
474
+ const operator: FindOperator<string> = findBy.query
475
+ .hostname as unknown as FindOperator<string>;
476
+ const parameters: Record<string, unknown> =
477
+ operator.objectLiteralParameters || {};
478
+ const requested: Array<string> = Object.values(
479
+ parameters,
480
+ )[0] as Array<string>;
481
+ requestedChunks.push(requested);
482
+
483
+ return requested
484
+ .filter((hostname: string): boolean => {
485
+ return liveHostnames.has(hostname);
486
+ })
487
+ .map((hostname: string): NetworkDevice => {
488
+ const device: NetworkDevice = new NetworkDevice();
489
+ device.hostname = hostname;
490
+ return device;
491
+ });
492
+ },
493
+ );
494
+
495
+ const result: Array<NetworkDeviceDiscoveryScan> = await readScans([
496
+ scanWith(hosts),
497
+ ]);
498
+
499
+ expect(findSpy).toHaveBeenCalledTimes(3);
500
+ expect(
501
+ requestedChunks.map((chunk: Array<string>): number => {
502
+ return chunk.length;
503
+ }),
504
+ ).toEqual([500, 500, 1]);
505
+ expect(requestedChunks.flat()).toEqual(
506
+ hosts.map((item: DiscoveredNetworkDevice): string => {
507
+ return item.ipAddress;
508
+ }),
509
+ );
510
+ expect(discoveredAt(result)).toHaveLength(1001);
511
+ for (const item of discoveredAt(result)) {
512
+ expect(item.isAlreadyRegistered).toBe(liveHostnames.has(item.ipAddress));
513
+ }
514
+ expect(
515
+ hosts.every((item: DiscoveredNetworkDevice): boolean => {
516
+ return item.isAlreadyRegistered === true;
517
+ }),
518
+ ).toBe(true);
519
+ });
520
+ });