@oneuptime/common 12.0.14 → 12.0.15

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 (195) hide show
  1. package/Models/DatabaseModels/GlobalOidc.ts +20 -0
  2. package/Models/DatabaseModels/GlobalSso.ts +20 -0
  3. package/Models/DatabaseModels/MarketingConversion.ts +19 -9
  4. package/Models/DatabaseModels/NetworkDeviceLinkRule.ts +46 -0
  5. package/Models/DatabaseModels/StatusPage.ts +7 -4
  6. package/Server/API/StatusPageAPI.ts +29 -4
  7. package/Server/EnvironmentConfig.ts +7 -0
  8. package/Server/Infrastructure/Postgres/SchemaMigrations/1787800000000-AddScopeToNetworkDeviceLinkRule.ts +19 -0
  9. package/Server/Infrastructure/Postgres/SchemaMigrations/1787900000000-AddRestrictToAttachedProjectsToGlobalSso.ts +40 -0
  10. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +4 -0
  11. package/Server/Middleware/SlackAuthorization.ts +43 -6
  12. package/Server/Middleware/UserAuthorization.ts +236 -26
  13. package/Server/Middleware/WhatsAppAuthorization.ts +43 -3
  14. package/Server/Services/GlobalOidcProjectService.ts +126 -1
  15. package/Server/Services/GlobalOidcService.ts +132 -0
  16. package/Server/Services/GlobalSsoProjectService.ts +126 -1
  17. package/Server/Services/GlobalSsoService.ts +132 -0
  18. package/Server/Services/UserEmailService.ts +4 -4
  19. package/Server/Utils/CronTab.ts +73 -0
  20. package/Server/Utils/GlobalSsoAuthorization.ts +175 -0
  21. package/Server/Utils/Marketing/ConversionUploadProvider.ts +35 -5
  22. package/Server/Utils/Marketing/Providers/GoogleAds.ts +1 -1
  23. package/Server/Utils/Marketing/Providers/LinkedIn.ts +1 -1
  24. package/Server/Utils/Marketing/Providers/Meta.ts +1 -1
  25. package/Server/Utils/Marketing/Providers/MicrosoftAds.ts +1 -1
  26. package/Server/Utils/Marketing/Providers/Reddit.ts +1 -1
  27. package/Server/Utils/Monitor/Criteria/APIRequestCriteria.ts +48 -33
  28. package/Server/Utils/Monitor/Criteria/DnsMonitorCriteria.ts +35 -30
  29. package/Server/Utils/Monitor/Criteria/DomainMonitorCriteria.ts +33 -28
  30. package/Server/Utils/Monitor/Criteria/EvaluateOverTime.ts +475 -47
  31. package/Server/Utils/Monitor/Criteria/ExternalStatusPageMonitorCriteria.ts +35 -30
  32. package/Server/Utils/Monitor/Criteria/IncomingRequestCriteria.ts +34 -28
  33. package/Server/Utils/Monitor/Criteria/SSLMonitorCriteria.ts +33 -28
  34. package/Server/Utils/Monitor/Criteria/ServerMonitorCriteria.ts +69 -40
  35. package/Server/Utils/Monitor/Criteria/SnmpMonitorCriteria.ts +35 -29
  36. package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +8 -0
  37. package/Server/Utils/Monitor/MonitorResource.ts +161 -21
  38. package/Server/Utils/StatusPageContentSecurityPolicy.ts +56 -0
  39. package/Server/Utils/StatusPageCustomizationAccess.ts +117 -0
  40. package/Tests/Server/API/StatusPageCustomizationOrigin.test.ts +348 -0
  41. package/Tests/Server/Infrastructure/Postgres/FixTotpOtpUrlAlgorithmMigration.test.ts +9 -59
  42. package/Tests/Server/Infrastructure/Postgres/SchemaMigrationsOrdering.test.ts +227 -0
  43. package/Tests/Server/Middleware/SlackAuthorization.test.ts +149 -0
  44. package/Tests/Server/Middleware/UserAuthorization.test.ts +27 -13
  45. package/Tests/Server/Middleware/UserAuthorizationGlobalSsoGovernance.test.ts +1455 -0
  46. package/Tests/Server/Middleware/UserAuthorizationSSOProvider.test.ts +91 -2
  47. package/Tests/Server/Middleware/WhatsAppAuthorization.test.ts +233 -0
  48. package/Tests/Server/Services/DiscoveryScanClaimHookFreeSafety.test.ts +21 -8
  49. package/Tests/Server/Services/GlobalSsoProviderAuthorization.test.ts +1242 -0
  50. package/Tests/Server/Services/MonitorResourceProbeAgreementHydration.test.ts +459 -0
  51. package/Tests/Server/Services/MonitorResourceProbeAgreementReuse.test.ts +10 -0
  52. package/Tests/Server/Types/AnalyticsDatabase/AggregateUtilBucketTimestamp.test.ts +416 -0
  53. package/Tests/Server/Types/Database/QueryHelperManyToManyAndEmptyValues.test.ts +551 -0
  54. package/Tests/Server/Types/Database/SelectUtil.test.ts +348 -0
  55. package/Tests/Server/Utils/CronTab.test.ts +116 -0
  56. package/Tests/Server/Utils/GlobalSSOToken.test.ts +581 -0
  57. package/Tests/Server/Utils/GlobalSsoAuthorization.test.ts +1401 -0
  58. package/Tests/Server/Utils/Marketing/AdUploadableConversionTypes.test.ts +254 -0
  59. package/Tests/Server/Utils/Marketing/ConversionUploadProvider.test.ts +1 -1
  60. package/Tests/Server/Utils/Monitor/Criteria/APIRequestCriteriaEvaluateOverTime.test.ts +508 -0
  61. package/Tests/Server/Utils/Monitor/Criteria/APIRequestCriteriaPortTimings.test.ts +30 -9
  62. package/Tests/Server/Utils/Monitor/Criteria/EvaluateOverTime.test.ts +987 -0
  63. package/Tests/Server/Utils/Monitor/Criteria/ExternalStatusPageMonitorCriteria.test.ts +916 -0
  64. package/Tests/Server/Utils/Monitor/Criteria/ProfileMonitorCriteria.test.ts +382 -0
  65. package/Tests/Server/Utils/Monitor/Criteria/ServerMonitorCriteria.test.ts +1219 -0
  66. package/Tests/Server/Utils/Monitor/MonitorResourceIngestedStepSelection.test.ts +86 -0
  67. package/Tests/Server/Utils/StatusPageContentSecurityPolicy.test.ts +136 -0
  68. package/Tests/Server/Utils/StatusPageCustomizationAccess.test.ts +223 -0
  69. package/Tests/Types/Monitor/MonitorStepConfigHelpers.test.ts +165 -0
  70. package/Tests/Types/NetworkDevice/NetworkDeviceLinkRuleScope.test.ts +215 -0
  71. package/Tests/UI/Components/Forms/FormSubmitAnalytics.test.tsx +206 -0
  72. package/Tests/UI/Utils/AIChatExport/ConversationMarkdown.test.ts +774 -0
  73. package/Tests/UI/Utils/AIChatExport/MarkdownBlocks.test.ts +791 -0
  74. package/Tests/UI/Utils/AIChatExport/MarkdownSafety.test.ts +544 -0
  75. package/Tests/Utils/Analytics.test.ts +187 -0
  76. package/Tests/Utils/Monitor/MonitorMetricType.test.ts +80 -0
  77. package/Tests/Utils/Monitor/NetworkDeviceLinkRuleUtil.test.ts +1291 -0
  78. package/Tests/Utils/NetworkDiscovery/UnclaimedScanDiagnosis.test.ts +214 -0
  79. package/Types/Marketing/MarketingConversion.ts +12 -0
  80. package/Types/Monitor/CriteriaFilter.ts +9 -0
  81. package/Types/NetworkDevice/NetworkDeviceLinkRuleScope.ts +51 -0
  82. package/UI/Components/FormModal/BasicFormModal.tsx +12 -0
  83. package/UI/Components/Forms/BasicForm.tsx +13 -1
  84. package/UI/Components/Forms/BasicModelForm.tsx +2 -1
  85. package/UI/Components/Forms/ModelForm.tsx +10 -1
  86. package/UI/Components/Forms/Utils/FormAnalyticsName.ts +39 -0
  87. package/UI/Components/ModelFormModal/ModelFormModal.tsx +6 -1
  88. package/Utils/Analytics.ts +55 -2
  89. package/Utils/Monitor/MonitorMetricType.ts +40 -0
  90. package/Utils/Monitor/NetworkDeviceLinkRuleUtil.ts +587 -37
  91. package/Utils/NetworkDiscovery/UnclaimedScanDiagnosis.ts +115 -0
  92. package/build/dist/Models/DatabaseModels/GlobalOidc.js +21 -0
  93. package/build/dist/Models/DatabaseModels/GlobalOidc.js.map +1 -1
  94. package/build/dist/Models/DatabaseModels/GlobalSso.js +21 -0
  95. package/build/dist/Models/DatabaseModels/GlobalSso.js.map +1 -1
  96. package/build/dist/Models/DatabaseModels/MarketingConversion.js +20 -10
  97. package/build/dist/Models/DatabaseModels/MarketingConversion.js.map +1 -1
  98. package/build/dist/Models/DatabaseModels/NetworkDeviceLinkRule.js +47 -0
  99. package/build/dist/Models/DatabaseModels/NetworkDeviceLinkRule.js.map +1 -1
  100. package/build/dist/Models/DatabaseModels/StatusPage.js +4 -4
  101. package/build/dist/Models/DatabaseModels/StatusPage.js.map +1 -1
  102. package/build/dist/Server/API/StatusPageAPI.js +25 -4
  103. package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
  104. package/build/dist/Server/EnvironmentConfig.js +6 -0
  105. package/build/dist/Server/EnvironmentConfig.js.map +1 -1
  106. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787800000000-AddScopeToNetworkDeviceLinkRule.js +12 -0
  107. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787800000000-AddScopeToNetworkDeviceLinkRule.js.map +1 -0
  108. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787900000000-AddRestrictToAttachedProjectsToGlobalSso.js +29 -0
  109. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787900000000-AddRestrictToAttachedProjectsToGlobalSso.js.map +1 -0
  110. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +4 -0
  111. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  112. package/build/dist/Server/Middleware/SlackAuthorization.js +28 -4
  113. package/build/dist/Server/Middleware/SlackAuthorization.js.map +1 -1
  114. package/build/dist/Server/Middleware/UserAuthorization.js +197 -22
  115. package/build/dist/Server/Middleware/UserAuthorization.js.map +1 -1
  116. package/build/dist/Server/Middleware/WhatsAppAuthorization.js +32 -2
  117. package/build/dist/Server/Middleware/WhatsAppAuthorization.js.map +1 -1
  118. package/build/dist/Server/Services/GlobalOidcProjectService.js +105 -0
  119. package/build/dist/Server/Services/GlobalOidcProjectService.js.map +1 -1
  120. package/build/dist/Server/Services/GlobalOidcService.js +127 -0
  121. package/build/dist/Server/Services/GlobalOidcService.js.map +1 -1
  122. package/build/dist/Server/Services/GlobalSsoProjectService.js +105 -0
  123. package/build/dist/Server/Services/GlobalSsoProjectService.js.map +1 -1
  124. package/build/dist/Server/Services/GlobalSsoService.js +127 -0
  125. package/build/dist/Server/Services/GlobalSsoService.js.map +1 -1
  126. package/build/dist/Server/Services/UserEmailService.js +4 -4
  127. package/build/dist/Server/Utils/CronTab.js +66 -0
  128. package/build/dist/Server/Utils/CronTab.js.map +1 -1
  129. package/build/dist/Server/Utils/GlobalSsoAuthorization.js +112 -0
  130. package/build/dist/Server/Utils/GlobalSsoAuthorization.js.map +1 -0
  131. package/build/dist/Server/Utils/Marketing/ConversionUploadProvider.js +17 -1
  132. package/build/dist/Server/Utils/Marketing/ConversionUploadProvider.js.map +1 -1
  133. package/build/dist/Server/Utils/Marketing/Providers/GoogleAds.js +1 -1
  134. package/build/dist/Server/Utils/Marketing/Providers/GoogleAds.js.map +1 -1
  135. package/build/dist/Server/Utils/Marketing/Providers/LinkedIn.js +1 -1
  136. package/build/dist/Server/Utils/Marketing/Providers/LinkedIn.js.map +1 -1
  137. package/build/dist/Server/Utils/Marketing/Providers/Meta.js +1 -1
  138. package/build/dist/Server/Utils/Marketing/Providers/Meta.js.map +1 -1
  139. package/build/dist/Server/Utils/Marketing/Providers/MicrosoftAds.js +1 -1
  140. package/build/dist/Server/Utils/Marketing/Providers/MicrosoftAds.js.map +1 -1
  141. package/build/dist/Server/Utils/Marketing/Providers/Reddit.js +1 -1
  142. package/build/dist/Server/Utils/Marketing/Providers/Reddit.js.map +1 -1
  143. package/build/dist/Server/Utils/Monitor/Criteria/APIRequestCriteria.js +37 -35
  144. package/build/dist/Server/Utils/Monitor/Criteria/APIRequestCriteria.js.map +1 -1
  145. package/build/dist/Server/Utils/Monitor/Criteria/DnsMonitorCriteria.js +21 -25
  146. package/build/dist/Server/Utils/Monitor/Criteria/DnsMonitorCriteria.js.map +1 -1
  147. package/build/dist/Server/Utils/Monitor/Criteria/DomainMonitorCriteria.js +20 -22
  148. package/build/dist/Server/Utils/Monitor/Criteria/DomainMonitorCriteria.js.map +1 -1
  149. package/build/dist/Server/Utils/Monitor/Criteria/EvaluateOverTime.js +370 -42
  150. package/build/dist/Server/Utils/Monitor/Criteria/EvaluateOverTime.js.map +1 -1
  151. package/build/dist/Server/Utils/Monitor/Criteria/ExternalStatusPageMonitorCriteria.js +21 -25
  152. package/build/dist/Server/Utils/Monitor/Criteria/ExternalStatusPageMonitorCriteria.js.map +1 -1
  153. package/build/dist/Server/Utils/Monitor/Criteria/IncomingRequestCriteria.js +21 -25
  154. package/build/dist/Server/Utils/Monitor/Criteria/IncomingRequestCriteria.js.map +1 -1
  155. package/build/dist/Server/Utils/Monitor/Criteria/SSLMonitorCriteria.js +21 -25
  156. package/build/dist/Server/Utils/Monitor/Criteria/SSLMonitorCriteria.js.map +1 -1
  157. package/build/dist/Server/Utils/Monitor/Criteria/ServerMonitorCriteria.js +64 -52
  158. package/build/dist/Server/Utils/Monitor/Criteria/ServerMonitorCriteria.js.map +1 -1
  159. package/build/dist/Server/Utils/Monitor/Criteria/SnmpMonitorCriteria.js +25 -29
  160. package/build/dist/Server/Utils/Monitor/Criteria/SnmpMonitorCriteria.js.map +1 -1
  161. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +8 -0
  162. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
  163. package/build/dist/Server/Utils/Monitor/MonitorResource.js +123 -23
  164. package/build/dist/Server/Utils/Monitor/MonitorResource.js.map +1 -1
  165. package/build/dist/Server/Utils/StatusPageContentSecurityPolicy.js +39 -0
  166. package/build/dist/Server/Utils/StatusPageContentSecurityPolicy.js.map +1 -0
  167. package/build/dist/Server/Utils/StatusPageCustomizationAccess.js +85 -0
  168. package/build/dist/Server/Utils/StatusPageCustomizationAccess.js.map +1 -0
  169. package/build/dist/Types/Marketing/MarketingConversion.js +10 -0
  170. package/build/dist/Types/Marketing/MarketingConversion.js.map +1 -1
  171. package/build/dist/Types/Monitor/CriteriaFilter.js +1 -0
  172. package/build/dist/Types/Monitor/CriteriaFilter.js.map +1 -1
  173. package/build/dist/Types/NetworkDevice/NetworkDeviceLinkRuleScope.js +46 -0
  174. package/build/dist/Types/NetworkDevice/NetworkDeviceLinkRuleScope.js.map +1 -0
  175. package/build/dist/UI/Components/FormModal/BasicFormModal.js +2 -1
  176. package/build/dist/UI/Components/FormModal/BasicFormModal.js.map +1 -1
  177. package/build/dist/UI/Components/Forms/BasicForm.js +10 -1
  178. package/build/dist/UI/Components/Forms/BasicForm.js.map +1 -1
  179. package/build/dist/UI/Components/Forms/BasicModelForm.js +2 -1
  180. package/build/dist/UI/Components/Forms/BasicModelForm.js.map +1 -1
  181. package/build/dist/UI/Components/Forms/ModelForm.js +4 -1
  182. package/build/dist/UI/Components/Forms/ModelForm.js.map +1 -1
  183. package/build/dist/UI/Components/Forms/Utils/FormAnalyticsName.js +33 -0
  184. package/build/dist/UI/Components/Forms/Utils/FormAnalyticsName.js.map +1 -0
  185. package/build/dist/UI/Components/ModelFormModal/ModelFormModal.js +2 -1
  186. package/build/dist/UI/Components/ModelFormModal/ModelFormModal.js.map +1 -1
  187. package/build/dist/Utils/Analytics.js +50 -2
  188. package/build/dist/Utils/Analytics.js.map +1 -1
  189. package/build/dist/Utils/Monitor/MonitorMetricType.js +37 -0
  190. package/build/dist/Utils/Monitor/MonitorMetricType.js.map +1 -1
  191. package/build/dist/Utils/Monitor/NetworkDeviceLinkRuleUtil.js +318 -37
  192. package/build/dist/Utils/Monitor/NetworkDeviceLinkRuleUtil.js.map +1 -1
  193. package/build/dist/Utils/NetworkDiscovery/UnclaimedScanDiagnosis.js +87 -0
  194. package/build/dist/Utils/NetworkDiscovery/UnclaimedScanDiagnosis.js.map +1 -0
  195. package/package.json +1 -1
