@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,316 @@
1
+ import InventoryItem, {
2
+ EntityAttributes,
3
+ ExtractedEntity,
4
+ ResourceEntityRef,
5
+ } from "../../../../Server/Utils/Telemetry/TelemetryEntity";
6
+ import EntityType from "../../../../Types/Telemetry/EntityType";
7
+ import { describe, expect, test } from "@jest/globals";
8
+
9
+ /*
10
+ * Regression suite for duplicate Inventory items.
11
+ *
12
+ * `InventoryItem` has a unique index on (projectId, entityType, entityKey),
13
+ * so two rows of the same type in one project can only mean one thing: the
14
+ * same real object hashed to two different keys. Nothing downstream can undo
15
+ * that — the two rows are, as far as the registry is concerned, two things —
16
+ * and because both keep being observed, neither ever ages out. The Inventory
17
+ * list then shows a node, pod or service twice, both marked Discovered, both
18
+ * with the same name, because the display name is derived from the attributes
19
+ * the two identities SHARE rather than the ones that differ.
20
+ *
21
+ * The rule these tests exist to hold: an entity's identity must depend only on
22
+ * WHICH OBJECT is being described, never on WHICH PIPELINE is describing it.
23
+ * Every case below is a pair of resources that our own shipped configuration
24
+ * produces for one object, differing only in what the producing collector was
25
+ * able to resolve.
26
+ */
27
+
28
+ const PROJECT: string = "proj-dup-1";
29
+
30
+ function entitiesFor(
31
+ attrs: EntityAttributes,
32
+ entityRefs?: Array<ResourceEntityRef>,
33
+ ): Array<ExtractedEntity> {
34
+ return InventoryItem.extractEntities({
35
+ projectId: PROJECT,
36
+ attributes: attrs,
37
+ ...(entityRefs ? { entityRefs } : {}),
38
+ });
39
+ }
40
+
41
+ function entityOfType(
42
+ attrs: EntityAttributes,
43
+ type: EntityType,
44
+ ): ExtractedEntity | undefined {
45
+ return entitiesFor(attrs).find((e: ExtractedEntity) => {
46
+ return e.entityType === type;
47
+ });
48
+ }
49
+
50
+ function keyOfType(attrs: EntityAttributes, type: EntityType): string {
51
+ const entity: ExtractedEntity | undefined = entityOfType(attrs, type);
52
+ expect(entity).toBeDefined();
53
+ return entity!.entityKey;
54
+ }
55
+
56
+ const CLUSTER: string = "prod-us";
57
+
58
+ /*
59
+ * The two collectors the `kubernetes-agent` chart installs, reduced to the
60
+ * resource attributes each actually carries for the SAME node.
61
+ *
62
+ * Deployment / metrics-collector: the `k8s_cluster` receiver reports node
63
+ * metrics (`k8s.node.condition_ready`, allocatable cpu/memory/storage) and
64
+ * resolves node uids from the Kubernetes API.
65
+ *
66
+ * DaemonSet / node-collector: kubeletstats, hostmetrics, the cAdvisor scrape
67
+ * and the pod filelog receiver. None of them can produce a node uid — the
68
+ * kubelet summary API has no such field and `k8sattributes` is not asked to
69
+ * extract one — so the chart stamps `k8s.node.name` from the downward API
70
+ * instead.
71
+ */
72
+ const NODE_FROM_K8S_CLUSTER_RECEIVER: EntityAttributes = {
73
+ "k8s.cluster.name": CLUSTER,
74
+ "k8s.node.name": "ip-10-0-1-23.ec2.internal",
75
+ "k8s.node.uid": "a8170da5-c826-49b0-b48b-a35af366e689",
76
+ "service.name": `kubernetes-agent-${CLUSTER}`,
77
+ };
78
+
79
+ const NODE_FROM_DAEMONSET: EntityAttributes = {
80
+ "k8s.cluster.name": CLUSTER,
81
+ "k8s.node.name": "ip-10-0-1-23.ec2.internal",
82
+ "service.name": `kubernetes-agent-${CLUSTER}`,
83
+ };
84
+
85
+ /*
86
+ * The same pod as seen by the deployment collector (k8sattributes resolves
87
+ * `k8s.pod.uid`) and by a pod-log record that only carries the pod name.
88
+ */
89
+ const POD_WITH_UID: EntityAttributes = {
90
+ "k8s.cluster.name": CLUSTER,
91
+ "k8s.namespace.name": "shop",
92
+ "k8s.pod.name": "checkout-api-7d9f884c7d-q5bz2",
93
+ "k8s.pod.uid": "7c2f9dd0-8400-4c47-9c54-321ebeb9543b",
94
+ };
95
+
96
+ const POD_WITHOUT_UID: EntityAttributes = {
97
+ "k8s.cluster.name": CLUSTER,
98
+ "k8s.namespace.name": "shop",
99
+ "k8s.pod.name": "checkout-api-7d9f884c7d-q5bz2",
100
+ };
101
+
102
+ describe("one object, one identity, whichever collector saw it", () => {
103
+ test("a node reported with a uid and the same node reported without one are one entity", () => {
104
+ expect(
105
+ keyOfType(NODE_FROM_K8S_CLUSTER_RECEIVER, EntityType.KubernetesNode),
106
+ ).toBe(keyOfType(NODE_FROM_DAEMONSET, EntityType.KubernetesNode));
107
+ });
108
+
109
+ test("a pod reported with a uid and the same pod reported without one are one entity", () => {
110
+ expect(keyOfType(POD_WITH_UID, EntityType.KubernetesPod)).toBe(
111
+ keyOfType(POD_WITHOUT_UID, EntityType.KubernetesPod),
112
+ );
113
+ });
114
+
115
+ test("node identity is the cluster plus the node name, and nothing else", () => {
116
+ expect(
117
+ entityOfType(NODE_FROM_K8S_CLUSTER_RECEIVER, EntityType.KubernetesNode)!
118
+ .identifyingAttributes,
119
+ ).toEqual({
120
+ "k8s.cluster.name": CLUSTER,
121
+ "k8s.node.name": "ip-10-0-1-23.ec2.internal",
122
+ });
123
+ });
124
+
125
+ test("pod identity is the cluster, namespace and pod name, and nothing else", () => {
126
+ expect(
127
+ entityOfType(POD_WITH_UID, EntityType.KubernetesPod)!
128
+ .identifyingAttributes,
129
+ ).toEqual({
130
+ "k8s.cluster.name": CLUSTER,
131
+ "k8s.namespace.name": "shop",
132
+ "k8s.pod.name": "checkout-api-7d9f884c7d-q5bz2",
133
+ });
134
+ });
135
+
136
+ test("the uid is still recorded, just not as identity", () => {
137
+ expect(
138
+ entityOfType(NODE_FROM_K8S_CLUSTER_RECEIVER, EntityType.KubernetesNode)!
139
+ .descriptiveAttributes,
140
+ ).toMatchObject({
141
+ "k8s.node.uid": "a8170da5-c826-49b0-b48b-a35af366e689",
142
+ });
143
+ expect(
144
+ entityOfType(POD_WITH_UID, EntityType.KubernetesPod)!
145
+ .descriptiveAttributes,
146
+ ).toMatchObject({
147
+ "k8s.pod.uid": "7c2f9dd0-8400-4c47-9c54-321ebeb9543b",
148
+ });
149
+ });
150
+
151
+ test("a pod that keeps its name across a restart keeps its inventory row", () => {
152
+ /*
153
+ * A StatefulSet pod is recreated under the same name with a fresh uid.
154
+ * Under uid identity that minted a new registry row per incarnation; the
155
+ * name is what an inventory is actually tracking.
156
+ */
157
+ const before: string = keyOfType(POD_WITH_UID, EntityType.KubernetesPod);
158
+ const after: string = keyOfType(
159
+ {
160
+ ...POD_WITH_UID,
161
+ "k8s.pod.uid": "0000aaaa-1111-2222-3333-444455556666",
162
+ },
163
+ EntityType.KubernetesPod,
164
+ );
165
+
166
+ expect(after).toBe(before);
167
+ });
168
+ });
169
+
170
+ describe("identity that genuinely differs must stay different", () => {
171
+ test("the same node name in two clusters is two entities", () => {
172
+ expect(keyOfType(NODE_FROM_DAEMONSET, EntityType.KubernetesNode)).not.toBe(
173
+ keyOfType(
174
+ { ...NODE_FROM_DAEMONSET, "k8s.cluster.name": "prod-eu" },
175
+ EntityType.KubernetesNode,
176
+ ),
177
+ );
178
+ });
179
+
180
+ test("the same pod name in two namespaces is two entities", () => {
181
+ expect(keyOfType(POD_WITHOUT_UID, EntityType.KubernetesPod)).not.toBe(
182
+ keyOfType(
183
+ { ...POD_WITHOUT_UID, "k8s.namespace.name": "checkout" },
184
+ EntityType.KubernetesPod,
185
+ ),
186
+ );
187
+ });
188
+
189
+ test("the same pod name in two clusters is two entities", () => {
190
+ expect(keyOfType(POD_WITHOUT_UID, EntityType.KubernetesPod)).not.toBe(
191
+ keyOfType(
192
+ { ...POD_WITHOUT_UID, "k8s.cluster.name": "prod-eu" },
193
+ EntityType.KubernetesPod,
194
+ ),
195
+ );
196
+ });
197
+
198
+ test("two different nodes in one cluster are two entities", () => {
199
+ expect(keyOfType(NODE_FROM_DAEMONSET, EntityType.KubernetesNode)).not.toBe(
200
+ keyOfType(
201
+ {
202
+ ...NODE_FROM_DAEMONSET,
203
+ "k8s.node.name": "ip-10-0-1-24.ec2.internal",
204
+ },
205
+ EntityType.KubernetesNode,
206
+ ),
207
+ );
208
+ });
209
+
210
+ test("a uid-only node and a named node are not silently merged", () => {
211
+ /*
212
+ * Nothing can prove these are the same object from the resource alone, so
213
+ * they stay distinct. The uid-only shape exists only for a producer that
214
+ * carries no name at all.
215
+ */
216
+ expect(
217
+ keyOfType(
218
+ { "k8s.cluster.name": CLUSTER, "k8s.node.uid": "node-uid-only" },
219
+ EntityType.KubernetesNode,
220
+ ),
221
+ ).not.toBe(keyOfType(NODE_FROM_DAEMONSET, EntityType.KubernetesNode));
222
+ });
223
+ });
224
+
225
+ describe("casing and whitespace do not fork identity", () => {
226
+ test.each([
227
+ ["upper-cased node name", { "k8s.node.name": "IP-10-0-1-23.EC2.INTERNAL" }],
228
+ ["padded node name", { "k8s.node.name": " ip-10-0-1-23.ec2.internal " }],
229
+ ["upper-cased cluster", { "k8s.cluster.name": "PROD-US" }],
230
+ ])(
231
+ "%s resolves to the same node entity",
232
+ (_label: string, override: EntityAttributes) => {
233
+ expect(
234
+ keyOfType(
235
+ { ...NODE_FROM_DAEMONSET, ...override },
236
+ EntityType.KubernetesNode,
237
+ ),
238
+ ).toBe(keyOfType(NODE_FROM_DAEMONSET, EntityType.KubernetesNode));
239
+ },
240
+ );
241
+ });
242
+
243
+ describe("one resource describes at most one entity per type", () => {
244
+ /*
245
+ * A resource is one pod, on one node, in one cluster. `entity_refs` are
246
+ * producer-supplied and nothing stops a producer sending two refs of the
247
+ * same type on one resource; promoting both put two Inventory rows in from
248
+ * a single batch. The extra ref stays a membership key so no signal loses
249
+ * its queryable identity.
250
+ */
251
+ const TWO_POD_REFS: Array<ResourceEntityRef> = [
252
+ { type: "k8s.pod", idKeys: ["k8s.pod.name"] },
253
+ { type: "k8s.pod", idKeys: ["k8s.pod.uid"] },
254
+ ];
255
+
256
+ test("only the first ref of a type is promoted to a registry row", () => {
257
+ const pods: Array<ExtractedEntity> = entitiesFor(
258
+ POD_WITH_UID,
259
+ TWO_POD_REFS,
260
+ ).filter((e: ExtractedEntity) => {
261
+ return e.entityType === EntityType.KubernetesPod;
262
+ });
263
+
264
+ expect(pods).toHaveLength(2);
265
+ expect(
266
+ pods.filter((p: ExtractedEntity) => {
267
+ return !p.membershipOnly;
268
+ }),
269
+ ).toHaveLength(1);
270
+ });
271
+
272
+ test("the demoted ref keeps its key so signals stay queryable by it", () => {
273
+ const pods: Array<ExtractedEntity> = entitiesFor(
274
+ POD_WITH_UID,
275
+ TWO_POD_REFS,
276
+ ).filter((e: ExtractedEntity) => {
277
+ return e.entityType === EntityType.KubernetesPod;
278
+ });
279
+
280
+ const keys: Array<string> = pods.map((p: ExtractedEntity) => {
281
+ return p.entityKey;
282
+ });
283
+
284
+ expect(new Set(keys).size).toBe(2);
285
+ expect(
286
+ InventoryItem.extractEntityKeys({
287
+ projectId: PROJECT,
288
+ attributes: POD_WITH_UID,
289
+ entityRefs: TWO_POD_REFS,
290
+ }),
291
+ ).toEqual(expect.arrayContaining(keys));
292
+ });
293
+
294
+ test("refs of different types are all promoted", () => {
295
+ const entities: Array<ExtractedEntity> = entitiesFor(POD_WITH_UID, [
296
+ { type: "k8s.pod", idKeys: ["k8s.pod.name"] },
297
+ { type: "k8s.namespace", idKeys: ["k8s.namespace.name"] },
298
+ ]);
299
+
300
+ expect(
301
+ entities.filter((e: ExtractedEntity) => {
302
+ return !e.membershipOnly;
303
+ }),
304
+ ).toHaveLength(2);
305
+ });
306
+
307
+ test("the heuristic resolvers already emit one entity per type", () => {
308
+ const types: Array<EntityType> = entitiesFor(POD_WITH_UID).map(
309
+ (e: ExtractedEntity) => {
310
+ return e.entityType;
311
+ },
312
+ );
313
+
314
+ expect(new Set(types).size).toBe(types.length);
315
+ });
316
+ });
@@ -750,7 +750,7 @@ describe("descriptive attributes & labels (never identity-bearing)", () => {
750
750
  name: "worker-1",
751
751
  },
752
752
  ])(
753
- "k8s.$label: keeps the stable UID identity and carries the name descriptively",
753
+ "k8s.$label: keys on the name and carries the UID descriptively",
754
754
  ({
755
755
  type,
756
756
  uidKey,
@@ -765,19 +765,122 @@ describe("descriptive attributes & labels (never identity-bearing)", () => {
765
765
  nameKey: string;
766
766
  name: string;
767
767
  }) => {
768
- const uidOnly: ExtractedEntity | undefined = entityOfType(
769
- { [uidKey]: uid },
770
- type,
771
- );
772
768
  const named: ExtractedEntity | undefined = entityOfType(
773
769
  { [uidKey]: uid, [nameKey]: name },
774
770
  type,
775
771
  );
776
772
 
777
773
  expect(named).toBeDefined();
778
- expect(named!.entityKey).toBe(uidOnly!.entityKey);
779
- expect(named!.identifyingAttributes).toEqual({ [uidKey]: uid });
780
- expect(named!.descriptiveAttributes).toEqual({ [nameKey]: name });
774
+ expect(named!.identifyingAttributes).toEqual({ [nameKey]: name });
775
+ /*
776
+ * The name is carried descriptively as well as identifying it. That is
777
+ * deliberate: `buildDescriptiveUpdate` reads the display name out of
778
+ * the descriptive bag first, so a row whose identity later changes
779
+ * shape still has a name to show.
780
+ */
781
+ expect(named!.descriptiveAttributes).toEqual({
782
+ [nameKey]: name,
783
+ [uidKey]: uid,
784
+ });
785
+ },
786
+ );
787
+
788
+ /*
789
+ * The regression test for duplicate Inventory rows.
790
+ *
791
+ * The shipped Kubernetes agent runs two collectors over the same nodes and
792
+ * pods. The Deployment's `k8s_cluster` receiver resolves uids; the
793
+ * DaemonSet's kubeletstats / hostmetrics / cAdvisor / filelog receivers
794
+ * never do. Identity must not depend on which of them is talking.
795
+ */
796
+ test.each([
797
+ {
798
+ label: "node",
799
+ type: EntityType.KubernetesNode,
800
+ withUid: {
801
+ "k8s.cluster.name": "prod-us",
802
+ "k8s.node.name": "worker-1",
803
+ "k8s.node.uid": "node-uid-1",
804
+ },
805
+ withoutUid: {
806
+ "k8s.cluster.name": "prod-us",
807
+ "k8s.node.name": "worker-1",
808
+ },
809
+ },
810
+ {
811
+ label: "pod",
812
+ type: EntityType.KubernetesPod,
813
+ withUid: {
814
+ "k8s.cluster.name": "prod-us",
815
+ "k8s.namespace.name": "shop",
816
+ "k8s.pod.name": "checkout-7d9f",
817
+ "k8s.pod.uid": "pod-uid-1",
818
+ },
819
+ withoutUid: {
820
+ "k8s.cluster.name": "prod-us",
821
+ "k8s.namespace.name": "shop",
822
+ "k8s.pod.name": "checkout-7d9f",
823
+ },
824
+ },
825
+ ])(
826
+ "k8s.$label: a producer that resolves the UID and one that does not agree on identity",
827
+ ({
828
+ type,
829
+ withUid,
830
+ withoutUid,
831
+ }: {
832
+ label: string;
833
+ type: EntityType;
834
+ withUid: EntityAttributes;
835
+ withoutUid: EntityAttributes;
836
+ }) => {
837
+ const resolved: ExtractedEntity | undefined = entityOfType(withUid, type);
838
+ const unresolved: ExtractedEntity | undefined = entityOfType(
839
+ withoutUid,
840
+ type,
841
+ );
842
+
843
+ expect(resolved).toBeDefined();
844
+ expect(unresolved).toBeDefined();
845
+ expect(resolved!.entityKey).toBe(unresolved!.entityKey);
846
+ expect(resolved!.identifyingAttributes).toEqual(
847
+ unresolved!.identifyingAttributes,
848
+ );
849
+ },
850
+ );
851
+
852
+ test.each([
853
+ {
854
+ label: "node",
855
+ type: EntityType.KubernetesNode,
856
+ uidKey: "k8s.node.uid",
857
+ uid: "node-uid-1",
858
+ },
859
+ {
860
+ label: "pod",
861
+ type: EntityType.KubernetesPod,
862
+ uidKey: "k8s.pod.uid",
863
+ uid: "pod-uid-1",
864
+ },
865
+ ])(
866
+ "k8s.$label: falls back to the UID when the resource carries no name",
867
+ ({
868
+ type,
869
+ uidKey,
870
+ uid,
871
+ }: {
872
+ label: string;
873
+ type: EntityType;
874
+ uidKey: string;
875
+ uid: string;
876
+ }) => {
877
+ const entity: ExtractedEntity | undefined = entityOfType(
878
+ { [uidKey]: uid },
879
+ type,
880
+ );
881
+
882
+ expect(entity).toBeDefined();
883
+ expect(entity!.identifyingAttributes).toEqual({ [uidKey]: uid });
781
884
  },
782
885
  );
783
886
 
@@ -0,0 +1,106 @@
1
+ import IncludesAnyOfGroups from "../../../Types/BaseDatabase/IncludesAnyOfGroups";
2
+ import BadDataException from "../../../Types/Exception/BadDataException";
3
+ import { JSONObject, ObjectType } from "../../../Types/JSON";
4
+ import JSONFunctions from "../../../Types/JSONFunctions";
5
+ import SerializableObjectDictionary from "../../../Types/SerializableObjectDictionary";
6
+
7
+ describe("IncludesAnyOfGroups", () => {
8
+ test("preserves group boundaries and removes duplicate ids within a group", () => {
9
+ const value: IncludesAnyOfGroups = new IncludesAnyOfGroups([
10
+ ["network", "router", "network"],
11
+ ["0660", "0661"],
12
+ ]);
13
+ expect(value.groups).toEqual([
14
+ ["network", "router"],
15
+ ["0660", "0661"],
16
+ ]);
17
+ });
18
+
19
+ test("retains empty groups so they can match nothing", () => {
20
+ expect(new IncludesAnyOfGroups([["network"], []]).groups).toEqual([
21
+ ["network"],
22
+ [],
23
+ ]);
24
+ });
25
+
26
+ test("owns its data and does not expose mutable validated groups", () => {
27
+ const groups: Array<Array<string>> = [["network"], ["0660"]];
28
+ const value: IncludesAnyOfGroups = new IncludesAnyOfGroups(groups);
29
+ groups[0]!.push("router");
30
+ value.groups[1]!.push("0661");
31
+ (value.toJSON()["value"] as Array<Array<string>>)[0]!.push("other");
32
+ expect(value.groups).toEqual([["network"], ["0660"]]);
33
+ });
34
+
35
+ test("round trips through the API JSON registry with leading zeros intact", () => {
36
+ const value: IncludesAnyOfGroups = new IncludesAnyOfGroups([
37
+ ["network"],
38
+ ["0660", "0661"],
39
+ ]);
40
+ const serialized: JSONObject = JSONFunctions.serialize({ labels: value });
41
+ expect(serialized["labels"]).toEqual({
42
+ _type: ObjectType.IncludesAnyOfGroups,
43
+ value: [["network"], ["0660", "0661"]],
44
+ });
45
+ const result: JSONObject = JSONFunctions.deserialize(
46
+ JSON.parse(JSON.stringify(serialized)),
47
+ ) as JSONObject;
48
+ expect(SerializableObjectDictionary[ObjectType.IncludesAnyOfGroups]).toBe(
49
+ IncludesAnyOfGroups,
50
+ );
51
+ expect(result["labels"]).toBeInstanceOf(IncludesAnyOfGroups);
52
+ expect((result["labels"] as IncludesAnyOfGroups).groups).toEqual(
53
+ value.groups,
54
+ );
55
+ });
56
+
57
+ test.each(
58
+ [
59
+ undefined,
60
+ null,
61
+ [],
62
+ "network",
63
+ ["network"],
64
+ [null],
65
+ [[null]],
66
+ [[1]],
67
+ [[{}]],
68
+ [[""]],
69
+ [["a".repeat(257)]],
70
+ Array.from({ length: 33 }, () => {
71
+ return ["id"];
72
+ }),
73
+ [
74
+ Array.from({ length: 1001 }, () => {
75
+ return "id";
76
+ }),
77
+ ],
78
+ Array.from({ length: 11 }, () => {
79
+ return Array.from({ length: 1000 }, () => {
80
+ return "id";
81
+ });
82
+ }),
83
+ ].map((groups: unknown) => {
84
+ return [groups];
85
+ }),
86
+ )("rejects invalid or oversized group data %#", (groups: unknown) => {
87
+ expect(() => {
88
+ return new IncludesAnyOfGroups(groups as Array<Array<string>>);
89
+ }).toThrow(BadDataException);
90
+ expect(() => {
91
+ return IncludesAnyOfGroups.fromJSON({
92
+ _type: ObjectType.IncludesAnyOfGroups,
93
+ value: groups,
94
+ } as JSONObject);
95
+ }).toThrow(BadDataException);
96
+ });
97
+
98
+ test("rejects a different operator type", () => {
99
+ expect(() => {
100
+ return IncludesAnyOfGroups.fromJSON({
101
+ _type: ObjectType.Includes,
102
+ value: [["network"]],
103
+ });
104
+ }).toThrow(BadDataException);
105
+ });
106
+ });
@@ -484,6 +484,46 @@ describe("JSONFunctions Class", () => {
484
484
  }).toThrow("Expected JSONObject, but got JSONArray");
485
485
  });
