@oneuptime/common 11.7.1 → 11.7.2

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 (237) hide show
  1. package/Models/DatabaseModels/DashboardDomain.ts +1 -0
  2. package/Models/DatabaseModels/Incident.ts +2 -0
  3. package/Models/DatabaseModels/IncidentEpisode.ts +1 -0
  4. package/Models/DatabaseModels/IncidentEpisodePublicNote.ts +1 -0
  5. package/Models/DatabaseModels/IncidentEpisodeStateTimeline.ts +1 -0
  6. package/Models/DatabaseModels/IncidentPublicNote.ts +1 -0
  7. package/Models/DatabaseModels/IncidentStateTimeline.ts +1 -0
  8. package/Models/DatabaseModels/Monitor.ts +1 -0
  9. package/Models/DatabaseModels/MonitorStatusTimeline.ts +1 -0
  10. package/Models/DatabaseModels/NetworkSite.ts +1 -0
  11. package/Models/DatabaseModels/NetworkSiteStatusTimeline.ts +1 -0
  12. package/Models/DatabaseModels/OnCallDutyPolicyExecutionLog.ts +1 -0
  13. package/Models/DatabaseModels/OnCallDutyPolicySchedule.ts +2 -0
  14. package/Models/DatabaseModels/Probe.ts +13 -13
  15. package/Models/DatabaseModels/ScheduledMaintenance.ts +1 -0
  16. package/Models/DatabaseModels/ScheduledMaintenancePublicNote.ts +1 -0
  17. package/Models/DatabaseModels/ScheduledMaintenanceStateTimeline.ts +1 -0
  18. package/Models/DatabaseModels/ScheduledMaintenanceTemplate.ts +1 -0
  19. package/Models/DatabaseModels/StatusPage.ts +1 -0
  20. package/Models/DatabaseModels/StatusPageAnnouncement.ts +1 -0
  21. package/Models/DatabaseModels/StatusPageDomain.ts +1 -0
  22. package/Models/DatabaseModels/WorkspaceNotificationSummary.ts +1 -0
  23. package/Models/DatabaseModels/WorkspaceProjectAuthToken.ts +11 -0
  24. package/Server/API/BaseAPI.ts +17 -1
  25. package/Server/API/CommonAPI.ts +32 -0
  26. package/Server/API/MicrosoftTeamsAPI.ts +99 -2
  27. package/Server/API/ProbeAPI.ts +2 -0
  28. package/Server/API/SlackAPI.ts +62 -0
  29. package/Server/API/StatusPageAPI.ts +157 -192
  30. package/Server/Infrastructure/Postgres/SchemaMigrations/1785140242697-AddHotQueryIndexes.ts +89 -0
  31. package/Server/Infrastructure/Postgres/SchemaMigrations/1785148065137-AddHotQueryIndexesSecondPass.ts +61 -0
  32. package/Server/Infrastructure/Postgres/SchemaMigrations/1785240000000-RepairCrossProjectMonitorStatusReferences.ts +280 -0
  33. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +6 -0
  34. package/Server/Services/DatabaseService.ts +11 -3
  35. package/Server/Services/MonitorGroupService.ts +145 -0
  36. package/Server/Services/MonitorService.ts +231 -30
  37. package/Server/Services/ProbeService.ts +319 -16
  38. package/Server/Services/StatusPageService.ts +72 -42
  39. package/Server/Services/WorkspaceNotificationRuleService.ts +232 -2
  40. package/Server/Utils/Database/PostgresErrorTranslator.ts +131 -0
  41. package/Server/Utils/Monitor/MonitorResource.ts +88 -35
  42. package/Server/Utils/Monitor/MonitorStatusTimeline.ts +21 -1
  43. package/Server/Utils/Monitor/MonitorStepsProjectValidator.ts +158 -0
  44. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +706 -6
  45. package/Tests/Models/DatabaseModels/ProbeColumnAccessControl.test.ts +103 -0
  46. package/Tests/Server/API/BaseAPIUpdateNoOp.test.ts +135 -0
  47. package/Tests/Server/API/CommonAPIAuthGuard.test.ts +317 -0
  48. package/Tests/Server/API/MicrosoftTeamsManifest.test.ts +228 -0
  49. package/Tests/Server/API/ProbeAPI.test.ts +6 -0
  50. package/Tests/Server/API/ZZVerifyChatsAuth.test.ts +167 -0
  51. package/Tests/Server/Services/AddHotQueryIndexesMigration.test.ts +255 -0
  52. package/Tests/Server/Services/AddHotQueryIndexesSecondPassMigration.test.ts +258 -0
  53. package/Tests/Server/Services/DatabaseServiceSanitizeUpdateData.test.ts +1 -1
  54. package/Tests/Server/Services/DiscoveryScanClaimHookFreeSafety.test.ts +121 -0
  55. package/Tests/Server/Services/HeartbeatWriteFastPathSafety.test.ts +200 -0
  56. package/Tests/Server/Services/HookFreeWriteSafetySecondPass.test.ts +181 -0
  57. package/Tests/Server/Services/MonitorGroupServiceBatchedStatuses.test.ts +513 -0
  58. package/Tests/Server/Services/MonitorResourceProbeAgreementReuse.test.ts +492 -0
  59. package/Tests/Server/Services/MonitorServiceProbeSelection.test.ts +401 -0
  60. package/Tests/Server/Services/MonitorStatusTimelineFastPath.test.ts +453 -0
  61. package/Tests/Server/Services/ProbeAuthKeyCacheAndHeartbeat.test.ts +679 -0
  62. package/Tests/Server/Services/ServiceLevelObjectiveService.test.ts +3 -3
  63. package/Tests/Server/Services/StatusPageDomainResolutionCache.test.ts +245 -0
  64. package/Tests/Server/Services/StatusPageOverviewTimelineWindow.test.ts +283 -0
  65. package/Tests/Server/Services/StatusPageServiceMcp.test.ts +17 -1
  66. package/Tests/Server/Services/StatusPageServiceMonitorGroupBatching.test.ts +331 -0
  67. package/Tests/Server/Services/UserOnCallLogClaimNotificationRule.test.ts +12 -12
  68. package/Tests/Server/Services/WorkspaceNotificationRuleChats.test.ts +1390 -0
  69. package/Tests/Server/Utils/AI/Insights/InsightFixRouting.test.ts +4 -4
  70. package/Tests/Server/Utils/AI/Insights/InsightScanner.test.ts +2 -2
  71. package/Tests/Server/Utils/AI/Insights/InsightStore.test.ts +1 -3
  72. package/Tests/Server/Utils/AI/Insights/InsightStoreHardening.test.ts +1 -3
  73. package/Tests/Server/Utils/AI/Insights/InsightTriage.test.ts +1 -1
  74. package/Tests/Server/Utils/AI/Insights/InsightTriageRunner.test.ts +5 -5
  75. package/Tests/Server/Utils/Alert/AlertPrivacyFilter.test.ts +187 -0
  76. package/Tests/Server/Utils/AlertEpisode/AlertEpisodePrivacyFilter.test.ts +170 -0
  77. package/Tests/Server/Utils/Database/PostgresErrorTranslator.test.ts +125 -0
  78. package/Tests/Server/Utils/Incident/IncidentPrivacyFilter.test.ts +264 -0
  79. package/Tests/Server/Utils/IncidentEpisode/IncidentEpisodePrivacyFilter.test.ts +175 -0
  80. package/Tests/Server/Utils/Monitor/MonitorStepsProjectValidator.test.ts +181 -0
  81. package/Tests/Server/Utils/Monitor/NetworkDeviceWalkUtil.test.ts +1 -1
  82. package/Tests/Server/Utils/Monitor/NetworkInventoryUtil.test.ts +2 -2
  83. package/Tests/Server/Utils/Workspace/MicrosoftTeamsChannelsList.test.ts +341 -0
  84. package/Tests/Server/Utils/Workspace/MicrosoftTeamsChats.test.ts +2175 -0
  85. package/Tests/Server/Utils/Workspace/SlackChannelsList.test.ts +585 -0
  86. package/Tests/Types/Billing/MeteredPlan.test.ts +46 -0
  87. package/Tests/Types/Workspace/NotificationRules/NotificationRuleCondition.test.ts +219 -1
  88. package/Tests/UI/Components/BulkUpdateForm.test.tsx +174 -0
  89. package/Tests/UI/Components/CardModelDetailEdit.test.tsx +315 -0
  90. package/Tests/UI/Components/ModelDetailSelect.test.tsx +229 -0
  91. package/Tests/UI/Components/ModelTable/BaseModelTableBulkSelectAll.test.tsx +1258 -0
  92. package/Tests/UI/Components/ModelTableExportFromColumns.test.ts +153 -0
  93. package/Tests/UI/Components/MonacoLoader.test.ts +67 -0
  94. package/Tests/UI/Components/MonacoRuntime.test.ts +121 -0
  95. package/Tests/UI/Components/TableBulkCsvExport.test.tsx +212 -3
  96. package/Tests/UI/Utils/Project.test.ts +809 -0
  97. package/Tests/UI/Utils/TableColumnsToCsv.test.ts +360 -0
  98. package/Tests/Utils/Alerts/AlertMetricType.test.ts +143 -0
  99. package/Tests/Utils/Dashboard/VariableUrlState.test.ts +219 -0
  100. package/Tests/Utils/Incident/IncidentMetricType.test.ts +174 -0
  101. package/Tests/Utils/Monitor/MonitorProbeSelectionUtil.test.ts +145 -0
  102. package/Tests/Utils/StatusPage/ResourceUptime.test.ts +263 -0
  103. package/Types/Workspace/NotificationRules/BaseNotificationRule.ts +4 -0
  104. package/Types/Workspace/NotificationRules/NotificationRuleCondition.ts +25 -10
  105. package/Types/Workspace/WorkspaceMessagePayload.ts +1 -0
  106. package/UI/Components/BulkUpdate/BulkUpdateForm.tsx +63 -8
  107. package/UI/Components/CodeEditor/CodeEditor.tsx +3 -0
  108. package/UI/Components/CodeEditor/MonacoLoader.ts +36 -0
  109. package/UI/Components/Forms/ModelForm.tsx +41 -18
  110. package/UI/Components/ModelDetail/CardModelDetail.tsx +23 -1
  111. package/UI/Components/ModelDetail/ModelDetail.tsx +46 -1
  112. package/UI/Components/ModelTable/BaseModelTable.tsx +300 -30
  113. package/UI/Components/ModelTable/Column.ts +12 -0
  114. package/UI/Components/ModelTable/ExportFromColumns.ts +57 -0
  115. package/UI/Components/Navbar/NavBar.tsx +66 -1
  116. package/UI/Components/Table/Table.tsx +52 -13
  117. package/UI/Components/Table/Types/Column.ts +18 -0
  118. package/UI/Utils/Project.ts +214 -18
  119. package/UI/Utils/TableColumnsToCsv.ts +86 -6
  120. package/UI/esbuild-config.js +34 -0
  121. package/Utils/Monitor/MonitorProbeSelectionUtil.ts +72 -0
  122. package/build/dist/Models/DatabaseModels/DashboardDomain.js +1 -0
  123. package/build/dist/Models/DatabaseModels/DashboardDomain.js.map +1 -1
  124. package/build/dist/Models/DatabaseModels/Incident.js +2 -0
  125. package/build/dist/Models/DatabaseModels/Incident.js.map +1 -1
  126. package/build/dist/Models/DatabaseModels/IncidentEpisode.js +1 -0
  127. package/build/dist/Models/DatabaseModels/IncidentEpisode.js.map +1 -1
  128. package/build/dist/Models/DatabaseModels/IncidentEpisodePublicNote.js +1 -0
  129. package/build/dist/Models/DatabaseModels/IncidentEpisodePublicNote.js.map +1 -1
  130. package/build/dist/Models/DatabaseModels/IncidentEpisodeStateTimeline.js +1 -0
  131. package/build/dist/Models/DatabaseModels/IncidentEpisodeStateTimeline.js.map +1 -1
  132. package/build/dist/Models/DatabaseModels/IncidentPublicNote.js +1 -0
  133. package/build/dist/Models/DatabaseModels/IncidentPublicNote.js.map +1 -1
  134. package/build/dist/Models/DatabaseModels/IncidentStateTimeline.js +1 -0
  135. package/build/dist/Models/DatabaseModels/IncidentStateTimeline.js.map +1 -1
  136. package/build/dist/Models/DatabaseModels/Monitor.js +1 -0
  137. package/build/dist/Models/DatabaseModels/Monitor.js.map +1 -1
  138. package/build/dist/Models/DatabaseModels/MonitorStatusTimeline.js +1 -0
  139. package/build/dist/Models/DatabaseModels/MonitorStatusTimeline.js.map +1 -1
  140. package/build/dist/Models/DatabaseModels/NetworkSite.js +1 -0
  141. package/build/dist/Models/DatabaseModels/NetworkSite.js.map +1 -1
  142. package/build/dist/Models/DatabaseModels/NetworkSiteStatusTimeline.js +1 -0
  143. package/build/dist/Models/DatabaseModels/NetworkSiteStatusTimeline.js.map +1 -1
  144. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyExecutionLog.js +1 -0
  145. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyExecutionLog.js.map +1 -1
  146. package/build/dist/Models/DatabaseModels/OnCallDutyPolicySchedule.js +2 -0
  147. package/build/dist/Models/DatabaseModels/OnCallDutyPolicySchedule.js.map +1 -1
  148. package/build/dist/Models/DatabaseModels/Probe.js +13 -13
  149. package/build/dist/Models/DatabaseModels/Probe.js.map +1 -1
  150. package/build/dist/Models/DatabaseModels/ScheduledMaintenance.js +1 -0
  151. package/build/dist/Models/DatabaseModels/ScheduledMaintenance.js.map +1 -1
  152. package/build/dist/Models/DatabaseModels/ScheduledMaintenancePublicNote.js +1 -0
  153. package/build/dist/Models/DatabaseModels/ScheduledMaintenancePublicNote.js.map +1 -1
  154. package/build/dist/Models/DatabaseModels/ScheduledMaintenanceStateTimeline.js +1 -0
  155. package/build/dist/Models/DatabaseModels/ScheduledMaintenanceStateTimeline.js.map +1 -1
  156. package/build/dist/Models/DatabaseModels/ScheduledMaintenanceTemplate.js +1 -0
  157. package/build/dist/Models/DatabaseModels/ScheduledMaintenanceTemplate.js.map +1 -1
  158. package/build/dist/Models/DatabaseModels/StatusPage.js +1 -0
  159. package/build/dist/Models/DatabaseModels/StatusPage.js.map +1 -1
  160. package/build/dist/Models/DatabaseModels/StatusPageAnnouncement.js +1 -0
  161. package/build/dist/Models/DatabaseModels/StatusPageAnnouncement.js.map +1 -1
  162. package/build/dist/Models/DatabaseModels/StatusPageDomain.js +1 -0
  163. package/build/dist/Models/DatabaseModels/StatusPageDomain.js.map +1 -1
  164. package/build/dist/Models/DatabaseModels/WorkspaceNotificationSummary.js +1 -0
  165. package/build/dist/Models/DatabaseModels/WorkspaceNotificationSummary.js.map +1 -1
  166. package/build/dist/Models/DatabaseModels/WorkspaceProjectAuthToken.js.map +1 -1
  167. package/build/dist/Server/API/BaseAPI.js +12 -1
  168. package/build/dist/Server/API/BaseAPI.js.map +1 -1
  169. package/build/dist/Server/API/CommonAPI.js +21 -0
  170. package/build/dist/Server/API/CommonAPI.js.map +1 -1
  171. package/build/dist/Server/API/MicrosoftTeamsAPI.js +72 -2
  172. package/build/dist/Server/API/MicrosoftTeamsAPI.js.map +1 -1
  173. package/build/dist/Server/API/ProbeAPI.js +2 -0
  174. package/build/dist/Server/API/ProbeAPI.js.map +1 -1
  175. package/build/dist/Server/API/SlackAPI.js +44 -0
  176. package/build/dist/Server/API/SlackAPI.js.map +1 -1
  177. package/build/dist/Server/API/StatusPageAPI.js +98 -152
  178. package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
  179. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785140242697-AddHotQueryIndexes.js +36 -0
  180. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785140242697-AddHotQueryIndexes.js.map +1 -0
  181. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785148065137-AddHotQueryIndexesSecondPass.js +26 -0
  182. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785148065137-AddHotQueryIndexesSecondPass.js.map +1 -0
  183. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785240000000-RepairCrossProjectMonitorStatusReferences.js +254 -0
  184. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785240000000-RepairCrossProjectMonitorStatusReferences.js.map +1 -0
  185. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +6 -0
  186. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  187. package/build/dist/Server/Services/DatabaseService.js +10 -2
  188. package/build/dist/Server/Services/DatabaseService.js.map +1 -1
  189. package/build/dist/Server/Services/MonitorGroupService.js +118 -0
  190. package/build/dist/Server/Services/MonitorGroupService.js.map +1 -1
  191. package/build/dist/Server/Services/MonitorService.js +198 -24
  192. package/build/dist/Server/Services/MonitorService.js.map +1 -1
  193. package/build/dist/Server/Services/ProbeService.js +238 -9
  194. package/build/dist/Server/Services/ProbeService.js.map +1 -1
  195. package/build/dist/Server/Services/StatusPageService.js +46 -39
  196. package/build/dist/Server/Services/StatusPageService.js.map +1 -1
  197. package/build/dist/Server/Services/WorkspaceNotificationRuleService.js +178 -3
  198. package/build/dist/Server/Services/WorkspaceNotificationRuleService.js.map +1 -1
  199. package/build/dist/Server/Utils/Database/PostgresErrorTranslator.js +80 -0
  200. package/build/dist/Server/Utils/Database/PostgresErrorTranslator.js.map +1 -0
  201. package/build/dist/Server/Utils/Monitor/MonitorResource.js +67 -29
  202. package/build/dist/Server/Utils/Monitor/MonitorResource.js.map +1 -1
  203. package/build/dist/Server/Utils/Monitor/MonitorStatusTimeline.js +31 -14
  204. package/build/dist/Server/Utils/Monitor/MonitorStatusTimeline.js.map +1 -1
  205. package/build/dist/Server/Utils/Monitor/MonitorStepsProjectValidator.js +117 -0
  206. package/build/dist/Server/Utils/Monitor/MonitorStepsProjectValidator.js.map +1 -0
  207. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +549 -7
  208. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
  209. package/build/dist/Types/Workspace/NotificationRules/NotificationRuleCondition.js +17 -7
  210. package/build/dist/Types/Workspace/NotificationRules/NotificationRuleCondition.js.map +1 -1
  211. package/build/dist/UI/Components/BulkUpdate/BulkUpdateForm.js +37 -11
  212. package/build/dist/UI/Components/BulkUpdate/BulkUpdateForm.js.map +1 -1
  213. package/build/dist/UI/Components/CodeEditor/CodeEditor.js +2 -0
  214. package/build/dist/UI/Components/CodeEditor/CodeEditor.js.map +1 -1
  215. package/build/dist/UI/Components/CodeEditor/MonacoLoader.js +34 -0
  216. package/build/dist/UI/Components/CodeEditor/MonacoLoader.js.map +1 -0
  217. package/build/dist/UI/Components/Forms/ModelForm.js +37 -17
  218. package/build/dist/UI/Components/Forms/ModelForm.js.map +1 -1
  219. package/build/dist/UI/Components/ModelDetail/CardModelDetail.js +22 -1
  220. package/build/dist/UI/Components/ModelDetail/CardModelDetail.js.map +1 -1
  221. package/build/dist/UI/Components/ModelDetail/ModelDetail.js +31 -1
  222. package/build/dist/UI/Components/ModelDetail/ModelDetail.js.map +1 -1
  223. package/build/dist/UI/Components/ModelTable/BaseModelTable.js +234 -21
  224. package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
  225. package/build/dist/UI/Components/ModelTable/ExportFromColumns.js +30 -0
  226. package/build/dist/UI/Components/ModelTable/ExportFromColumns.js.map +1 -0
  227. package/build/dist/UI/Components/Navbar/NavBar.js +39 -1
  228. package/build/dist/UI/Components/Navbar/NavBar.js.map +1 -1
  229. package/build/dist/UI/Components/Table/Table.js +31 -11
  230. package/build/dist/UI/Components/Table/Table.js.map +1 -1
  231. package/build/dist/UI/Utils/Project.js +161 -15
  232. package/build/dist/UI/Utils/Project.js.map +1 -1
  233. package/build/dist/UI/Utils/TableColumnsToCsv.js +67 -6
  234. package/build/dist/UI/Utils/TableColumnsToCsv.js.map +1 -1
  235. package/build/dist/Utils/Monitor/MonitorProbeSelectionUtil.js +52 -0
  236. package/build/dist/Utils/Monitor/MonitorProbeSelectionUtil.js.map +1 -0
  237. package/package.json +4 -3
