@oneuptime/common 12.0.2 → 12.0.3

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 (228) hide show
  1. package/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel.ts +14 -0
  2. package/Models/DatabaseModels/Index.ts +2 -0
  3. package/Models/DatabaseModels/LogDropFilter.ts +9 -20
  4. package/Models/DatabaseModels/StatusPageMonitorRule.ts +911 -0
  5. package/Models/DatabaseModels/StatusPageResource.ts +73 -0
  6. package/Models/DatabaseModels/TraceDropFilter.ts +9 -20
  7. package/Server/API/BaseAPI.ts +13 -2
  8. package/Server/API/DashboardAPI.ts +39 -0
  9. package/Server/EnvironmentConfig.ts +31 -1
  10. package/Server/Infrastructure/GlobalCache.ts +115 -0
  11. package/Server/Infrastructure/Postgres/DataSourceOptions.ts +16 -0
  12. package/Server/Infrastructure/Postgres/SchemaMigrations/1786005052769-AddStatusPageMonitorRule.ts +123 -0
  13. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +2 -0
  14. package/Server/Services/AlertStateTimelineService.ts +20 -0
  15. package/Server/Services/CephClusterService.ts +26 -86
  16. package/Server/Services/CloudResourceService.ts +23 -83
  17. package/Server/Services/DatabaseService.ts +111 -0
  18. package/Server/Services/DockerHostService.ts +19 -79
  19. package/Server/Services/DockerSwarmClusterService.ts +27 -88
  20. package/Server/Services/HostService.ts +46 -99
  21. package/Server/Services/IncidentStateTimelineService.ts +20 -0
  22. package/Server/Services/Index.ts +2 -0
  23. package/Server/Services/IoTFleetService.ts +19 -79
  24. package/Server/Services/KubernetesClusterService.ts +17 -78
  25. package/Server/Services/LabelService.ts +57 -10
  26. package/Server/Services/MetricTypeService.ts +84 -0
  27. package/Server/Services/MonitorService.ts +89 -14
  28. package/Server/Services/MonitorStatusTimelineService.ts +29 -0
  29. package/Server/Services/MonitorTemplateService.ts +65 -0
  30. package/Server/Services/PodmanHostService.ts +19 -79
  31. package/Server/Services/ProxmoxClusterService.ts +23 -83
  32. package/Server/Services/RumApplicationService.ts +19 -80
  33. package/Server/Services/ScheduledMaintenanceStateTimelineService.ts +21 -0
  34. package/Server/Services/ServerlessFunctionService.ts +24 -85
  35. package/Server/Services/ServiceService.ts +299 -73
  36. package/Server/Services/StatusPageMonitorRuleEngineService.ts +752 -0
  37. package/Server/Services/StatusPageMonitorRuleService.ts +471 -0
  38. package/Server/Services/TelemetryEntityRelationshipService.ts +1 -0
  39. package/Server/Services/TelemetryEntityService.ts +1 -0
  40. package/Server/Utils/Database/ProjectScopedReferenceValidator.ts +170 -39
  41. package/Server/Utils/Monitor/MonitorResource.ts +63 -36
  42. package/Server/Utils/Monitor/MonitorStatusTimeline.ts +36 -0
  43. package/Server/Utils/Monitor/MonitorStepsProjectValidator.ts +148 -127
  44. package/Server/Utils/Monitor/MonitorStepsReferenceExtractor.ts +434 -0
  45. package/Server/Utils/SingleFlight.ts +83 -0
  46. package/Server/Utils/StatusPage/MonitorRulePatternValidator.ts +46 -0
  47. package/Server/Utils/Telemetry/EntityRegistry.ts +107 -26
  48. package/Server/Utils/Telemetry/ResourceHeartbeat.ts +314 -0
  49. package/Server/Utils/Telemetry/Telemetry.ts +351 -122
  50. package/Tests/App/Dashboard/WidgetCatalog.test.ts +2 -0
  51. package/Tests/Models/DatabaseModels/ColumnTransformerInvariants.test.ts +306 -0
  52. package/Tests/Models/DatabaseModels/NumberColumnDeserialization.test.ts +333 -0
  53. package/Tests/Models/DatabaseModels/StatusPageMonitorRuleModel.test.ts +190 -0
  54. package/Tests/Server/API/DashboardPublicResourceListAPI.test.ts +26 -0
  55. package/Tests/Server/Infrastructure/GlobalCache.test.ts +226 -0
  56. package/Tests/Server/Infrastructure/PostgresLockTimeoutOptions.test.ts +181 -0
  57. package/Tests/Server/Services/DatabaseServiceUpdateColumnsIfUnlocked.test.ts +342 -0
  58. package/Tests/Server/Services/DropFilterSaveValidation.test.ts +245 -0
  59. package/Tests/Server/Services/HostServiceUpdateLastSeen.test.ts +163 -15
  60. package/Tests/Server/Services/MetricTypeAttachServices.test.ts +222 -0
  61. package/Tests/Server/Services/MissingReferenceWriteGuard.test.ts +614 -0
  62. package/Tests/Server/Services/MonitorStatusTimelineFastPath.test.ts +23 -0
  63. package/Tests/Server/Services/ResourceUpdateLastSeenLivenessFallback.test.ts +305 -56
  64. package/Tests/Server/Services/ServiceUpdateLastSeenSplitGates.test.ts +705 -0
  65. package/Tests/Server/Services/StatusPageMonitorRuleEngineService.test.ts +1719 -0
  66. package/Tests/Server/Services/StatusPageMonitorRuleHooks.test.ts +1212 -0
  67. package/Tests/Server/Services/StatusPageMonitorRuleMonitorCreateHook.test.ts +228 -0
  68. package/Tests/Server/Utils/APIKey/AccessPermission.test.ts +267 -0
  69. package/Tests/Server/Utils/Database/ProjectScopedReferenceValidator.test.ts +260 -13
  70. package/Tests/Server/Utils/EntityRegistryRowFence.test.ts +302 -0
  71. package/Tests/Server/Utils/MetricTypeIndexConvergence.test.ts +598 -0
  72. package/Tests/Server/Utils/Monitor/MonitorStatusTimelineUnusableStatus.test.ts +154 -0
  73. package/Tests/Server/Utils/Monitor/MonitorStepsProjectValidator.test.ts +638 -92
  74. package/Tests/Server/Utils/Monitor/MonitorStepsReferenceExtractor.test.ts +724 -0
  75. package/Tests/Server/Utils/SingleFlight.test.ts +221 -0
  76. package/Tests/Types/Dashboard/NetworkDashboardTemplate.test.ts +793 -0
  77. package/Tests/Types/Database/BigIntColumnTransformer.test.ts +147 -0
  78. package/Tests/Types/Database/DatabasePropertyTransformer.test.ts +140 -0
  79. package/Tests/Types/Database/NumericColumnValue.test.ts +127 -0
  80. package/Tests/Types/Decimal.test.ts +37 -0
  81. package/Tests/Types/Monitor/SnmpVersionParsing.test.ts +152 -0
  82. package/Tests/Types/Port.test.ts +38 -0
  83. package/Tests/UI/Components/LogsHistogram.test.tsx +276 -0
  84. package/Tests/UI/Components/UseLiveLogsRefresh.test.tsx +489 -0
  85. package/Tests/UI/Components/UseLogsHistogram.test.tsx +500 -0
  86. package/Tests/Utils/Dashboard/Components/DashboardComponentsUtil.test.ts +120 -0
  87. package/Tests/Utils/Dashboard/DashboardNetworkMapComponent.test.ts +327 -0
  88. package/Types/Dashboard/DashboardComponentType.ts +1 -0
  89. package/Types/Dashboard/DashboardComponents/ComponentArgument.ts +1 -0
  90. package/Types/Dashboard/DashboardComponents/DashboardNetworkMapComponent.ts +40 -0
  91. package/Types/Dashboard/DashboardTemplates.ts +397 -0
  92. package/Types/Database/BigIntColumnTransformer.ts +74 -0
  93. package/Types/Database/DatabaseProperty.ts +26 -0
  94. package/Types/Database/NumericColumnValue.ts +128 -0
  95. package/Types/Decimal.ts +16 -4
  96. package/Types/Permission.ts +48 -0
  97. package/Types/Port.ts +16 -4
  98. package/UI/Components/LogsViewer/useLiveLogsRefresh.ts +86 -0
  99. package/UI/Components/LogsViewer/useLogsHistogram.ts +113 -0
  100. package/Utils/Dashboard/Components/DashboardNetworkMapComponent.ts +136 -0
  101. package/Utils/Dashboard/Components/Index.ts +7 -0
  102. package/build/dist/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel.js +12 -0
  103. package/build/dist/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel.js.map +1 -1
  104. package/build/dist/Models/DatabaseModels/Index.js +2 -0
  105. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  106. package/build/dist/Models/DatabaseModels/LogDropFilter.js +9 -20
  107. package/build/dist/Models/DatabaseModels/LogDropFilter.js.map +1 -1
  108. package/build/dist/Models/DatabaseModels/StatusPageMonitorRule.js +925 -0
  109. package/build/dist/Models/DatabaseModels/StatusPageMonitorRule.js.map +1 -0
  110. package/build/dist/Models/DatabaseModels/StatusPageResource.js +72 -0
  111. package/build/dist/Models/DatabaseModels/StatusPageResource.js.map +1 -1
  112. package/build/dist/Models/DatabaseModels/TraceDropFilter.js +9 -20
  113. package/build/dist/Models/DatabaseModels/TraceDropFilter.js.map +1 -1
  114. package/build/dist/Server/API/BaseAPI.js +11 -1
  115. package/build/dist/Server/API/BaseAPI.js.map +1 -1
  116. package/build/dist/Server/API/DashboardAPI.js +39 -0
  117. package/build/dist/Server/API/DashboardAPI.js.map +1 -1
  118. package/build/dist/Server/EnvironmentConfig.js +27 -1
  119. package/build/dist/Server/EnvironmentConfig.js.map +1 -1
  120. package/build/dist/Server/Infrastructure/GlobalCache.js +91 -0
  121. package/build/dist/Server/Infrastructure/GlobalCache.js.map +1 -1
  122. package/build/dist/Server/Infrastructure/Postgres/DataSourceOptions.js +5 -3
  123. package/build/dist/Server/Infrastructure/Postgres/DataSourceOptions.js.map +1 -1
  124. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786005052769-AddStatusPageMonitorRule.js +48 -0
  125. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786005052769-AddStatusPageMonitorRule.js.map +1 -0
  126. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +2 -0
  127. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  128. package/build/dist/Server/Services/AlertStateTimelineService.js +19 -0
  129. package/build/dist/Server/Services/AlertStateTimelineService.js.map +1 -1
  130. package/build/dist/Server/Services/CephClusterService.js +26 -76
  131. package/build/dist/Server/Services/CephClusterService.js.map +1 -1
  132. package/build/dist/Server/Services/CloudResourceService.js +23 -73
  133. package/build/dist/Server/Services/CloudResourceService.js.map +1 -1
  134. package/build/dist/Server/Services/DatabaseService.js +86 -0
  135. package/build/dist/Server/Services/DatabaseService.js.map +1 -1
  136. package/build/dist/Server/Services/DockerHostService.js +19 -69
  137. package/build/dist/Server/Services/DockerHostService.js.map +1 -1
  138. package/build/dist/Server/Services/DockerSwarmClusterService.js +27 -78
  139. package/build/dist/Server/Services/DockerSwarmClusterService.js.map +1 -1
  140. package/build/dist/Server/Services/HostService.js +58 -102
  141. package/build/dist/Server/Services/HostService.js.map +1 -1
  142. package/build/dist/Server/Services/IncidentStateTimelineService.js +19 -0
  143. package/build/dist/Server/Services/IncidentStateTimelineService.js.map +1 -1
  144. package/build/dist/Server/Services/Index.js +2 -0
  145. package/build/dist/Server/Services/Index.js.map +1 -1
  146. package/build/dist/Server/Services/IoTFleetService.js +19 -69
  147. package/build/dist/Server/Services/IoTFleetService.js.map +1 -1
  148. package/build/dist/Server/Services/KubernetesClusterService.js +17 -68
  149. package/build/dist/Server/Services/KubernetesClusterService.js.map +1 -1
  150. package/build/dist/Server/Services/LabelService.js +42 -7
  151. package/build/dist/Server/Services/LabelService.js.map +1 -1
  152. package/build/dist/Server/Services/MetricTypeService.js +74 -0
  153. package/build/dist/Server/Services/MetricTypeService.js.map +1 -1
  154. package/build/dist/Server/Services/MonitorService.js +81 -9
  155. package/build/dist/Server/Services/MonitorService.js.map +1 -1
  156. package/build/dist/Server/Services/MonitorStatusTimelineService.js +28 -0
  157. package/build/dist/Server/Services/MonitorStatusTimelineService.js.map +1 -1
  158. package/build/dist/Server/Services/MonitorTemplateService.js +60 -0
  159. package/build/dist/Server/Services/MonitorTemplateService.js.map +1 -1
  160. package/build/dist/Server/Services/PodmanHostService.js +19 -69
  161. package/build/dist/Server/Services/PodmanHostService.js.map +1 -1
  162. package/build/dist/Server/Services/ProxmoxClusterService.js +23 -73
  163. package/build/dist/Server/Services/ProxmoxClusterService.js.map +1 -1
  164. package/build/dist/Server/Services/RumApplicationService.js +19 -70
  165. package/build/dist/Server/Services/RumApplicationService.js.map +1 -1
  166. package/build/dist/Server/Services/ScheduledMaintenanceStateTimelineService.js +20 -0
  167. package/build/dist/Server/Services/ScheduledMaintenanceStateTimelineService.js.map +1 -1
  168. package/build/dist/Server/Services/ServerlessFunctionService.js +24 -75
  169. package/build/dist/Server/Services/ServerlessFunctionService.js.map +1 -1
  170. package/build/dist/Server/Services/ServiceService.js +236 -61
  171. package/build/dist/Server/Services/ServiceService.js.map +1 -1
  172. package/build/dist/Server/Services/StatusPageMonitorRuleEngineService.js +603 -0
  173. package/build/dist/Server/Services/StatusPageMonitorRuleEngineService.js.map +1 -0
  174. package/build/dist/Server/Services/StatusPageMonitorRuleService.js +401 -0
  175. package/build/dist/Server/Services/StatusPageMonitorRuleService.js.map +1 -0
  176. package/build/dist/Server/Services/TelemetryEntityRelationshipService.js +1 -0
  177. package/build/dist/Server/Services/TelemetryEntityRelationshipService.js.map +1 -1
  178. package/build/dist/Server/Services/TelemetryEntityService.js +1 -0
  179. package/build/dist/Server/Services/TelemetryEntityService.js.map +1 -1
  180. package/build/dist/Server/Utils/Database/ProjectScopedReferenceValidator.js +105 -29
  181. package/build/dist/Server/Utils/Database/ProjectScopedReferenceValidator.js.map +1 -1
  182. package/build/dist/Server/Utils/Monitor/MonitorResource.js +56 -32
  183. package/build/dist/Server/Utils/Monitor/MonitorResource.js.map +1 -1
  184. package/build/dist/Server/Utils/Monitor/MonitorStatusTimeline.js +30 -3
  185. package/build/dist/Server/Utils/Monitor/MonitorStatusTimeline.js.map +1 -1
  186. package/build/dist/Server/Utils/Monitor/MonitorStepsProjectValidator.js +110 -98
  187. package/build/dist/Server/Utils/Monitor/MonitorStepsProjectValidator.js.map +1 -1
  188. package/build/dist/Server/Utils/Monitor/MonitorStepsReferenceExtractor.js +312 -0
  189. package/build/dist/Server/Utils/Monitor/MonitorStepsReferenceExtractor.js.map +1 -0
  190. package/build/dist/Server/Utils/SingleFlight.js +76 -0
  191. package/build/dist/Server/Utils/SingleFlight.js.map +1 -0
  192. package/build/dist/Server/Utils/StatusPage/MonitorRulePatternValidator.js +29 -0
  193. package/build/dist/Server/Utils/StatusPage/MonitorRulePatternValidator.js.map +1 -0
  194. package/build/dist/Server/Utils/Telemetry/EntityRegistry.js +78 -19
  195. package/build/dist/Server/Utils/Telemetry/EntityRegistry.js.map +1 -1
  196. package/build/dist/Server/Utils/Telemetry/ResourceHeartbeat.js +160 -0
  197. package/build/dist/Server/Utils/Telemetry/ResourceHeartbeat.js.map +1 -0
  198. package/build/dist/Server/Utils/Telemetry/Telemetry.js +279 -104
  199. package/build/dist/Server/Utils/Telemetry/Telemetry.js.map +1 -1
  200. package/build/dist/Types/Dashboard/DashboardComponentType.js +1 -0
  201. package/build/dist/Types/Dashboard/DashboardComponentType.js.map +1 -1
  202. package/build/dist/Types/Dashboard/DashboardComponents/ComponentArgument.js +1 -0
  203. package/build/dist/Types/Dashboard/DashboardComponents/ComponentArgument.js.map +1 -1
  204. package/build/dist/Types/Dashboard/DashboardComponents/DashboardNetworkMapComponent.js +2 -0
  205. package/build/dist/Types/Dashboard/DashboardComponents/DashboardNetworkMapComponent.js.map +1 -0
  206. package/build/dist/Types/Dashboard/DashboardTemplates.js +368 -0
  207. package/build/dist/Types/Dashboard/DashboardTemplates.js.map +1 -1
  208. package/build/dist/Types/Database/BigIntColumnTransformer.js +66 -0
  209. package/build/dist/Types/Database/BigIntColumnTransformer.js.map +1 -0
  210. package/build/dist/Types/Database/DatabaseProperty.js +25 -0
  211. package/build/dist/Types/Database/DatabaseProperty.js.map +1 -1
  212. package/build/dist/Types/Database/NumericColumnValue.js +97 -0
  213. package/build/dist/Types/Database/NumericColumnValue.js.map +1 -0
  214. package/build/dist/Types/Decimal.js +14 -3
  215. package/build/dist/Types/Decimal.js.map +1 -1
  216. package/build/dist/Types/Permission.js +42 -0
  217. package/build/dist/Types/Permission.js.map +1 -1
  218. package/build/dist/Types/Port.js +15 -5
  219. package/build/dist/Types/Port.js.map +1 -1
  220. package/build/dist/UI/Components/LogsViewer/useLiveLogsRefresh.js +44 -0
  221. package/build/dist/UI/Components/LogsViewer/useLiveLogsRefresh.js.map +1 -0
  222. package/build/dist/UI/Components/LogsViewer/useLogsHistogram.js +75 -0
  223. package/build/dist/UI/Components/LogsViewer/useLogsHistogram.js.map +1 -0
  224. package/build/dist/Utils/Dashboard/Components/DashboardNetworkMapComponent.js +114 -0
  225. package/build/dist/Utils/Dashboard/Components/DashboardNetworkMapComponent.js.map +1 -0
  226. package/build/dist/Utils/Dashboard/Components/Index.js +4 -0
  227. package/build/dist/Utils/Dashboard/Components/Index.js.map +1 -1
  228. package/package.json +1 -1