486
486
 
487
+ /*
488
+ * An array was once the only shape refused, so every other non-object -
489
+ * all of them valid JSON - came back typed as a JSONObject and was read
490
+ * as one. The two callers that matter both did exactly that: the Text to
491
+ * JSON workflow component announced a bare number as a successful parse,
492
+ * and JSONWebToken.decodeJsonPayload took a string payload and read
493
+ * undefined out of every field of it.
494
+ */
495
+ test("parseJSONObject throws when given a bare number", () => {
496
+ expect(() => {
497
+ return JSONFunctions.parseJSONObject("42");
498
+ }).toThrow("Expected JSONObject, but got number");
499
+ });
500
+
501
+ test("parseJSONObject throws when given a bare string", () => {
502
+ expect(() => {
503
+ return JSONFunctions.parseJSONObject('"just a string"');
504
+ }).toThrow("Expected JSONObject, but got string");
505
+ });
506
+
507
+ test("parseJSONObject throws when given a bare boolean", () => {
508
+ expect(() => {
509
+ return JSONFunctions.parseJSONObject("true");
510
+ }).toThrow("Expected JSONObject, but got boolean");
511
+ });
512
+
513
+ test("parseJSONObject throws when given null", () => {
514
+ expect(() => {
515
+ return JSONFunctions.parseJSONObject("null");
516
+ }).toThrow("Expected JSONObject, but got null");
517
+ });
518
+
519
+ test("parseJSONObject still accepts an empty object", () => {
520
+ expect(JSONFunctions.parseJSONObject("{}")).toEqual({});
521
+ });
522
+
523
+ test("parseJSONObject still accepts JSON5 unquoted keys", () => {
524
+ expect(JSONFunctions.parseJSONObject("{ a: 1 }")).toEqual({ a: 1 });
525
+ });
526
+
487
527
  test("parseJSONArray returns an array", () => {
488
528
  expect(JSONFunctions.parseJSONArray("[1, 2]")).toEqual([1, 2]);
489
529
  });