@@ -185,7 +185,7 @@ describe("InsightFixRouting.routeInsightFix", () => {
185
185
  const create: jest.SpyInstance = jest
186
186
  .spyOn(TelemetryExceptionService, "createCodeFixRunForException")
187
187
  .mockResolvedValue({ id: fixRunId } as unknown as AIRun);
188
- jest.spyOn(AIRunService, "updateOneById").mockResolvedValue(undefined);
188
+ jest.spyOn(AIRunService, "updateOneById").mockResolvedValue(1);
189
189
 
190
190
  const explicitlyOn: InsightFixRoutingResult =
191
191
  await InsightFixRouting.routeInsightFix({
@@ -303,7 +303,7 @@ describe("InsightFixRouting.routeInsightFix", () => {
303
303
  .mockResolvedValue({ id: fixRunId } as unknown as AIRun);
304
304
  const stamp: jest.SpyInstance = jest
305
305
  .spyOn(AIRunService, "updateOneById")
306
- .mockResolvedValue(undefined);
306
+ .mockResolvedValue(1);
307
307
 
308
308
  const result: InsightFixRoutingResult =
309
309
  await InsightFixRouting.routeInsightFix({
@@ -339,7 +339,7 @@ describe("InsightFixRouting.routeInsightFix", () => {
339
339
  .mockResolvedValue({ id: fixRunId } as unknown as AIRun);
340
340
  const stamp: jest.SpyInstance = jest
341
341
  .spyOn(AIRunService, "updateOneById")
342
- .mockResolvedValue(undefined);
342
+ .mockResolvedValue(1);
343
343
 
344
344
  const result: InsightFixRoutingResult =
345
345
  await InsightFixRouting.routeInsightFix({
@@ -382,7 +382,7 @@ describe("InsightFixRouting.routeInsightFix", () => {
382
382
  const createFromFindings: jest.SpyInstance = jest
383
383
  .spyOn(FixPerformanceTaskTrigger, "createPerformanceFixTaskFromFindings")
384
384
  .mockResolvedValue({ id: fixRunId } as unknown as AIRun);
385
- jest.spyOn(AIRunService, "updateOneById").mockResolvedValue(undefined);
385
+ jest.spyOn(AIRunService, "updateOneById").mockResolvedValue(1);
386
386
 
387
387
  await InsightFixRouting.routeInsightFix({
388
388
  insight: makeLatencyInsight({ traceId: undefined }),
@@ -289,7 +289,7 @@ describe("InsightScanner — routing newly created insights", () => {
289
289
  jest.spyOn(AIInsightService, "findBy").mockResolvedValue([]);
290
290
  updateOneById = jest
291
291
  .spyOn(AIInsightService, "updateOneById")
292
- .mockResolvedValue(undefined);
292
+ .mockResolvedValue(1);
293
293
  routeInsightFix = jest
294
294
  .spyOn(InsightFixRouting, "routeInsightFix")
295
295
  .mockResolvedValue({});
@@ -477,7 +477,7 @@ describe("InsightScanner — the self-heal sweep for insights stranded in Detect
477
477
  });
478
478
  updateOneById = jest
479
479
  .spyOn(AIInsightService, "updateOneById")
480
- .mockResolvedValue(undefined);
480
+ .mockResolvedValue(1);
481
481
  routeInsightFix = jest
482
482
  .spyOn(InsightFixRouting, "routeInsightFix")
483
483
  .mockResolvedValue({});
@@ -74,9 +74,7 @@ function mockCreate(): jest.SpyInstance {
74
74
  }
75
75
 
76
76
  function mockUpdateOneById(): jest.SpyInstance {
77
- return jest
78
- .spyOn(AIInsightService, "updateOneById")
79
- .mockResolvedValue(undefined);
77
+ return jest.spyOn(AIInsightService, "updateOneById").mockResolvedValue(1);
80
78
  }
81
79
 
82
80
  describe("InsightStore.upsertCandidates — dedupe matrix", () => {
@@ -83,9 +83,7 @@ function mockFindOneBy(existing: AIInsight | null): jest.SpyInstance {
83
83
  }
84
84
 
85
85
  function mockUpdateOneById(): jest.SpyInstance {
86
- return jest
87
- .spyOn(AIInsightService, "updateOneById")
88
- .mockResolvedValue(undefined);
86
+ return jest.spyOn(AIInsightService, "updateOneById").mockResolvedValue(1);
89
87
  }
90
88
 
91
89
  describe("InsightStore — column-length clamps", () => {
@@ -160,7 +160,7 @@ describe("InsightTriage.enqueueInsightTriage", () => {
160
160
  .mockResolvedValue(triageRunId);
161
161
  const persist: jest.SpyInstance = jest
162
162
  .spyOn(AIInsightService, "updateOneById")
163
- .mockResolvedValue(undefined);
163
+ .mockResolvedValue(1);
164
164
 
165
165
  const result: InsightTriageResult =
166
166
  await InsightTriage.enqueueInsightTriage({ insight: makeInsight() });
@@ -194,7 +194,7 @@ describe("InsightTriageRunner.executeTriage", () => {
194
194
  .mockResolvedValue(makeInsight());
195
195
  const persist: jest.SpyInstance = jest
196
196
  .spyOn(AIInsightService, "updateOneById")
197
- .mockResolvedValue(undefined);
197
+ .mockResolvedValue(1);
198
198
  const incidentFeed: jest.SpyInstance = jest
199
199
  .spyOn(IncidentFeedService, "createIncidentFeedItem")
200
200
  .mockResolvedValue(undefined);
@@ -258,7 +258,7 @@ describe("InsightTriageRunner.executeTriage", () => {
258
258
  .mockResolvedValue(makeInsight({ telemetryExceptionId }));
259
259
  const persistInsight: jest.SpyInstance = jest
260
260
  .spyOn(AIInsightService, "updateOneById")
261
- .mockResolvedValue(undefined);
261
+ .mockResolvedValue(1);
262
262
  const flipStatus: jest.SpyInstance = jest
263
263
  .spyOn(AIInsightService, "updateOneBy")
264
264
  .mockResolvedValue(undefined as never);
@@ -342,7 +342,7 @@ describe("InsightTriageRunner.executeTriage", () => {
342
342
  );
343
343
  const persistInsight: jest.SpyInstance = jest
344
344
  .spyOn(AIInsightService, "updateOneById")
345
- .mockResolvedValue(undefined);
345
+ .mockResolvedValue(1);
346
346
  const flipStatus: jest.SpyInstance = jest
347
347
  .spyOn(AIInsightService, "updateOneBy")
348
348
  .mockResolvedValue(undefined as never);
@@ -389,7 +389,7 @@ describe("InsightTriageRunner.executeTriage", () => {
389
389
  jest
390
390
  .spyOn(AIInsightService, "findOneById")
391
391
  .mockResolvedValue(makeInsight({ telemetryExceptionId }));
392
- jest.spyOn(AIInsightService, "updateOneById").mockResolvedValue(undefined);
392
+ jest.spyOn(AIInsightService, "updateOneById").mockResolvedValue(1);
393
393
  const exceptionWrites: jest.SpyInstance = jest
394
394
  .spyOn(TelemetryExceptionService, "updateOneById")
395
395
  .mockResolvedValue(undefined as never);
@@ -432,7 +432,7 @@ describe("InsightTriageRunner.executeTriage", () => {
432
432
  jest
433
433
  .spyOn(AIInsightService, "findOneById")
434
434
  .mockResolvedValue(makeInsight({ telemetryExceptionId }));
435
- jest.spyOn(AIInsightService, "updateOneById").mockResolvedValue(undefined);
435
+ jest.spyOn(AIInsightService, "updateOneById").mockResolvedValue(1);
436
436
  const exceptionWrites: jest.SpyInstance = jest
437
437
  .spyOn(TelemetryExceptionService, "updateOneById")
438
438
  .mockResolvedValue(undefined as never);
@@ -0,0 +1,187 @@
1
+ import {
2
+ applyAlertRelatedRecordPrivacyFilter,
3
+ applyAlertSelfPrivacyFilter,
4
+ getAlertRelatedRecordPrivacyRaw,
5
+ getAlertSelfPrivacyRaw,
6
+ shouldBypassAlertPrivacy,
7
+ } from "../../../../Server/Utils/Alert/AlertPrivacyFilter";
8
+ import DatabaseCommonInteractionProps from "../../../../Types/BaseDatabase/DatabaseCommonInteractionProps";
9
+ import ObjectID from "../../../../Types/ObjectID";
10
+ import Permission, {
11
+ UserPermission,
12
+ UserTenantAccessPermission,
13
+ } from "../../../../Types/Permission";
14
+ import { describe, expect, test } from "@jest/globals";
15
+ import { FindOperator } from "typeorm";
16
+
17
+ /*
18
+ * AlertPrivacyFilter is the alert-side twin of IncidentPrivacyFilter: private
19
+ * alerts are visible to owners (directly or via team), and to project
20
+ * owners/admins/root. Each branch is pinned against the SQL actually emitted.
21
+ */
22
+
23
+ const userId: ObjectID = ObjectID.generate();
24
+ const tenantId: ObjectID = ObjectID.generate();
25
+
26
+ const propsWithProjectPermission: (
27
+ permission: Permission,
28
+ ) => DatabaseCommonInteractionProps = (
29
+ permission: Permission,
30
+ ): DatabaseCommonInteractionProps => {
31
+ const userPermission: UserPermission = {
32
+ _type: "UserPermission",
33
+ permission: permission,
34
+ labelIds: [],
35
+ };
36
+
37
+ const tenantPermission: UserTenantAccessPermission = {
38
+ _type: "UserTenantAccessPermission",
39
+ projectId: tenantId,
40
+ permissions: [userPermission],
41
+ };
42
+
43
+ return {
44
+ userId: userId,
45
+ tenantId: tenantId,
46
+ userTenantAccessPermission: {
47
+ [tenantId.toString()]: tenantPermission,
48
+ },
49
+ };
50
+ };
51
+
52
+ /* eslint-disable @typescript-eslint/no-explicit-any */
53
+ type RawParts = { sql: string; params: Record<string, string> };
54
+
55
+ const rawParts: (clause: unknown) => RawParts = (clause: unknown): RawParts => {
56
+ const operator: any = clause as any;
57
+
58
+ return {
59
+ sql: String(operator._getSql("COLUMN")),
60
+ params: (operator._objectLiteralParameters || {}) as Record<string, string>,
61
+ };
62
+ };
63
+ /* eslint-enable @typescript-eslint/no-explicit-any */
64
+
65
+ describe("shouldBypassAlertPrivacy", () => {
66
+ test("root, master-admin, project owner and project admin bypass", () => {
67
+ expect(shouldBypassAlertPrivacy({ isRoot: true })).toBe(true);
68
+ expect(shouldBypassAlertPrivacy({ isMasterAdmin: true })).toBe(true);
69
+ expect(
70
+ shouldBypassAlertPrivacy(
71
+ propsWithProjectPermission(Permission.ProjectOwner),
72
+ ),
73
+ ).toBe(true);
74
+ expect(
75
+ shouldBypassAlertPrivacy(
76
+ propsWithProjectPermission(Permission.ProjectAdmin),
77
+ ),
78
+ ).toBe(true);
79
+ });
80
+
81
+ test("an ordinary member, or a permission under another tenant, does not bypass", () => {
82
+ expect(
83
+ shouldBypassAlertPrivacy(
84
+ propsWithProjectPermission(Permission.ProjectMember),
85
+ ),
86
+ ).toBe(false);
87
+
88
+ const wrongTenant: DatabaseCommonInteractionProps =
89
+ propsWithProjectPermission(Permission.ProjectOwner);
90
+ wrongTenant.tenantId = ObjectID.generate();
91
+ expect(shouldBypassAlertPrivacy(wrongTenant)).toBe(false);
92
+
93
+ expect(shouldBypassAlertPrivacy({})).toBe(false);
94
+ });
95
+ });
96
+
97
+ describe("getAlertSelfPrivacyRaw", () => {
98
+ test("bypassing callers get no clause", () => {
99
+ expect(getAlertSelfPrivacyRaw({ isRoot: true })).toBeUndefined();
100
+ });
101
+
102
+ test("anonymous callers only see non-private alerts, no params bound", () => {
103
+ const { sql, params }: RawParts = rawParts(getAlertSelfPrivacyRaw({}));
104
+
105
+ expect(sql).toContain("COLUMN IS NULL OR COLUMN = FALSE");
106
+ expect(sql).not.toContain("AlertOwnerUser");
107
+ expect(Object.keys(params)).toHaveLength(0);
108
+ });
109
+
110
+ test("a user also sees alerts they own directly or via a team, bound to their id", () => {
111
+ const { sql, params }: RawParts = rawParts(
112
+ getAlertSelfPrivacyRaw({ userId }),
113
+ );
114
+
115
+ expect(sql).toContain("AlertOwnerUser");
116
+ expect(sql).toContain("AlertOwnerTeam");
117
+ expect(sql).toContain("TeamMember");
118
+ expect(sql).toContain(`aou."deletedAt" IS NULL`);
119
+ expect(Object.values(params)).toEqual([userId.toString()]);
120
+ });
121
+
122
+ test("fresh parameter name per call", () => {
123
+ const first: RawParts = rawParts(getAlertSelfPrivacyRaw({ userId }));
124
+ const second: RawParts = rawParts(getAlertSelfPrivacyRaw({ userId }));
125
+ expect(Object.keys(first.params)).not.toEqual(Object.keys(second.params));
126
+ });
127
+ });
128
+
129
+ describe("getAlertRelatedRecordPrivacyRaw", () => {
130
+ test("anonymous callers are scoped to child rows of non-private alerts", () => {
131
+ const { sql, params }: RawParts = rawParts(
132
+ getAlertRelatedRecordPrivacyRaw({}),
133
+ );
134
+
135
+ expect(sql).toContain(`FROM "Alert" a`);
136
+ expect(sql).toContain(`a."isPrivate" IS NULL OR a."isPrivate" = FALSE`);
137
+ expect(Object.keys(params)).toHaveLength(0);
138
+ });
139
+
140
+ test("a user's child rows also include alerts they own", () => {
141
+ const { sql, params }: RawParts = rawParts(
142
+ getAlertRelatedRecordPrivacyRaw({ userId }),
143
+ );
144
+
145
+ expect(sql).toContain("AlertOwnerUser");
146
+ expect(Object.values(params)).toEqual([userId.toString()]);
147
+ });
148
+ });
149
+
150
+ describe("applyAlertSelfPrivacyFilter / applyAlertRelatedRecordPrivacyFilter", () => {
151
+ test("bypassing callers get their query back untouched", () => {
152
+ const query: { isPrivate?: unknown } = {};
153
+ expect(applyAlertSelfPrivacyFilter(query, { isRoot: true })).toBe(query);
154
+ });
155
+
156
+ test("self filter sets isPrivate and keeps other keys", () => {
157
+ const projectId: ObjectID = ObjectID.generate();
158
+ const result: { projectId: ObjectID; isPrivate?: unknown } =
159
+ applyAlertSelfPrivacyFilter({ projectId }, { userId });
160
+
161
+ expect(result.projectId).toBe(projectId);
162
+ expect(result.isPrivate).toBeInstanceOf(FindOperator);
163
+ });
164
+
165
+ test("self filter ANDs an existing isPrivate value", () => {
166
+ const result: { isPrivate?: unknown } = applyAlertSelfPrivacyFilter(
167
+ { isPrivate: false },
168
+ { userId },
169
+ );
170
+ /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
171
+ expect(String((result.isPrivate as any)._type)).toBe("and");
172
+ });
173
+
174
+ test("related filter builds { alertId: clause } and ANDs an existing alertId", () => {
175
+ const created: { alertId?: unknown } = applyAlertRelatedRecordPrivacyFilter(
176
+ undefined as unknown as { alertId?: unknown },
177
+ { userId },
178
+ );
179
+ expect(created.alertId).toBeInstanceOf(FindOperator);
180
+
181
+ const alertId: ObjectID = ObjectID.generate();
182
+ const combined: { alertId?: unknown } =
183
+ applyAlertRelatedRecordPrivacyFilter({ alertId }, { userId });
184
+ /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
185
+ expect(String((combined.alertId as any)._type)).toBe("and");
186
+ });
187
+ });
@@ -0,0 +1,170 @@
1
+ import {
2
+ applyAlertEpisodeRelatedRecordPrivacyFilter,
3
+ applyAlertEpisodeSelfPrivacyFilter,
4
+ getAlertEpisodeRelatedRecordPrivacyRaw,
5
+ getAlertEpisodeSelfPrivacyRaw,
6
+ shouldBypassAlertEpisodePrivacy,
7
+ } from "../../../../Server/Utils/AlertEpisode/AlertEpisodePrivacyFilter";
8
+ import DatabaseCommonInteractionProps from "../../../../Types/BaseDatabase/DatabaseCommonInteractionProps";
9
+ import ObjectID from "../../../../Types/ObjectID";
10
+ import Permission, {
11
+ UserPermission,
12
+ UserTenantAccessPermission,
13
+ } from "../../../../Types/Permission";
14
+ import { describe, expect, test } from "@jest/globals";
15
+ import { FindOperator } from "typeorm";
16
+
17
+ /*
18
+ * Alert-episode privacy is the alert-side twin of incident-episode privacy.
19
+ * The emitted SQL is pinned so a wrong table/column or a dropped soft-delete
20
+ * guard is caught.
21
+ */
22
+
23
+ const userId: ObjectID = ObjectID.generate();
24
+ const tenantId: ObjectID = ObjectID.generate();
25
+
26
+ const propsWithProjectPermission: (
27
+ permission: Permission,
28
+ ) => DatabaseCommonInteractionProps = (
29
+ permission: Permission,
30
+ ): DatabaseCommonInteractionProps => {
31
+ const userPermission: UserPermission = {
32
+ _type: "UserPermission",
33
+ permission: permission,
34
+ labelIds: [],
35
+ };
36
+
37
+ const tenantPermission: UserTenantAccessPermission = {
38
+ _type: "UserTenantAccessPermission",
39
+ projectId: tenantId,
40
+ permissions: [userPermission],
41
+ };
42
+
43
+ return {
44
+ userId: userId,
45
+ tenantId: tenantId,
46
+ userTenantAccessPermission: {
47
+ [tenantId.toString()]: tenantPermission,
48
+ },
49
+ };
50
+ };
51
+
52
+ /* eslint-disable @typescript-eslint/no-explicit-any */
53
+ type RawParts = { sql: string; params: Record<string, string> };
54
+
55
+ const rawParts: (clause: unknown) => RawParts = (clause: unknown): RawParts => {
56
+ const operator: any = clause as any;
57
+
58
+ return {
59
+ sql: String(operator._getSql("COLUMN")),
60
+ params: (operator._objectLiteralParameters || {}) as Record<string, string>,
61
+ };
62
+ };
63
+ /* eslint-enable @typescript-eslint/no-explicit-any */
64
+
65
+ describe("shouldBypassAlertEpisodePrivacy", () => {
66
+ test("root, master-admin, owner and admin bypass; member does not", () => {
67
+ expect(shouldBypassAlertEpisodePrivacy({ isRoot: true })).toBe(true);
68
+ expect(shouldBypassAlertEpisodePrivacy({ isMasterAdmin: true })).toBe(true);
69
+ expect(
70
+ shouldBypassAlertEpisodePrivacy(
71
+ propsWithProjectPermission(Permission.ProjectAdmin),
72
+ ),
73
+ ).toBe(true);
74
+ expect(
75
+ shouldBypassAlertEpisodePrivacy(
76
+ propsWithProjectPermission(Permission.ProjectMember),
77
+ ),
78
+ ).toBe(false);
79
+ });
80
+ });
81
+
82
+ describe("getAlertEpisodeSelfPrivacyRaw", () => {
83
+ test("bypassing callers get no clause", () => {
84
+ expect(getAlertEpisodeSelfPrivacyRaw({ isRoot: true })).toBeUndefined();
85
+ });
86
+
87
+ test("anonymous callers only see non-private episodes, no params bound", () => {
88
+ const { sql, params }: RawParts = rawParts(
89
+ getAlertEpisodeSelfPrivacyRaw({}),
90
+ );
91
+
92
+ expect(sql).toContain("COLUMN IS NULL OR COLUMN = FALSE");
93
+ expect(sql).not.toContain("AlertEpisodeOwnerUser");
94
+ expect(Object.keys(params)).toHaveLength(0);
95
+ });
96
+
97
+ test("a user also sees episodes they own, bound to their id", () => {
98
+ const { sql, params }: RawParts = rawParts(
99
+ getAlertEpisodeSelfPrivacyRaw({ userId }),
100
+ );
101
+
102
+ expect(sql).toContain("AlertEpisodeOwnerUser");
103
+ expect(sql).toContain("AlertEpisodeOwnerTeam");
104
+ expect(sql).toContain(`aeou."deletedAt" IS NULL`);
105
+ expect(Object.values(params)).toEqual([userId.toString()]);
106
+ });
107
+
108
+ test("fresh parameter name per call", () => {
109
+ const first: RawParts = rawParts(getAlertEpisodeSelfPrivacyRaw({ userId }));
110
+ const second: RawParts = rawParts(
111
+ getAlertEpisodeSelfPrivacyRaw({ userId }),
112
+ );
113
+ expect(Object.keys(first.params)).not.toEqual(Object.keys(second.params));
114
+ });
115
+ });
116
+
117
+ describe("getAlertEpisodeRelatedRecordPrivacyRaw", () => {
118
+ test("anonymous callers are scoped to child rows of non-private episodes", () => {
119
+ const { sql }: RawParts = rawParts(
120
+ getAlertEpisodeRelatedRecordPrivacyRaw({}),
121
+ );
122
+
123
+ expect(sql).toContain(`FROM "AlertEpisode" ae`);
124
+ expect(sql).toContain(`ae."isPrivate" IS NULL OR ae."isPrivate" = FALSE`);
125
+ });
126
+
127
+ test("a user's child rows include episodes they own", () => {
128
+ const { sql, params }: RawParts = rawParts(
129
+ getAlertEpisodeRelatedRecordPrivacyRaw({ userId }),
130
+ );
131
+
132
+ expect(sql).toContain("AlertEpisodeOwnerUser");
133
+ expect(Object.values(params)).toEqual([userId.toString()]);
134
+ });
135
+ });
136
+
137
+ describe("apply* alert-episode filters", () => {
138
+ test("bypassing callers get their query back untouched", () => {
139
+ const query: { isPrivate?: unknown } = {};
140
+ expect(applyAlertEpisodeSelfPrivacyFilter(query, { isRoot: true })).toBe(
141
+ query,
142
+ );
143
+ });
144
+
145
+ test("self filter sets isPrivate; related filter builds { alertEpisodeId }", () => {
146
+ const self: { isPrivate?: unknown } = applyAlertEpisodeSelfPrivacyFilter(
147
+ {},
148
+ { userId },
149
+ );
150
+ expect(self.isPrivate).toBeInstanceOf(FindOperator);
151
+
152
+ const related: { alertEpisodeId?: unknown } =
153
+ applyAlertEpisodeRelatedRecordPrivacyFilter(
154
+ undefined as unknown as { alertEpisodeId?: unknown },
155
+ { userId },
156
+ );
157
+ expect(related.alertEpisodeId).toBeInstanceOf(FindOperator);
158
+ });
159
+
160
+ test("an existing alertEpisodeId filter is ANDed, not replaced", () => {
161
+ const alertEpisodeId: ObjectID = ObjectID.generate();
162
+ const combined: { alertEpisodeId?: unknown } =
163
+ applyAlertEpisodeRelatedRecordPrivacyFilter(
164
+ { alertEpisodeId },
165
+ { userId },
166
+ );
167
+ /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
168
+ expect(String((combined.alertEpisodeId as any)._type)).toBe("and");
169
+ });
170
+ });
@@ -0,0 +1,125 @@
1
+ import PostgresErrorTranslator from "../../../../Server/Utils/Database/PostgresErrorTranslator";
2
+ import BadDataException from "../../../../Types/Exception/BadDataException";
3
+ import Exception from "../../../../Types/Exception/Exception";
4
+ import { describe, expect, it } from "@jest/globals";
5
+
6
+ /*
7
+ * Contract under test: TypeORM's QueryFailedError is not an Exception, so it
8
+ * used to fall through StartServer's generic handler and reach the client as a
9
+ * bare 500 "Server Error" — which is exactly what deleting a project hit when
10
+ * another project's MonitorStatusTimeline rows still referenced its statuses.
11
+ *
12
+ * The translator turns the actionable Postgres failures into a
13
+ * BadDataException (400) and leaves everything else untouched.
14
+ */
15
+
16
+ type FakeQueryFailedError = {
17
+ message: string;
18
+ code?: string | undefined;
19
+ table?: string | undefined;
20
+ detail?: string | undefined;
21
+ driverError?: unknown;
22
+ };
23
+
24
+ const foreignKeyDeleteError: () => FakeQueryFailedError =
25
+ (): FakeQueryFailedError => {
26
+ return {
27
+ message:
28
+ 'update or delete on table "MonitorStatus" violates foreign key constraint "FK_574feb4161c5216c2c7ee0faaf8" on table "MonitorStatusTimeline"',
29
+ code: "23503",
30
+ table: "MonitorStatusTimeline",
31
+ detail:
32
+ 'Key (_id)=(cfc2f04f-79cb-4344-8c54-dafe5e3a290c) is still referenced from table "MonitorStatusTimeline".',
33
+ };
34
+ };
35
+
36
+ describe("PostgresErrorTranslator", () => {
37
+ describe("foreign key violation on delete", () => {
38
+ it("translates to a BadDataException naming the blocking table", () => {
39
+ const translated: unknown = PostgresErrorTranslator.translate(
40
+ foreignKeyDeleteError(),
41
+ );
42
+
43
+ expect(translated).toBeInstanceOf(BadDataException);
44
+ expect((translated as Exception).message).toBe(
45
+ "This item cannot be deleted because Monitor Status Timeline records still reference it. Please delete those records first, and then try again.",
46
+ );
47
+ });
48
+
49
+ it("reads the pg fields off driverError when the wrapper has no code", () => {
50
+ const translated: unknown = PostgresErrorTranslator.translate({
51
+ message: "wrapped",
52
+ driverError: foreignKeyDeleteError(),
53
+ });
54
+
55
+ expect(translated).toBeInstanceOf(BadDataException);
56
+ expect((translated as Exception).message).toContain(
57
+ "Monitor Status Timeline",
58
+ );
59
+ });
60
+
61
+ it("does not leak the offending row id into the message", () => {
62
+ const translated: unknown = PostgresErrorTranslator.translate(
63
+ foreignKeyDeleteError(),
64
+ );
65
+
66
+ expect((translated as Exception).message).not.toContain(
67
+ "cfc2f04f-79cb-4344-8c54-dafe5e3a290c",
68
+ );
69
+ });
70
+ });
71
+
72
+ describe("foreign key violation on insert or update", () => {
73
+ it("translates to a BadDataException naming the missing record", () => {
74
+ const translated: unknown = PostgresErrorTranslator.translate({
75
+ message: "insert violates foreign key constraint",
76
+ code: "23503",
77
+ table: "MonitorStatusTimeline",
78
+ detail:
79
+ 'Key (monitorStatusId)=(cfc2f04f-79cb-4344-8c54-dafe5e3a290c) is not present in table "MonitorStatus".',
80
+ });
81
+
82
+ expect(translated).toBeInstanceOf(BadDataException);
83
+ expect((translated as Exception).message).toBe(
84
+ "This request references Monitor Status that does not exist. Please check the request and try again.",
85
+ );
86
+ });
87
+ });
88
+
89
+ describe("errors it should not touch", () => {
90
+ it("passes through a non-foreign-key Postgres error unchanged", () => {
91
+ const uniqueViolation: FakeQueryFailedError = {
92
+ message: "duplicate key value violates unique constraint",
93
+ code: "23505",
94
+ table: "Project",
95
+ detail: "Key (slug)=(acme) already exists.",
96
+ };
97
+
98
+ expect(PostgresErrorTranslator.translate(uniqueViolation)).toBe(
99
+ uniqueViolation,
100
+ );
101
+ });
102
+
103
+ it("passes through an ordinary Exception unchanged", () => {
104
+ const exception: BadDataException = new BadDataException("Nope");
105
+
106
+ expect(PostgresErrorTranslator.translate(exception)).toBe(exception);
107
+ });
108
+
109
+ it("passes through null and non-objects unchanged", () => {
110
+ expect(PostgresErrorTranslator.translate(null)).toBeNull();
111
+ expect(PostgresErrorTranslator.translate("boom")).toBe("boom");
112
+ });
113
+
114
+ it("passes through a 23503 with an unrecognised detail unchanged", () => {
115
+ const odd: FakeQueryFailedError = {
116
+ message: "foreign key",
117
+ code: "23503",
118
+ table: "MonitorStatusTimeline",
119
+ detail: "something we have not seen before",
120
+ };
121
+
122
+ expect(PostgresErrorTranslator.translate(odd)).toBe(odd);
123
+ });
124
+ });
125
+ });