@@ -0,0 +1,459 @@
1
+ /*
2
+ * Probe agreement re-runs the criteria evaluator over every OTHER probe's
3
+ * stored result before a status change is allowed. Two things about that
4
+ * pass used to make it disagree with the evaluation summary shown to the
5
+ * user on the very same incident:
6
+ *
7
+ * 1. The stored result is `JSON.parse(JSON.stringify(response))`, so its
8
+ * ObjectIDs come back as plain `{ _type, value }` objects. Those are
9
+ * not `instanceof ObjectID`, so an "evaluate over time" lookup bound
10
+ * them as-is and matched zero rows - indistinguishable from "this
11
+ * monitor has no history", which sent the evaluator down its
12
+ * instantaneous-value fallback.
13
+ * 2. The probe whose result triggered this whole pass was re-evaluated
14
+ * from that stored copy rather than reusing the verdict the caller had
15
+ * just computed, so its "over time" window was a different window.
16
+ *
17
+ * Together those produced incidents whose stored root cause quoted a single
18
+ * sample while the summary beside it reported the full window and said the
19
+ * criteria was not met. See
20
+ * https://github.com/OneUptime/oneuptime/issues/2321.
21
+ *
22
+ * As in the sibling reuse suite, everything heavy is mocked BEFORE
23
+ * importing MonitorResource.
24
+ */
25
+
26
+ /*
27
+ * MonitorResource's import chain reaches the native isolated-vm addon
28
+ * through the sandbox runner. Nothing here touches the sandbox and the
29
+ * prebuilt binary cannot always dlopen in the test environment, so stub it
30
+ * out before anything imports it.
31
+ */
32
+ jest.mock("isolated-vm", () => {
33
+ return {};
34
+ });
35
+
36
+ jest.mock("../../../Server/Utils/Monitor/MonitorCriteriaEvaluator", () => {
37
+ return {
38
+ __esModule: true,
39
+ default: {
40
+ processMonitorStep: jest.fn(),
41
+ },
42
+ };
43
+ });
44
+
45
+ jest.mock("../../../Server/Services/MonitorProbeService", () => {
46
+ return {
47
+ __esModule: true,
48
+ default: {
49
+ findBy: jest.fn(),
50
+ },
51
+ };
52
+ });
53
+
54
+ jest.mock("../../../Server/Utils/Logger", () => {
55
+ return {
56
+ __esModule: true,
57
+ default: {
58
+ debug: jest.fn(),
59
+ info: jest.fn(),
60
+ warn: jest.fn(),
61
+ error: jest.fn(),
62
+ trace: jest.fn(),
63
+ },
64
+ };
65
+ });
66
+
67
+ import MonitorResourceUtil from "../../../Server/Utils/Monitor/MonitorResource";
68
+ import MonitorCriteriaEvaluator from "../../../Server/Utils/Monitor/MonitorCriteriaEvaluator";
69
+ import Monitor from "../../../Models/DatabaseModels/Monitor";
70
+ import MonitorProbe, {
71
+ MonitorStepProbeResponse,
72
+ } from "../../../Models/DatabaseModels/MonitorProbe";
73
+ import Probe, {
74
+ ProbeConnectionStatus,
75
+ } from "../../../Models/DatabaseModels/Probe";
76
+ import MonitorStep from "../../../Types/Monitor/MonitorStep";
77
+ import ObjectID from "../../../Types/ObjectID";
78
+ import ProbeApiIngestResponse from "../../../Types/Probe/ProbeApiIngestResponse";
79
+ import ProbeMonitorResponse from "../../../Types/Probe/ProbeMonitorResponse";
80
+ import DataToProcess from "../../../Server/Utils/Monitor/DataToProcess";
81
+ import { describe, expect, test, beforeEach } from "@jest/globals";
82
+
83
+ interface ProbeAgreementResult {
84
+ hasAgreement: boolean;
85
+ agreementCount: number;
86
+ requiredCount: number;
87
+ totalActiveProbes: number;
88
+ agreedCriteriaId: string | null;
89
+ agreedRootCause: string | null;
90
+ agreedProbeNames: Array<string>;
91
+ }
92
+
93
+ const processMonitorStepMock: jest.Mock =
94
+ MonitorCriteriaEvaluator.processMonitorStep as unknown as jest.Mock;
95
+
96
+ type CheckProbeAgreementFunction = (input: {
97
+ monitor: Monitor;
98
+ monitorStep: MonitorStep;
99
+ currentCriteriaMetId: string | null;
100
+ currentRootCause: string | null;
101
+ currentProbeId?: ObjectID | undefined;
102
+ monitorProbes?: Array<MonitorProbe> | undefined;
103
+ }) => Promise<ProbeAgreementResult>;
104
+
105
+ const checkProbeAgreement: CheckProbeAgreementFunction = (
106
+ MonitorResourceUtil as any
107
+ )["checkProbeAgreement"].bind(MonitorResourceUtil);
108
+
109
+ const PROJECT_ID: string = "11111111-1111-4111-8111-111111111111";
110
+ const MONITOR_ID: string = "22222222-2222-4222-8222-222222222222";
111
+ const STEP_ID: string = "33333333-3333-4333-8333-333333333333";
112
+ const PROBE_ID: string = "44444444-4444-4444-8444-444444444444";
113
+
114
+ /**
115
+ * A probe response exactly as it comes back out of the lastMonitoringLog
116
+ * jsonb column - every ObjectID flattened to `{ _type, value }` and every
117
+ * Date to an ISO string.
118
+ */
119
+ function storedProbeResponse(
120
+ overrides: Record<string, unknown> = {},
121
+ ): ProbeMonitorResponse {
122
+ return {
123
+ projectId: { _type: "ObjectID", value: PROJECT_ID },
124
+ monitorId: { _type: "ObjectID", value: MONITOR_ID },
125
+ monitorStepId: { _type: "ObjectID", value: STEP_ID },
126
+ probeId: { _type: "ObjectID", value: PROBE_ID },
127
+ monitoredAt: "2026-08-20T12:00:00.000Z",
128
+ ingestedAt: "2026-08-20T12:00:01.000Z",
129
+ isOnline: false,
130
+ responseCode: 404,
131
+ failureCause: "",
132
+ ...overrides,
133
+ } as unknown as ProbeMonitorResponse;
134
+ }
135
+
136
+ function makeMonitorProbe(input: {
137
+ probeName: string;
138
+ probeId?: ObjectID | undefined;
139
+ lastMonitoringLog?: MonitorStepProbeResponse | undefined;
140
+ }): MonitorProbe {
141
+ const probe: Probe = new Probe();
142
+ probe.name = input.probeName;
143
+ probe.connectionStatus = ProbeConnectionStatus.Connected;
144
+
145
+ const monitorProbe: MonitorProbe = new MonitorProbe();
146
+ monitorProbe.id = ObjectID.generate();
147
+ monitorProbe.probeId = input.probeId || ObjectID.generate();
148
+ monitorProbe.isEnabled = true;
149
+ monitorProbe.probe = probe;
150
+
151
+ if (input.lastMonitoringLog !== undefined) {
152
+ monitorProbe.lastMonitoringLog = input.lastMonitoringLog;
153
+ }
154
+
155
+ return monitorProbe;
156
+ }
157
+
158
+ function makeMonitor(): Monitor {
159
+ const monitor: Monitor = new Monitor();
160
+ monitor.id = ObjectID.generate();
161
+ return monitor;
162
+ }
163
+
164
+ describe("MonitorResourceUtil.hydrateStoredProbeResponse", () => {
165
+ const hydrate: (response: ProbeMonitorResponse) => ProbeMonitorResponse = (
166
+ response: ProbeMonitorResponse,
167
+ ): ProbeMonitorResponse => {
168
+ return MonitorResourceUtil.hydrateStoredProbeResponse(response);
169
+ };
170
+
171
+ test("turns serialized ObjectIDs back into ObjectID instances", () => {
172
+ const hydrated: ProbeMonitorResponse = hydrate(storedProbeResponse());
173
+
174
+ expect(hydrated.projectId).toBeInstanceOf(ObjectID);
175
+ expect(hydrated.monitorId).toBeInstanceOf(ObjectID);
176
+ expect(hydrated.monitorStepId).toBeInstanceOf(ObjectID);
177
+ expect(hydrated.probeId).toBeInstanceOf(ObjectID);
178
+ });
179
+
180
+ test("preserves the id values themselves", () => {
181
+ const hydrated: ProbeMonitorResponse = hydrate(storedProbeResponse());
182
+
183
+ expect(hydrated.projectId.toString()).toBe(PROJECT_ID);
184
+ expect(hydrated.monitorId.toString()).toBe(MONITOR_ID);
185
+ expect(hydrated.monitorStepId.toString()).toBe(STEP_ID);
186
+ expect(hydrated.probeId.toString()).toBe(PROBE_ID);
187
+ });
188
+
189
+ test("accepts a bare id string too", () => {
190
+ const hydrated: ProbeMonitorResponse = hydrate(
191
+ storedProbeResponse({ projectId: PROJECT_ID }),
192
+ );
193
+
194
+ expect(hydrated.projectId).toBeInstanceOf(ObjectID);
195
+ expect(hydrated.projectId.toString()).toBe(PROJECT_ID);
196
+ });
197
+
198
+ test("leaves a real ObjectID alone", () => {
199
+ const projectId: ObjectID = new ObjectID(PROJECT_ID);
200
+
201
+ const hydrated: ProbeMonitorResponse = hydrate(
202
+ storedProbeResponse({ projectId: projectId }),
203
+ );
204
+
205
+ expect(hydrated.projectId).toBe(projectId);
206
+ });
207
+
208
+ test("turns serialized dates back into Date instances", () => {
209
+ const hydrated: ProbeMonitorResponse = hydrate(storedProbeResponse());
210
+
211
+ expect(hydrated.monitoredAt).toBeInstanceOf(Date);
212
+ expect(hydrated.monitoredAt.toISOString()).toBe("2026-08-20T12:00:00.000Z");
213
+ expect(hydrated.ingestedAt).toBeInstanceOf(Date);
214
+ });
215
+
216
+ test("leaves a real Date alone", () => {
217
+ const monitoredAt: Date = new Date("2026-08-20T12:00:00.000Z");
218
+
219
+ const hydrated: ProbeMonitorResponse = hydrate(
220
+ storedProbeResponse({ monitoredAt: monitoredAt }),
221
+ );
222
+
223
+ expect(hydrated.monitoredAt).toBe(monitoredAt);
224
+ });
225
+
226
+ test("carries every other field through untouched", () => {
227
+ const hydrated: ProbeMonitorResponse = hydrate(
228
+ storedProbeResponse({
229
+ responseCode: 503,
230
+ responseTimeInMs: 1234,
231
+ isOnline: false,
232
+ }),
233
+ );
234
+
235
+ expect(hydrated.responseCode).toBe(503);
236
+ expect(hydrated.responseTimeInMs).toBe(1234);
237
+ expect(hydrated.isOnline).toBe(false);
238
+ });
239
+
240
+ test("does not mutate the stored row", () => {
241
+ const stored: ProbeMonitorResponse = storedProbeResponse();
242
+
243
+ hydrate(stored);
244
+
245
+ expect(stored.projectId).not.toBeInstanceOf(ObjectID);
246
+ });
247
+
248
+ test("tolerates missing ids and dates", () => {
249
+ const sparse: ProbeMonitorResponse = {
250
+ isOnline: true,
251
+ } as unknown as ProbeMonitorResponse;
252
+
253
+ expect(() => {
254
+ hydrate(sparse);
255
+ }).not.toThrow();
256
+ });
257
+
258
+ test("tolerates an unparseable date", () => {
259
+ const hydrated: ProbeMonitorResponse = hydrate(
260
+ storedProbeResponse({ monitoredAt: "not-a-date" }),
261
+ );
262
+
263
+ // Left as-is rather than replaced with an Invalid Date.
264
+ expect(hydrated.monitoredAt).toBe("not-a-date" as unknown as Date);
265
+ });
266
+ });
267
+
268
+ describe("MonitorResourceUtil.checkProbeAgreement stored-payload handling", () => {
269
+ let monitorStep: MonitorStep;
270
+ let stepId: string;
271
+
272
+ beforeEach(() => {
273
+ jest.clearAllMocks();
274
+
275
+ monitorStep = new MonitorStep();
276
+ stepId = monitorStep.id.toString();
277
+
278
+ processMonitorStepMock.mockImplementation(
279
+ (input: {
280
+ dataToProcess: DataToProcess;
281
+ monitor: Monitor;
282
+ }): Promise<ProbeApiIngestResponse> => {
283
+ return Promise.resolve({
284
+ monitorId: input.monitor.id!,
285
+ criteriaMetId: (input.dataToProcess as any).testCriteriaId,
286
+ rootCause: (input.dataToProcess as any).testRootCause || null,
287
+ });
288
+ },
289
+ );
290
+ });
291
+
292
+ test("hydrates a stored payload before handing it to the evaluator", async () => {
293
+ const monitor: Monitor = makeMonitor();
294
+
295
+ const otherProbe: MonitorProbe = makeMonitorProbe({
296
+ probeName: "Probe B",
297
+ lastMonitoringLog: {
298
+ [stepId]: storedProbeResponse({ testCriteriaId: "criteria-c" }),
299
+ },
300
+ });
301
+
302
+ await checkProbeAgreement({
303
+ monitor: monitor,
304
+ monitorStep: monitorStep,
305
+ currentCriteriaMetId: "criteria-c",
306
+ currentRootCause: "root cause C",
307
+ monitorProbes: [otherProbe],
308
+ });
309
+
310
+ expect(processMonitorStepMock).toHaveBeenCalledTimes(1);
311
+
312
+ const handed: ProbeMonitorResponse = (
313
+ processMonitorStepMock.mock.calls[0]![0] as {
314
+ dataToProcess: ProbeMonitorResponse;
315
+ }
316
+ ).dataToProcess;
317
+
318
+ expect(handed.projectId).toBeInstanceOf(ObjectID);
319
+ expect(handed.projectId.toString()).toBe(PROJECT_ID);
320
+ expect(handed.monitorId).toBeInstanceOf(ObjectID);
321
+ expect(handed.monitoredAt).toBeInstanceOf(Date);
322
+ });
323
+
324
+ /*
325
+ * The caller has already evaluated the probe whose result triggered this
326
+ * pass, and that evaluation is the one the user sees. Re-running it here
327
+ * would read a different "over time" window seconds later.
328
+ */
329
+ test("reuses the caller's verdict for the probe being handled", async () => {
330
+ const monitor: Monitor = makeMonitor();
331
+ const currentProbeId: ObjectID = new ObjectID(PROBE_ID);
332
+
333
+ const currentProbe: MonitorProbe = makeMonitorProbe({
334
+ probeName: "Probe A",
335
+ probeId: currentProbeId,
336
+ lastMonitoringLog: {
337
+ // Deliberately different: it must NOT be what the result reports.
338
+ [stepId]: storedProbeResponse({ testCriteriaId: "stale-criteria" }),
339
+ },
340
+ });
341
+
342
+ const result: ProbeAgreementResult = await checkProbeAgreement({
343
+ monitor: monitor,
344
+ monitorStep: monitorStep,
345
+ currentCriteriaMetId: "criteria-live",
346
+ currentRootCause: "root cause live",
347
+ currentProbeId: currentProbeId,
348
+ monitorProbes: [currentProbe],
349
+ });
350
+
351
+ expect(processMonitorStepMock).not.toHaveBeenCalled();
352
+ expect(result.agreedCriteriaId).toBe("criteria-live");
353
+ expect(result.agreedRootCause).toBe("root cause live");
354
+ expect(result.agreementCount).toBe(1);
355
+ });
356
+
357
+ test("still evaluates the other probes", async () => {
358
+ const monitor: Monitor = makeMonitor();
359
+ const currentProbeId: ObjectID = new ObjectID(PROBE_ID);
360
+
361
+ const currentProbe: MonitorProbe = makeMonitorProbe({
362
+ probeName: "Probe A",
363
+ probeId: currentProbeId,
364
+ lastMonitoringLog: {
365
+ [stepId]: storedProbeResponse({ testCriteriaId: "criteria-live" }),
366
+ },
367
+ });
368
+
369
+ const otherProbe: MonitorProbe = makeMonitorProbe({
370
+ probeName: "Probe B",
371
+ lastMonitoringLog: {
372
+ [stepId]: storedProbeResponse({ testCriteriaId: "criteria-live" }),
373
+ },
374
+ });
375
+
376
+ const result: ProbeAgreementResult = await checkProbeAgreement({
377
+ monitor: monitor,
378
+ monitorStep: monitorStep,
379
+ currentCriteriaMetId: "criteria-live",
380
+ currentRootCause: "root cause live",
381
+ currentProbeId: currentProbeId,
382
+ monitorProbes: [currentProbe, otherProbe],
383
+ });
384
+
385
+ expect(processMonitorStepMock).toHaveBeenCalledTimes(1);
386
+ expect(result.agreementCount).toBe(2);
387
+ expect(result.hasAgreement).toBe(true);
388
+ expect(result.agreedProbeNames).toEqual(["Probe A", "Probe B"]);
389
+ });
390
+
391
+ /*
392
+ * The current probe seeing nothing while the others see a breach must
393
+ * still count as a disagreement, not be quietly folded into the majority.
394
+ */
395
+ test("a caller verdict of 'no criteria met' is counted as its own outcome", async () => {
396
+ const monitor: Monitor = makeMonitor();
397
+ const currentProbeId: ObjectID = new ObjectID(PROBE_ID);
398
+
399
+ const currentProbe: MonitorProbe = makeMonitorProbe({
400
+ probeName: "Probe A",
401
+ probeId: currentProbeId,
402
+ lastMonitoringLog: {
403
+ [stepId]: storedProbeResponse({ testCriteriaId: "criteria-c" }),
404
+ },
405
+ });
406
+
407
+ const otherProbe: MonitorProbe = makeMonitorProbe({
408
+ probeName: "Probe B",
409
+ lastMonitoringLog: {
410
+ [stepId]: storedProbeResponse({ testCriteriaId: "criteria-c" }),
411
+ },
412
+ });
413
+
414
+ const result: ProbeAgreementResult = await checkProbeAgreement({
415
+ monitor: monitor,
416
+ monitorStep: monitorStep,
417
+ currentCriteriaMetId: null,
418
+ currentRootCause: null,
419
+ currentProbeId: currentProbeId,
420
+ monitorProbes: [currentProbe, otherProbe],
421
+ });
422
+
423
+ // Two probes, both required, but they disagree - no status change.
424
+ expect(result.hasAgreement).toBe(false);
425
+ expect(result.agreementCount).toBe(1);
426
+ });
427
+
428
+ /*
429
+ * Without currentProbeId (the incoming-request caller, for instance) the
430
+ * behaviour is unchanged: every stored row is evaluated.
431
+ */
432
+ test("evaluates every probe when no current probe is named", async () => {
433
+ const monitor: Monitor = makeMonitor();
434
+
435
+ const probeA: MonitorProbe = makeMonitorProbe({
436
+ probeName: "Probe A",
437
+ lastMonitoringLog: {
438
+ [stepId]: storedProbeResponse({ testCriteriaId: "criteria-c" }),
439
+ },
440
+ });
441
+ const probeB: MonitorProbe = makeMonitorProbe({
442
+ probeName: "Probe B",
443
+ lastMonitoringLog: {
444
+ [stepId]: storedProbeResponse({ testCriteriaId: "criteria-c" }),
445
+ },
446
+ });
447
+
448
+ const result: ProbeAgreementResult = await checkProbeAgreement({
449
+ monitor: monitor,
450
+ monitorStep: monitorStep,
451
+ currentCriteriaMetId: "criteria-c",
452
+ currentRootCause: "root cause C",
453
+ monitorProbes: [probeA, probeB],
454
+ });
455
+
456
+ expect(processMonitorStepMock).toHaveBeenCalledTimes(2);
457
+ expect(result.agreementCount).toBe(2);
458
+ });
459
+ });
@@ -28,6 +28,16 @@
28
28
  * Postgres, no Redis, no isolated-vm.
29
29
  */
30
30
 
31
+ /*
32
+ * MonitorResource's import chain reaches the native isolated-vm addon
33
+ * through the sandbox runner. Nothing here touches the sandbox and the
34
+ * prebuilt binary cannot always dlopen in the test environment, so stub it
35
+ * out before anything imports it.
36
+ */
37
+ jest.mock("isolated-vm", () => {
38
+ return {};
39
+ });
40
+
31
41
  jest.mock("../../../Server/Utils/Monitor/MonitorCriteriaEvaluator", () => {
32
42
  return {
33
43
  __esModule: true,