@@ -0,0 +1,724 @@
1
+ import MonitorStepsReferenceExtractor, {
2
+ MonitorStepsReference,
3
+ MonitorStepsReferenceModel,
4
+ toReferenceId,
5
+ } from "../../../../Server/Utils/Monitor/MonitorStepsReferenceExtractor";
6
+ import { JSONObject, JSONValue, ObjectType } from "../../../../Types/JSON";
7
+ import MonitorSteps from "../../../../Types/Monitor/MonitorSteps";
8
+ import ObjectID from "../../../../Types/ObjectID";
9
+ import { describe, expect, it } from "@jest/globals";
10
+
11
+ /*
12
+ * Contract under test: tell a real reference inside monitorSteps apart from the
13
+ * many uuids in there that are NOT references.
14
+ *
15
+ * This is the whole reason issue #3039 could not be fixed before. The old guard
16
+ * scraped every uuid out of the serialized blob with a regex, so it could only
17
+ * ever ask "does this happen to be another project's record?" — asking "does
18
+ * this exist?" would have rejected every step id, criteria id and
19
+ * incident/alert template id, none of which match a row anywhere.
20
+ *
21
+ * Reading the structure instead means every id that comes back is known to
22
+ * point at a specific model, so it can be required to exist.
23
+ */
24
+
25
+ const STATUS_ID: string = "0a3f7e0e-0a4a-4f3f-8b46-0f4f1e2d5c60";
26
+ const DEFAULT_STATUS_ID: string = "5e8b6bd1-3a09-4b0b-9a4e-2a76b0a1c111";
27
+ const INCIDENT_SEVERITY_ID: string = "9b1c1c1e-8a11-4a6f-9c31-6d2f0b6d4a22";
28
+ const ALERT_SEVERITY_ID: string = "c31b3a44-1f60-4a1d-95bb-3e0e6f9a7d33";
29
+ const ON_CALL_POLICY_ID: string = "d40e5c55-2b71-4b2e-86cc-4f1f7a0b8e44";
30
+ const LABEL_ID: string = "e51f6d66-3c82-4c3f-97dd-501a8b1c9f55";
31
+ const TEAM_ID: string = "f6207e77-4d93-4d40-a8ee-612b9c2daa66";
32
+ const USER_ID: string = "07318f88-5ea4-4e51-b9ff-723cad3ebb77";
33
+ const ROLE_ID: string = "1842909a-6fb5-4f62-8a00-834dbe4fcc88";
34
+ const TELEMETRY_SERVICE_ID: string = "29530aab-70c6-4073-9b11-945ecf50dd99";
35
+ const NETWORK_DEVICE_ID: string = "3a641bbc-81d7-4184-8c22-a56fd0610eaa";
36
+
37
+ // Local identity, not a pointer at another row. None of these may be returned.
38
+ const STEP_ID: string = "4b752ccd-92e8-4295-9d33-b670e1721fbb";
39
+ const CRITERIA_ID: string = "5c863dde-a3f9-43a6-8e44-c781f2832acc";
40
+ const INCIDENT_TEMPLATE_ID: string = "6d974eef-b40a-44b7-9f55-d892031943dd";
41
+ const ALERT_TEMPLATE_ID: string = "7ea85ff0-c51b-45c8-a066-e9a3142a54ee";
42
+
43
+ /*
44
+ * `{"_type":"ObjectID","value":"…"}` — how JSONFunctions.serialize writes the
45
+ * ObjectIDs nested inside the incidents and alerts arrays.
46
+ */
47
+ const objectIdJSON: (id: string) => JSONObject = (id: string): JSONObject => {
48
+ return { _type: ObjectType.ObjectID, value: id };
49
+ };
50
+
51
+ const criteriaInstance: (overrides: JSONObject) => JSONObject = (
52
+ overrides: JSONObject,
53
+ ): JSONObject => {
54
+ return {
55
+ _type: ObjectType.MonitorCriteriaInstance,
56
+ value: {
57
+ id: CRITERIA_ID,
58
+ name: "Monitor is offline",
59
+ description: "",
60
+ filterCondition: "all",
61
+ filters: [],
62
+ incidents: [],
63
+ alerts: [],
64
+ ...overrides,
65
+ },
66
+ };
67
+ };
68
+
69
+ const stepsWithCriteria: (
70
+ instances: Array<JSONObject>,
71
+ stepOverrides?: JSONObject,
72
+ ) => JSONObject = (
73
+ instances: Array<JSONObject>,
74
+ stepOverrides?: JSONObject,
75
+ ): JSONObject => {
76
+ return {
77
+ _type: ObjectType.MonitorSteps,
78
+ value: {
79
+ defaultMonitorStatusId: DEFAULT_STATUS_ID,
80
+ monitorStepsInstanceArray: [
81
+ {
82
+ _type: ObjectType.MonitorStep,
83
+ value: {
84
+ id: STEP_ID,
85
+ ...(stepOverrides || {}),
86
+ monitorCriteria: {
87
+ _type: ObjectType.MonitorCriteria,
88
+ value: {
89
+ monitorCriteriaInstanceArray: instances,
90
+ },
91
+ },
92
+ },
93
+ },
94
+ ],
95
+ },
96
+ };
97
+ };
98
+
99
+ const idsFor: (
100
+ references: Array<MonitorStepsReference>,
101
+ model: MonitorStepsReferenceModel,
102
+ ) => Array<string> = (
103
+ references: Array<MonitorStepsReference>,
104
+ model: MonitorStepsReferenceModel,
105
+ ): Array<string> => {
106
+ return references
107
+ .filter((reference: MonitorStepsReference) => {
108
+ return reference.model === model;
109
+ })
110
+ .map((reference: MonitorStepsReference) => {
111
+ return reference.id;
112
+ });
113
+ };
114
+
115
+ const findById: (
116
+ references: Array<MonitorStepsReference>,
117
+ id: string,
118
+ ) => MonitorStepsReference | undefined = (
119
+ references: Array<MonitorStepsReference>,
120
+ id: string,
121
+ ): MonitorStepsReference | undefined => {
122
+ return references.find((reference: MonitorStepsReference) => {
123
+ return reference.id === id;
124
+ });
125
+ };
126
+
127
+ describe("MonitorStepsReferenceExtractor", () => {
128
+ describe("what counts as a reference", () => {
129
+ it("returns the default monitor status", () => {
130
+ const references: Array<MonitorStepsReference> =
131
+ MonitorStepsReferenceExtractor.extract(stepsWithCriteria([]));
132
+
133
+ expect(
134
+ idsFor(references, MonitorStepsReferenceModel.MonitorStatus),
135
+ ).toEqual([DEFAULT_STATUS_ID]);
136
+ });
137
+
138
+ it("returns the status a criteria switches the monitor to", () => {
139
+ const references: Array<MonitorStepsReference> =
140
+ MonitorStepsReferenceExtractor.extract(
141
+ stepsWithCriteria([
142
+ criteriaInstance({
143
+ monitorStatusId: STATUS_ID,
144
+ changeMonitorStatus: true,
145
+ }),
146
+ ]),
147
+ );
148
+
149
+ expect(
150
+ idsFor(references, MonitorStepsReferenceModel.MonitorStatus),
151
+ ).toContain(STATUS_ID);
152
+ });
153
+
154
+ it("returns every id a criteria's incident template carries", () => {
155
+ const references: Array<MonitorStepsReference> =
156
+ MonitorStepsReferenceExtractor.extract(
157
+ stepsWithCriteria([
158
+ criteriaInstance({
159
+ createIncidents: true,
160
+ incidents: [
161
+ {
162
+ id: INCIDENT_TEMPLATE_ID,
163
+ title: "Down",
164
+ description: "",
165
+ incidentSeverityId: objectIdJSON(INCIDENT_SEVERITY_ID),
166
+ onCallPolicyIds: [objectIdJSON(ON_CALL_POLICY_ID)],
167
+ labelIds: [objectIdJSON(LABEL_ID)],
168
+ ownerTeamIds: [objectIdJSON(TEAM_ID)],
169
+ ownerUserIds: [objectIdJSON(USER_ID)],
170
+ incidentMemberRoles: [
171
+ {
172
+ roleId: objectIdJSON(ROLE_ID),
173
+ userId: objectIdJSON(USER_ID),
174
+ },
175
+ ],
176
+ },
177
+ ],
178
+ }),
179
+ ]),
180
+ );
181
+
182
+ expect(
183
+ idsFor(references, MonitorStepsReferenceModel.IncidentSeverity),
184
+ ).toEqual([INCIDENT_SEVERITY_ID]);
185
+ expect(
186
+ idsFor(references, MonitorStepsReferenceModel.OnCallDutyPolicy),
187
+ ).toEqual([ON_CALL_POLICY_ID]);
188
+ expect(idsFor(references, MonitorStepsReferenceModel.Label)).toEqual([
189
+ LABEL_ID,
190
+ ]);
191
+ expect(idsFor(references, MonitorStepsReferenceModel.Team)).toEqual([
192
+ TEAM_ID,
193
+ ]);
194
+ expect(
195
+ idsFor(references, MonitorStepsReferenceModel.IncidentRole),
196
+ ).toEqual([ROLE_ID]);
197
+ // Once as an owner and once as the member the role is assigned to.
198
+ expect(idsFor(references, MonitorStepsReferenceModel.User)).toEqual([
199
+ USER_ID,
200
+ USER_ID,
201
+ ]);
202
+ });
203
+
204
+ it("returns every id a criteria's alert template carries", () => {
205
+ const references: Array<MonitorStepsReference> =
206
+ MonitorStepsReferenceExtractor.extract(
207
+ stepsWithCriteria([
208
+ criteriaInstance({
209
+ createAlerts: true,
210
+ alerts: [
211
+ {
212
+ id: ALERT_TEMPLATE_ID,
213
+ title: "Down",
214
+ description: "",
215
+ alertSeverityId: objectIdJSON(ALERT_SEVERITY_ID),
216
+ onCallPolicyIds: [objectIdJSON(ON_CALL_POLICY_ID)],
217
+ labelIds: [objectIdJSON(LABEL_ID)],
218
+ ownerTeamIds: [objectIdJSON(TEAM_ID)],
219
+ ownerUserIds: [objectIdJSON(USER_ID)],
220
+ },
221
+ ],
222
+ }),
223
+ ]),
224
+ );
225
+
226
+ expect(
227
+ idsFor(references, MonitorStepsReferenceModel.AlertSeverity),
228
+ ).toEqual([ALERT_SEVERITY_ID]);
229
+ expect(
230
+ idsFor(references, MonitorStepsReferenceModel.OnCallDutyPolicy),
231
+ ).toEqual([ON_CALL_POLICY_ID]);
232
+ expect(idsFor(references, MonitorStepsReferenceModel.Label)).toEqual([
233
+ LABEL_ID,
234
+ ]);
235
+ expect(idsFor(references, MonitorStepsReferenceModel.Team)).toEqual([
236
+ TEAM_ID,
237
+ ]);
238
+ expect(idsFor(references, MonitorStepsReferenceModel.User)).toEqual([
239
+ USER_ID,
240
+ ]);
241
+ });
242
+
243
+ it("returns the telemetry services a step is scoped to, from every sub-config that carries them", () => {
244
+ for (const field of [
245
+ "logMonitor",
246
+ "traceMonitor",
247
+ "exceptionMonitor",
248
+ "profileMonitor",
249
+ "metricMonitor",
250
+ ]) {
251
+ const references: Array<MonitorStepsReference> =
252
+ MonitorStepsReferenceExtractor.extract(
253
+ stepsWithCriteria([], {
254
+ [field]: {
255
+ telemetryServiceIds: [objectIdJSON(TELEMETRY_SERVICE_ID)],
256
+ },
257
+ }),
258
+ );
259
+
260
+ expect(
261
+ idsFor(references, MonitorStepsReferenceModel.TelemetryService),
262
+ ).toEqual([TELEMETRY_SERVICE_ID]);
263
+ }
264
+ });
265
+
266
+ it("returns the device a network device step walks", () => {
267
+ const references: Array<MonitorStepsReference> =
268
+ MonitorStepsReferenceExtractor.extract(
269
+ stepsWithCriteria([], {
270
+ networkDeviceMonitor: { networkDeviceId: NETWORK_DEVICE_ID },
271
+ }),
272
+ );
273
+
274
+ expect(
275
+ idsFor(references, MonitorStepsReferenceModel.NetworkDevice),
276
+ ).toEqual([NETWORK_DEVICE_ID]);
277
+ });
278
+ });
279
+
280
+ describe("what is NOT a reference", () => {
281
+ it("never returns the step id, the criteria id, or an incident/alert template id", () => {
282
+ /*
283
+ * These are all `ObjectID.generate()` values minted client-side for local
284
+ * identity — Terraform never sends them, so they are minted again on
285
+ * every write. They match no row anywhere, and returning them is exactly
286
+ * what stopped the old regex-based guard from checking existence.
287
+ */
288
+ const references: Array<MonitorStepsReference> =
289
+ MonitorStepsReferenceExtractor.extract(
290
+ stepsWithCriteria([
291
+ criteriaInstance({
292
+ createIncidents: true,
293
+ createAlerts: true,
294
+ incidents: [
295
+ { id: INCIDENT_TEMPLATE_ID, title: "t", description: "" },
296
+ ],
297
+ alerts: [{ id: ALERT_TEMPLATE_ID, title: "t", description: "" }],
298
+ }),
299
+ ]),
300
+ );
301
+
302
+ const returned: Array<string> = references.map(
303
+ (reference: MonitorStepsReference) => {
304
+ return reference.id;
305
+ },
306
+ );
307
+
308
+ expect(returned).not.toContain(STEP_ID);
309
+ expect(returned).not.toContain(CRITERIA_ID);
310
+ expect(returned).not.toContain(INCIDENT_TEMPLATE_ID);
311
+ expect(returned).not.toContain(ALERT_TEMPLATE_ID);
312
+ });
313
+
314
+ it("skips a blank id rather than reporting it as a missing reference", () => {
315
+ /*
316
+ * A stored `{"_type":"ObjectID","value":""}` rehydrates to
317
+ * `new ObjectID("")`, which is truthy. Reporting it as a missing
318
+ * *reference* would be misleading — it is an unset field, and the
319
+ * required-field checks own that case.
320
+ */
321
+ const references: Array<MonitorStepsReference> =
322
+ MonitorStepsReferenceExtractor.extract(
323
+ stepsWithCriteria([
324
+ criteriaInstance({
325
+ monitorStatusId: "",
326
+ changeMonitorStatus: true,
327
+ createIncidents: true,
328
+ incidents: [
329
+ {
330
+ id: INCIDENT_TEMPLATE_ID,
331
+ title: "t",
332
+ description: "",
333
+ incidentSeverityId: objectIdJSON(""),
334
+ },
335
+ ],
336
+ }),
337
+ ]),
338
+ );
339
+
340
+ expect(
341
+ idsFor(references, MonitorStepsReferenceModel.IncidentSeverity),
342
+ ).toEqual([]);
343
+ expect(
344
+ idsFor(references, MonitorStepsReferenceModel.MonitorStatus),
345
+ ).toEqual([DEFAULT_STATUS_ID]);
346
+ });
347
+ });
348
+
349
+ describe("isOperative — which references the monitor actually reads", () => {
350
+ it("marks the default monitor status operative, because the monitor is stamped with it", () => {
351
+ const references: Array<MonitorStepsReference> =
352
+ MonitorStepsReferenceExtractor.extract(stepsWithCriteria([]));
353
+
354
+ expect(findById(references, DEFAULT_STATUS_ID)?.isOperative).toBe(true);
355
+ });
356
+
357
+ it("marks a criteria status operative only when the criteria changes the status", () => {
358
+ const on: Array<MonitorStepsReference> =
359
+ MonitorStepsReferenceExtractor.extract(
360
+ stepsWithCriteria([
361
+ criteriaInstance({
362
+ monitorStatusId: STATUS_ID,
363
+ changeMonitorStatus: true,
364
+ }),
365
+ ]),
366
+ );
367
+
368
+ const off: Array<MonitorStepsReference> =
369
+ MonitorStepsReferenceExtractor.extract(
370
+ stepsWithCriteria([
371
+ criteriaInstance({
372
+ monitorStatusId: STATUS_ID,
373
+ changeMonitorStatus: false,
374
+ }),
375
+ ]),
376
+ );
377
+
378
+ expect(findById(on, STATUS_ID)?.isOperative).toBe(true);
379
+ expect(findById(off, STATUS_ID)?.isOperative).toBe(false);
380
+ });
381
+
382
+ it("marks incident template ids operative only when the criteria creates incidents", () => {
383
+ const incidents: Array<JSONValue> = [
384
+ {
385
+ id: INCIDENT_TEMPLATE_ID,
386
+ title: "t",
387
+ description: "",
388
+ incidentSeverityId: objectIdJSON(INCIDENT_SEVERITY_ID),
389
+ onCallPolicyIds: [objectIdJSON(ON_CALL_POLICY_ID)],
390
+ },
391
+ ];
392
+
393
+ const on: Array<MonitorStepsReference> =
394
+ MonitorStepsReferenceExtractor.extract(
395
+ stepsWithCriteria([
396
+ criteriaInstance({ createIncidents: true, incidents: incidents }),
397
+ ]),
398
+ );
399
+
400
+ const off: Array<MonitorStepsReference> =
401
+ MonitorStepsReferenceExtractor.extract(
402
+ stepsWithCriteria([
403
+ criteriaInstance({ createIncidents: false, incidents: incidents }),
404
+ ]),
405
+ );
406
+
407
+ expect(findById(on, INCIDENT_SEVERITY_ID)?.isOperative).toBe(true);
408
+ expect(findById(on, ON_CALL_POLICY_ID)?.isOperative).toBe(true);
409
+ expect(findById(off, INCIDENT_SEVERITY_ID)?.isOperative).toBe(false);
410
+ expect(findById(off, ON_CALL_POLICY_ID)?.isOperative).toBe(false);
411
+ });
412
+
413
+ it("marks alert template ids operative only when the criteria creates alerts", () => {
414
+ const alerts: Array<JSONValue> = [
415
+ {
416
+ id: ALERT_TEMPLATE_ID,
417
+ title: "t",
418
+ description: "",
419
+ alertSeverityId: objectIdJSON(ALERT_SEVERITY_ID),
420
+ },
421
+ ];
422
+
423
+ const on: Array<MonitorStepsReference> =
424
+ MonitorStepsReferenceExtractor.extract(
425
+ stepsWithCriteria([
426
+ criteriaInstance({ createAlerts: true, alerts: alerts }),
427
+ ]),
428
+ );
429
+
430
+ const off: Array<MonitorStepsReference> =
431
+ MonitorStepsReferenceExtractor.extract(
432
+ stepsWithCriteria([
433
+ criteriaInstance({ createAlerts: false, alerts: alerts }),
434
+ ]),
435
+ );
436
+
437
+ expect(findById(on, ALERT_SEVERITY_ID)?.isOperative).toBe(true);
438
+ expect(findById(off, ALERT_SEVERITY_ID)?.isOperative).toBe(false);
439
+ });
440
+
441
+ it("never marks a telemetry service or network device operative", () => {
442
+ /*
443
+ * Neither can produce the foreign key violation this guard exists for,
444
+ * both already degrade on their own when the target is gone, and
445
+ * deleting a telemetry service is ordinary enough that requiring it would
446
+ * refuse every later edit of a monitor that once watched it.
447
+ */
448
+ const references: Array<MonitorStepsReference> =
449
+ MonitorStepsReferenceExtractor.extract(
450
+ stepsWithCriteria([], {
451
+ logMonitor: {
452
+ telemetryServiceIds: [objectIdJSON(TELEMETRY_SERVICE_ID)],
453
+ },
454
+ networkDeviceMonitor: { networkDeviceId: NETWORK_DEVICE_ID },
455
+ }),
456
+ );
457
+
458
+ expect(findById(references, TELEMETRY_SERVICE_ID)?.isOperative).toBe(
459
+ false,
460
+ );
461
+ expect(findById(references, NETWORK_DEVICE_ID)?.isOperative).toBe(false);
462
+ });
463
+ });
464
+
465
+ describe("the shapes monitorSteps actually arrives in", () => {
466
+ it("walks a live MonitorSteps object, not just its serialized form", () => {
467
+ const monitorSteps: MonitorSteps = MonitorSteps.fromJSON(
468
+ stepsWithCriteria([
469
+ criteriaInstance({
470
+ monitorStatusId: STATUS_ID,
471
+ changeMonitorStatus: true,
472
+ }),
473
+ ]),
474
+ );
475
+
476
+ const references: Array<MonitorStepsReference> =
477
+ MonitorStepsReferenceExtractor.extract(monitorSteps);
478
+
479
+ expect(
480
+ idsFor(references, MonitorStepsReferenceModel.MonitorStatus).sort(),
481
+ ).toEqual([DEFAULT_STATUS_ID, STATUS_ID].sort());
482
+ });
483
+
484
+ it("accepts a payload with no _type/value envelope", () => {
485
+ /*
486
+ * A hand-written REST body does not have to carry the envelopes the
487
+ * dashboard emits, and it must be walked just as thoroughly.
488
+ */
489
+ const references: Array<MonitorStepsReference> =
490
+ MonitorStepsReferenceExtractor.extract({
491
+ defaultMonitorStatusId: DEFAULT_STATUS_ID,
492
+ monitorStepsInstanceArray: [
493
+ {
494
+ id: STEP_ID,
495
+ monitorCriteria: {
496
+ monitorCriteriaInstanceArray: [
497
+ {
498
+ id: CRITERIA_ID,
499
+ monitorStatusId: STATUS_ID,
500
+ changeMonitorStatus: true,
501
+ incidents: [],
502
+ alerts: [],
503
+ },
504
+ ],
505
+ },
506
+ },
507
+ ],
508
+ });
509
+
510
+ expect(
511
+ idsFor(references, MonitorStepsReferenceModel.MonitorStatus).sort(),
512
+ ).toEqual([DEFAULT_STATUS_ID, STATUS_ID].sort());
513
+ });
514
+
515
+ it("reads an id whether it is a bare uuid or an ObjectID envelope", () => {
516
+ const bare: Array<MonitorStepsReference> =
517
+ MonitorStepsReferenceExtractor.extract(
518
+ stepsWithCriteria([
519
+ criteriaInstance({
520
+ createIncidents: true,
521
+ incidents: [
522
+ {
523
+ id: INCIDENT_TEMPLATE_ID,
524
+ title: "t",
525
+ description: "",
526
+ incidentSeverityId: INCIDENT_SEVERITY_ID,
527
+ },
528
+ ],
529
+ }),
530
+ ]),
531
+ );
532
+
533
+ const wrapped: Array<MonitorStepsReference> =
534
+ MonitorStepsReferenceExtractor.extract(
535
+ stepsWithCriteria([
536
+ criteriaInstance({
537
+ createIncidents: true,
538
+ incidents: [
539
+ {
540
+ id: INCIDENT_TEMPLATE_ID,
541
+ title: "t",
542
+ description: "",
543
+ incidentSeverityId: objectIdJSON(INCIDENT_SEVERITY_ID),
544
+ },
545
+ ],
546
+ }),
547
+ ]),
548
+ );
549
+
550
+ expect(idsFor(bare, MonitorStepsReferenceModel.IncidentSeverity)).toEqual(
551
+ [INCIDENT_SEVERITY_ID],
552
+ );
553
+ expect(
554
+ idsFor(wrapped, MonitorStepsReferenceModel.IncidentSeverity),
555
+ ).toEqual([INCIDENT_SEVERITY_ID]);
556
+ });
557
+
558
+ it("walks every step and every criteria, not just the first", () => {
559
+ const references: Array<MonitorStepsReference> =
560
+ MonitorStepsReferenceExtractor.extract({
561
+ _type: ObjectType.MonitorSteps,
562
+ value: {
563
+ defaultMonitorStatusId: DEFAULT_STATUS_ID,
564
+ monitorStepsInstanceArray: [
565
+ {
566
+ _type: ObjectType.MonitorStep,
567
+ value: {
568
+ id: STEP_ID,
569
+ monitorCriteria: {
570
+ _type: ObjectType.MonitorCriteria,
571
+ value: {
572
+ monitorCriteriaInstanceArray: [
573
+ criteriaInstance({
574
+ monitorStatusId: STATUS_ID,
575
+ changeMonitorStatus: true,
576
+ }),
577
+ ],
578
+ },
579
+ },
580
+ },
581
+ },
582
+ {
583
+ _type: ObjectType.MonitorStep,
584
+ value: {
585
+ id: STEP_ID,
586
+ monitorCriteria: {
587
+ _type: ObjectType.MonitorCriteria,
588
+ value: {
589
+ monitorCriteriaInstanceArray: [
590
+ criteriaInstance({
591
+ createAlerts: true,
592
+ alerts: [
593
+ {
594
+ id: ALERT_TEMPLATE_ID,
595
+ title: "t",
596
+ description: "",
597
+ alertSeverityId: objectIdJSON(ALERT_SEVERITY_ID),
598
+ },
599
+ ],
600
+ }),
601
+ ],
602
+ },
603
+ },
604
+ },
605
+ },
606
+ ],
607
+ },
608
+ });
609
+
610
+ expect(
611
+ idsFor(references, MonitorStepsReferenceModel.MonitorStatus),
612
+ ).toContain(STATUS_ID);
613
+ expect(
614
+ idsFor(references, MonitorStepsReferenceModel.AlertSeverity),
615
+ ).toEqual([ALERT_SEVERITY_ID]);
616
+ });
617
+
618
+ it("returns nothing rather than throwing on absent or malformed input", () => {
619
+ /*
620
+ * monitorSteps is a free-form JSON column and older rows can be
621
+ * malformed — MonitorSteps.fromDatabase is deliberately lenient about
622
+ * that for the same reason. A guard that threw here would make such a row
623
+ * impossible to save OR delete.
624
+ */
625
+ expect(MonitorStepsReferenceExtractor.extract(undefined)).toEqual([]);
626
+ expect(MonitorStepsReferenceExtractor.extract(null)).toEqual([]);
627
+ expect(MonitorStepsReferenceExtractor.extract({})).toEqual([]);
628
+ expect(
629
+ MonitorStepsReferenceExtractor.extract({
630
+ _type: ObjectType.MonitorSteps,
631
+ value: { monitorStepsInstanceArray: "not an array" },
632
+ }),
633
+ ).toEqual([]);
634
+ expect(
635
+ MonitorStepsReferenceExtractor.extract({
636
+ _type: ObjectType.MonitorSteps,
637
+ value: {
638
+ monitorStepsInstanceArray: [null, 7, "nope", { value: null }],
639
+ },
640
+ }),
641
+ ).toEqual([]);
642
+ expect(
643
+ MonitorStepsReferenceExtractor.extract(new MonitorSteps()),
644
+ ).toEqual([]);
645
+ });
646
+ });
647
+
648
+ describe("path — where the message points the user", () => {
649
+ it("names the criteria and the field", () => {
650
+ const references: Array<MonitorStepsReference> =
651
+ MonitorStepsReferenceExtractor.extract(
652
+ stepsWithCriteria([
653
+ criteriaInstance({
654
+ monitorStatusId: STATUS_ID,
655
+ changeMonitorStatus: true,
656
+ createIncidents: true,
657
+ incidents: [
658
+ {
659
+ id: INCIDENT_TEMPLATE_ID,
660
+ title: "t",
661
+ description: "",
662
+ incidentSeverityId: objectIdJSON(INCIDENT_SEVERITY_ID),
663
+ onCallPolicyIds: [objectIdJSON(ON_CALL_POLICY_ID)],
664
+ },
665
+ ],
666
+ }),
667
+ ]),
668
+ );
669
+
670
+ expect(findById(references, DEFAULT_STATUS_ID)?.path).toBe(
671
+ "default monitor status",
672
+ );
673
+ expect(findById(references, STATUS_ID)?.path).toBe(
674
+ 'criteria "Monitor is offline" monitor status',
675
+ );
676
+ expect(findById(references, INCIDENT_SEVERITY_ID)?.path).toBe(
677
+ 'criteria "Monitor is offline" incident severity',
678
+ );
679
+ expect(findById(references, ON_CALL_POLICY_ID)?.path).toBe(
680
+ 'criteria "Monitor is offline" incident on-call policy',
681
+ );
682
+ });
683
+
684
+ it("falls back to the criteria's position when it has no name", () => {
685
+ const references: Array<MonitorStepsReference> =
686
+ MonitorStepsReferenceExtractor.extract(
687
+ stepsWithCriteria([
688
+ criteriaInstance({
689
+ name: "",
690
+ monitorStatusId: STATUS_ID,
691
+ changeMonitorStatus: true,
692
+ }),
693
+ ]),
694
+ );
695
+
696
+ expect(findById(references, STATUS_ID)?.path).toBe(
697
+ 'criteria "#1" monitor status',
698
+ );
699
+ });
700
+ });
701
+
702
+ describe("toReferenceId", () => {
703
+ it("reads every shape an id arrives in", () => {
704
+ expect(toReferenceId(STATUS_ID)).toBe(STATUS_ID);
705
+ expect(toReferenceId(new ObjectID(STATUS_ID))).toBe(STATUS_ID);
706
+ expect(toReferenceId(objectIdJSON(STATUS_ID))).toBe(STATUS_ID);
707
+ // The marker-less `{ value }` shape MonitorCriteriaInstance.fromJSON accepts.
708
+ expect(toReferenceId({ value: STATUS_ID })).toBe(STATUS_ID);
709
+ expect(toReferenceId(` ${STATUS_ID} `)).toBe(STATUS_ID);
710
+ });
711
+
712
+ it("treats absent, blank and non-id values as no reference", () => {
713
+ expect(toReferenceId(undefined)).toBeNull();
714
+ expect(toReferenceId(null)).toBeNull();
715
+ expect(toReferenceId("")).toBeNull();
716
+ expect(toReferenceId(" ")).toBeNull();
717
+ expect(toReferenceId(objectIdJSON(""))).toBeNull();
718
+ expect(toReferenceId(new ObjectID(""))).toBeNull();
719
+ expect(toReferenceId(42)).toBeNull();
720
+ expect(toReferenceId([STATUS_ID] as unknown as JSONValue)).toBeNull();
721
+ expect(toReferenceId({ notAValue: STATUS_ID })).toBeNull();
722
+ });
723
+ });
724
+ });