@oneuptime/common 12.0.6 → 12.0.7

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 (424) hide show
  1. package/Models/DatabaseModels/AIInsight.ts +1 -1
  2. package/Models/DatabaseModels/DatabaseBaseModel/FileModel.ts +2 -3
  3. package/Models/DatabaseModels/DeletedProject.ts +595 -0
  4. package/Models/DatabaseModels/Index.ts +2 -0
  5. package/Models/DatabaseModels/Monitor.ts +111 -0
  6. package/Models/DatabaseModels/Project.ts +3 -3
  7. package/Models/DatabaseModels/WorkspaceProjectAuthToken.ts +19 -1
  8. package/Server/API/FileAPI.ts +15 -2
  9. package/Server/API/MicrosoftTeamsAPI.ts +6 -1
  10. package/Server/API/ProjectAPI.ts +214 -0
  11. package/Server/API/StatusPageAPI.ts +39 -6
  12. package/Server/Infrastructure/Postgres/SchemaMigrations/1786446545142-MigrationName.ts +50 -0
  13. package/Server/Infrastructure/Postgres/SchemaMigrations/1786449497966-AddMonitorDependency.ts +67 -0
  14. package/Server/Infrastructure/Postgres/SchemaMigrations/1786461136170-AddDeletedProjectTable.ts +39 -0
  15. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +6 -0
  16. package/Server/Services/AIAgentService.ts +20 -0
  17. package/Server/Services/DashboardDomainService.ts +73 -28
  18. package/Server/Services/DatabaseService.ts +1 -0
  19. package/Server/Services/DeletedProjectService.ts +138 -0
  20. package/Server/Services/FileService.ts +61 -0
  21. package/Server/Services/IncidentEpisodePublicNoteService.ts +18 -0
  22. package/Server/Services/IncidentPublicNoteService.ts +18 -0
  23. package/Server/Services/Index.ts +2 -0
  24. package/Server/Services/MonitorService.ts +311 -0
  25. package/Server/Services/ProbeService.ts +20 -0
  26. package/Server/Services/ProjectService.ts +297 -1
  27. package/Server/Services/PushNotificationService.ts +71 -0
  28. package/Server/Services/ScheduledMaintenancePublicNoteService.ts +18 -0
  29. package/Server/Services/StatusPageAnnouncementService.ts +53 -0
  30. package/Server/Services/StatusPageDomainService.ts +73 -28
  31. package/Server/Services/TeamMemberService.ts +117 -12
  32. package/Server/Services/TelemetryIngestionKeyService.ts +1 -0
  33. package/Server/Services/UserWebhookService.ts +34 -109
  34. package/Server/Services/WorkspaceNotificationRuleService.ts +69 -0
  35. package/Server/Services/WorkspaceProjectAuthTokenService.ts +37 -1
  36. package/Server/Types/Database/DeleteById.ts +2 -0
  37. package/Server/Types/Database/DeleteOneBy.ts +6 -0
  38. package/Server/Types/Domain.ts +41 -0
  39. package/Server/Types/Workflow/Components/API/Delete.ts +2 -1
  40. package/Server/Types/Workflow/Components/API/Get.ts +2 -1
  41. package/Server/Types/Workflow/Components/API/Patch.ts +2 -1
  42. package/Server/Types/Workflow/Components/API/Post.ts +2 -1
  43. package/Server/Types/Workflow/Components/API/Put.ts +2 -1
  44. package/Server/Types/Workflow/Components/API/Utils.ts +41 -2
  45. package/Server/Types/Workflow/Components/BaseModel/CreateManyBaseModel.ts +25 -19
  46. package/Server/Types/Workflow/Components/BaseModel/CreateOneBaseModel.ts +6 -5
  47. package/Server/Types/Workflow/Components/BaseModel/DeleteManyBaseModel.ts +32 -14
  48. package/Server/Types/Workflow/Components/BaseModel/DeleteOneBaseModel.ts +31 -13
  49. package/Server/Types/Workflow/Components/BaseModel/FindManyBaseModel.ts +29 -20
  50. package/Server/Types/Workflow/Components/BaseModel/FindOneBaseModel.ts +28 -21
  51. package/Server/Types/Workflow/Components/BaseModel/LogComponentError.ts +89 -0
  52. package/Server/Types/Workflow/Components/BaseModel/ModelArguments.ts +208 -0
  53. package/Server/Types/Workflow/Components/BaseModel/OnTriggerBaseModel.ts +11 -0
  54. package/Server/Types/Workflow/Components/BaseModel/UpdateManyBaseModel.ts +41 -18
  55. package/Server/Types/Workflow/Components/BaseModel/UpdateOneBaseModel.ts +45 -19
  56. package/Server/Types/Workflow/Components/Discord/SendMessageToChannel.ts +16 -9
  57. package/Server/Types/Workflow/Components/IncomingWebhookUtils.ts +66 -0
  58. package/Server/Types/Workflow/Components/MicrosoftTeams/SendMessageToChannel.ts +15 -9
  59. package/Server/Types/Workflow/Components/Slack/SendMessageToChannel.ts +17 -0
  60. package/Server/Utils/AI/Chat/ObservabilityChatPrompt.ts +1 -1
  61. package/Server/Utils/AI/CodeFix/CodeAgentWorkspaceGuard.ts +171 -0
  62. package/Server/Utils/AI/SRE/FixFromIncidentTaskTrigger.ts +3 -3
  63. package/Server/Utils/AI/SRE/Insights/FixRouting.ts +3 -2
  64. package/Server/Utils/AI/SRE/TelemetryImprovementTaskTrigger.ts +1 -1
  65. package/Server/Utils/AI/Toolbox/CodeWriteTools.ts +6 -6
  66. package/Server/Utils/CodeRepository/GitHub/GitHub.ts +43 -5
  67. package/Server/Utils/DataSource/EgressGuard.ts +176 -16
  68. package/Server/Utils/DataSource/HttpFetch.ts +6 -45
  69. package/Server/Utils/Execute.ts +8 -0
  70. package/Server/Utils/InlineImageAccessTokenSync.ts +23 -0
  71. package/Server/Utils/LLM/LLMService.ts +57 -12
  72. package/Server/Utils/Monitor/MonitorAlert.ts +40 -0
  73. package/Server/Utils/Monitor/MonitorDependencySuppression.ts +192 -0
  74. package/Server/Utils/Monitor/MonitorIncident.ts +40 -0
  75. package/Server/Utils/Monitor/MonitorResource.ts +36 -0
  76. package/Server/Utils/Response.ts +142 -6
  77. package/Server/Utils/SSRFProtection.ts +273 -60
  78. package/Server/Utils/StartServer.ts +8 -0
  79. package/Server/Utils/VM/VMRunner.ts +88 -1
  80. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +555 -49
  81. package/Server/Utils/Workspace/Slack/Slack.ts +25 -2
  82. package/Server/Utils/Workspace/WorkspaceBase.ts +6 -0
  83. package/Tests/App/Dashboard/DangerZone.test.tsx +334 -0
  84. package/Tests/App/Dashboard/DashboardVariableAllContract.test.tsx +539 -0
  85. package/Tests/App/Dashboard/DashboardVariableSelector.test.tsx +678 -0
  86. package/Tests/App/Dashboard/DashboardVariablesDiscard.test.tsx +706 -0
  87. package/Tests/App/Dashboard/DashboardVariablesModal.test.tsx +508 -0
  88. package/Tests/App/PublicDashboard/DashboardVariableSelector.test.tsx +470 -0
  89. package/Tests/App/StatusPage/StatusPageDetailPagesNotFound.test.tsx +308 -0
  90. package/Tests/Models/DatabaseModels/DeletedProjectModel.test.ts +251 -0
  91. package/Tests/Models/DatabaseModels/SavedViewJSONColumnTuples.test.ts +115 -0
  92. package/Tests/Server/API/FileAPIAccessControl.test.ts +316 -0
  93. package/Tests/Server/API/ProjectAPI.test.ts +299 -1
  94. package/Tests/Server/API/ProjectAPIAdjustBalance.test.ts +459 -0
  95. package/Tests/Server/API/ProjectAPIAdjustBalanceBillingDisabled.test.ts +177 -0
  96. package/Tests/Server/Services/DatabaseServiceDeletionReason.test.ts +123 -0
  97. package/Tests/Server/Services/DeletedProjectService.test.ts +415 -0
  98. package/Tests/Server/Services/DeletedProjectServiceSelfHosted.test.ts +70 -0
  99. package/Tests/Server/Services/DomainSubdomainHooks.test.ts +94 -0
  100. package/Tests/Server/Services/FileIsPublicBooleanMigration.test.ts +244 -0
  101. package/Tests/Server/Services/FileServiceMimeType.test.ts +89 -0
  102. package/Tests/Server/Services/MonitorServiceDependencyValidation.test.ts +702 -0
  103. package/Tests/Server/Services/ProjectServiceAdjustBalance.test.ts +615 -0
  104. package/Tests/Server/Services/ProjectServiceDeleteAudit.test.ts +406 -0
  105. package/Tests/Server/Services/PublishTimeImageVisibility.test.ts +450 -0
  106. package/Tests/Server/Services/TeamMemberAutoAcceptInvitation.test.ts +669 -0
  107. package/Tests/Server/Services/UserWebhookSSRF.test.ts +174 -0
  108. package/Tests/Server/Services/WebPushEndpointAllowlist.test.ts +133 -0
  109. package/Tests/Server/Services/WorkspaceProjectAuthTokenRepoint.test.ts +618 -0
  110. package/Tests/Server/Types/Workflow/Components/ApiComponentErrorPort.test.ts +26 -1
  111. package/Tests/Server/Types/Workflow/Components/ApiComponentSsrf.test.ts +215 -0
  112. package/Tests/Server/Types/Workflow/Components/BaseModelDatabaseComponents.test.ts +742 -0
  113. package/Tests/Server/Types/Workflow/Components/ChatWebhookComponents.test.ts +256 -0
  114. package/Tests/Server/Types/Workflow/Components/ModelArguments.test.ts +371 -0
  115. package/Tests/Server/Utils/AI/CodeAgentCommandGuard.test.ts +204 -0
  116. package/Tests/Server/Utils/AI/CodeAgentWriteLimits.test.ts +213 -0
  117. package/Tests/Server/Utils/AI/CodeWriteTools.test.ts +52 -5
  118. package/Tests/Server/Utils/AI/LLMServiceBaseUrl.test.ts +3 -0
  119. package/Tests/Server/Utils/AI/LLMServiceEgressGuard.test.ts +315 -0
  120. package/Tests/Server/Utils/AI/LLMServiceModelCompatibility.test.ts +3 -0
  121. package/Tests/Server/Utils/AI/LLMServiceRequestPolicy.test.ts +17 -5
  122. package/Tests/Server/Utils/AI/LLMServiceToolCalling.test.ts +14 -1
  123. package/Tests/Server/Utils/AI/ObservabilityChatPrompt.test.ts +35 -0
  124. package/Tests/Server/Utils/AI/StubLLMEgressGuard.ts +25 -0
  125. package/Tests/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.test.ts +62 -7
  126. package/Tests/Server/Utils/DataSource/EgressGuard.test.ts +69 -0
  127. package/Tests/Server/Utils/DataSource/EgressGuardPinning.test.ts +330 -0
  128. package/Tests/Server/Utils/GitHubCreatePullRequestWithToken.test.ts +393 -0
  129. package/Tests/Server/Utils/InlineImageAccessTokenSync.test.ts +202 -0
  130. package/Tests/Server/Utils/Monitor/MonitorDependencySuppression.test.ts +811 -0
  131. package/Tests/Server/Utils/Monitor/MonitorDependencySuppressionCreatorSkip.test.ts +604 -0
  132. package/Tests/Server/Utils/ResponseSendFileResponse.test.ts +192 -0
  133. package/Tests/Server/Utils/SSRFProtectionBypasses.test.ts +406 -0
  134. package/Tests/Server/Utils/VM/VMRunnerSsrf.test.ts +194 -0
  135. package/Tests/Server/Utils/Workspace/MicrosoftTeamsChannelSend.test.ts +1169 -0
  136. package/Tests/Server/Utils/Workspace/MicrosoftTeamsChannelsList.test.ts +2 -0
  137. package/Tests/Server/Utils/Workspace/MicrosoftTeamsTeamInstalls.test.ts +1605 -0
  138. package/Tests/Server/Utils/Workspace/MicrosoftTeamsTenantResolution.test.ts +1218 -0
  139. package/Tests/Types/AI/AIChatMessageStatus.test.ts +79 -0
  140. package/Tests/Types/AI/AIInsightStatus.test.ts +40 -0
  141. package/Tests/Types/API/HostnameAuthorityParsing.test.ts +157 -0
  142. package/Tests/Types/Dashboard/DashboardTemplateInvariants.test.ts +27 -0
  143. package/Tests/Types/DataSource/DataSourceLimits.test.ts +71 -0
  144. package/Tests/Types/DataSource/DataSourceType.test.ts +181 -0
  145. package/Tests/Types/DomainSubdomainValidation.test.ts +90 -0
  146. package/Tests/Types/JSONFunctions.test.ts +252 -1
  147. package/Tests/Types/Monitor/SnmpMonitor/SnmpAuthProtocol.test.ts +61 -0
  148. package/Tests/Types/Monitor/SnmpMonitor/SnmpPrivProtocol.test.ts +62 -0
  149. package/Tests/Types/Monitor/SnmpMonitor/SnmpSecurityLevel.test.ts +68 -0
  150. package/Tests/Types/Monitor/SnmpMonitor/SnmpVersion.test.ts +51 -0
  151. package/Tests/UI/Components/404.test.tsx +1 -1
  152. package/Tests/UI/Components/Alert.test.tsx +1 -1
  153. package/Tests/UI/Components/Badge.test.tsx +1 -1
  154. package/Tests/UI/Components/BasicForm.test.tsx +39 -10
  155. package/Tests/UI/Components/Button.test.tsx +1 -1
  156. package/Tests/UI/Components/Card.test.tsx +1 -1
  157. package/Tests/UI/Components/Charts/AxisTickColor.test.tsx +1 -1
  158. package/Tests/UI/Components/ColorViewer.test.tsx +1 -1
  159. package/Tests/UI/Components/ComponentsModal.test.tsx +7 -1
  160. package/Tests/UI/Components/DictionaryOfStrings.test.tsx +1 -1
  161. package/Tests/UI/Components/Dropdown.test.tsx +1 -1
  162. package/Tests/UI/Components/FilePicker.test.tsx +1 -1
  163. package/Tests/UI/Components/HiddenText.test.tsx +1 -1
  164. package/Tests/UI/Components/Input.test.tsx +1 -1
  165. package/Tests/UI/Components/Loader.test.tsx +1 -1
  166. package/Tests/UI/Components/LogTimeRangePicker.test.tsx +1 -1
  167. package/Tests/UI/Components/MasterPage.test.tsx +1 -1
  168. package/Tests/UI/Components/ModalPortalDismissal.test.tsx +489 -0
  169. package/Tests/UI/Components/ModelDelete.test.tsx +214 -0
  170. package/Tests/UI/Components/ModelTable/BaseModelTableBulkSelectAll.test.tsx +1 -1
  171. package/Tests/UI/Components/ModelTable/BaseModelTableColumnCustomization.test.tsx +1 -1
  172. package/Tests/UI/Components/ModelTable/BaseModelTableSearchLabels.test.tsx +1 -1
  173. package/Tests/UI/Components/ModelTable/BaseModelTableUrlState.test.tsx +1 -1
  174. package/Tests/UI/Components/ModelTable/CustomFieldColumns.test.tsx +1 -1
  175. package/Tests/UI/Components/NavBar.test.tsx +1 -1
  176. package/Tests/UI/Components/OperatorSelectorKeyboard.test.tsx +1 -1
  177. package/Tests/UI/Components/Pagination.test.tsx +1 -1
  178. package/Tests/UI/Components/Pill.test.tsx +1 -1
  179. package/Tests/UI/Components/Probe.test.tsx +1 -1
  180. package/Tests/UI/Components/ProgressBar.test.tsx +1 -1
  181. package/Tests/UI/Components/RadioButtons.test.tsx +1 -1
  182. package/Tests/UI/Components/SideMenuItem.test.tsx +1 -1
  183. package/Tests/UI/Components/SideOver.test.tsx +500 -6
  184. package/Tests/UI/Components/Tabs.test.tsx +1 -1
  185. package/Tests/UI/Components/TelemetrySearchBar.test.tsx +1 -1
  186. package/Tests/UI/Components/TelemetryTimeRangePicker.test.tsx +1 -1
  187. package/Tests/UI/Components/TextArea.test.tsx +1 -1
  188. package/Tests/UI/Components/Toast.test.tsx +1 -1
  189. package/Tests/UI/Components/Toggle.test.tsx +1 -1
  190. package/Tests/UI/Components/TopSection.test.tsx +1 -1
  191. package/Tests/UI/Components/UseLiveLogsRefresh.test.tsx +1 -1
  192. package/Tests/UI/Rum/ReplayScrubber.test.tsx +1 -1
  193. package/Tests/UI/Rum/ReplayStage.test.tsx +1 -1
  194. package/Tests/UI/Telemetry/TelemetrySnapshotWindowAlert.test.tsx +1 -1
  195. package/Tests/UI/Utils/LocalStorageArrayRoundTrip.test.ts +72 -0
  196. package/Tests/Utils/Dashboard/HtmlWidgetDocument.test.ts +19 -2
  197. package/Tests/Utils/Dashboard/ModelQueryVariableInterpolation.test.ts +44 -0
  198. package/Tests/Utils/Dashboard/VariableInterpolation.test.ts +288 -5
  199. package/Tests/Utils/Dashboard/VariableUrlState.test.ts +42 -0
  200. package/Tests/Utils/DataSource/DataSourceQueryText.test.ts +27 -0
  201. package/Tests/Utils/LegacySerializedArray.test.ts +139 -0
  202. package/Tests/Utils/Monitor/MonitorDependencyRule.test.ts +190 -0
  203. package/Tests/Utils/Realtime.test.ts +78 -0
  204. package/Tests/Utils/Telemetry/SavedViewFilters.test.ts +246 -0
  205. package/Tests/__mocks__/i18next-browser-languagedetector.js +21 -0
  206. package/Types/AI/FixPullRequestCiStatus.ts +1 -1
  207. package/Types/AI/FixVerificationStatus.ts +2 -2
  208. package/Types/API/Hostname.ts +92 -4
  209. package/Types/API/URL.ts +43 -25
  210. package/Types/Billing/BalanceAdjustmentType.ts +14 -0
  211. package/Types/Billing/ProjectBalanceType.ts +13 -0
  212. package/Types/Domain.ts +26 -0
  213. package/Types/File/MimeType.ts +11 -0
  214. package/Types/JSONFunctions.ts +15 -0
  215. package/Types/Telemetry/TelemetrySavedViewState.ts +9 -1
  216. package/Types/Workflow/Components/BaseModel.ts +92 -28
  217. package/Typings/Index.d.ts +12 -0
  218. package/UI/Components/GanttChart/ChartContainer.tsx +4 -3
  219. package/UI/Components/Modal/ConfirmModal.tsx +10 -5
  220. package/UI/Components/Modal/Modal.tsx +27 -47
  221. package/UI/Components/ModelDelete/ModelDelete.tsx +23 -6
  222. package/UI/Components/SideOver/SideOver.tsx +195 -68
  223. package/UI/Utils/PageScrollLock.ts +66 -0
  224. package/Utils/Dashboard/HtmlWidgetDocument.ts +4 -2
  225. package/Utils/Dashboard/VariableInterpolation.ts +19 -1
  226. package/Utils/LegacySerializedArray.ts +60 -0
  227. package/Utils/Monitor/MonitorDependencyRule.ts +88 -0
  228. package/Utils/Telemetry/SavedViewFilters.ts +88 -0
  229. package/build/dist/Models/DatabaseModels/AIInsight.js +1 -1
  230. package/build/dist/Models/DatabaseModels/AIInsight.js.map +1 -1
  231. package/build/dist/Models/DatabaseModels/DatabaseBaseModel/FileModel.js +2 -3
  232. package/build/dist/Models/DatabaseModels/DatabaseBaseModel/FileModel.js.map +1 -1
  233. package/build/dist/Models/DatabaseModels/DeletedProject.js +640 -0
  234. package/build/dist/Models/DatabaseModels/DeletedProject.js.map +1 -0
  235. package/build/dist/Models/DatabaseModels/Index.js +2 -0
  236. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  237. package/build/dist/Models/DatabaseModels/Monitor.js +106 -0
  238. package/build/dist/Models/DatabaseModels/Monitor.js.map +1 -1
  239. package/build/dist/Models/DatabaseModels/Project.js +3 -3
  240. package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
  241. package/build/dist/Models/DatabaseModels/WorkspaceProjectAuthToken.js.map +1 -1
  242. package/build/dist/Server/API/FileAPI.js +14 -2
  243. package/build/dist/Server/API/FileAPI.js.map +1 -1
  244. package/build/dist/Server/API/MicrosoftTeamsAPI.js +6 -1
  245. package/build/dist/Server/API/MicrosoftTeamsAPI.js.map +1 -1
  246. package/build/dist/Server/API/ProjectAPI.js +157 -4
  247. package/build/dist/Server/API/ProjectAPI.js.map +1 -1
  248. package/build/dist/Server/API/StatusPageAPI.js +27 -3
  249. package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
  250. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786446545142-MigrationName.js +34 -0
  251. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786446545142-MigrationName.js.map +1 -0
  252. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786449497966-AddMonitorDependency.js +30 -0
  253. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786449497966-AddMonitorDependency.js.map +1 -0
  254. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786461136170-AddDeletedProjectTable.js +20 -0
  255. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786461136170-AddDeletedProjectTable.js.map +1 -0
  256. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +6 -0
  257. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  258. package/build/dist/Server/Services/AIAgentService.js +17 -0
  259. package/build/dist/Server/Services/AIAgentService.js.map +1 -1
  260. package/build/dist/Server/Services/DashboardDomainService.js +55 -18
  261. package/build/dist/Server/Services/DashboardDomainService.js.map +1 -1
  262. package/build/dist/Server/Services/DatabaseService.js +1 -0
  263. package/build/dist/Server/Services/DatabaseService.js.map +1 -1
  264. package/build/dist/Server/Services/DeletedProjectService.js +124 -0
  265. package/build/dist/Server/Services/DeletedProjectService.js.map +1 -0
  266. package/build/dist/Server/Services/FileService.js +52 -0
  267. package/build/dist/Server/Services/FileService.js.map +1 -1
  268. package/build/dist/Server/Services/IncidentEpisodePublicNoteService.js +10 -1
  269. package/build/dist/Server/Services/IncidentEpisodePublicNoteService.js.map +1 -1
  270. package/build/dist/Server/Services/IncidentPublicNoteService.js +10 -1
  271. package/build/dist/Server/Services/IncidentPublicNoteService.js.map +1 -1
  272. package/build/dist/Server/Services/Index.js +2 -0
  273. package/build/dist/Server/Services/Index.js.map +1 -1
  274. package/build/dist/Server/Services/MonitorService.js +232 -0
  275. package/build/dist/Server/Services/MonitorService.js.map +1 -1
  276. package/build/dist/Server/Services/ProbeService.js +17 -0
  277. package/build/dist/Server/Services/ProbeService.js.map +1 -1
  278. package/build/dist/Server/Services/ProjectService.js +228 -6
  279. package/build/dist/Server/Services/ProjectService.js.map +1 -1
  280. package/build/dist/Server/Services/PushNotificationService.js +56 -0
  281. package/build/dist/Server/Services/PushNotificationService.js.map +1 -1
  282. package/build/dist/Server/Services/ScheduledMaintenancePublicNoteService.js +10 -1
  283. package/build/dist/Server/Services/ScheduledMaintenancePublicNoteService.js.map +1 -1
  284. package/build/dist/Server/Services/StatusPageAnnouncementService.js +54 -0
  285. package/build/dist/Server/Services/StatusPageAnnouncementService.js.map +1 -1
  286. package/build/dist/Server/Services/StatusPageDomainService.js +55 -18
  287. package/build/dist/Server/Services/StatusPageDomainService.js.map +1 -1
  288. package/build/dist/Server/Services/TeamMemberService.js +97 -6
  289. package/build/dist/Server/Services/TeamMemberService.js.map +1 -1
  290. package/build/dist/Server/Services/TelemetryIngestionKeyService.js +1 -0
  291. package/build/dist/Server/Services/TelemetryIngestionKeyService.js.map +1 -1
  292. package/build/dist/Server/Services/UserWebhookService.js +31 -87
  293. package/build/dist/Server/Services/UserWebhookService.js.map +1 -1
  294. package/build/dist/Server/Services/WorkspaceNotificationRuleService.js +65 -0
  295. package/build/dist/Server/Services/WorkspaceNotificationRuleService.js.map +1 -1
  296. package/build/dist/Server/Services/WorkspaceProjectAuthTokenService.js +23 -0
  297. package/build/dist/Server/Services/WorkspaceProjectAuthTokenService.js.map +1 -1
  298. package/build/dist/Server/Types/Domain.js +37 -0
  299. package/build/dist/Server/Types/Domain.js.map +1 -1
  300. package/build/dist/Server/Types/Workflow/Components/API/Delete.js +2 -1
  301. package/build/dist/Server/Types/Workflow/Components/API/Delete.js.map +1 -1
  302. package/build/dist/Server/Types/Workflow/Components/API/Get.js +2 -1
  303. package/build/dist/Server/Types/Workflow/Components/API/Get.js.map +1 -1
  304. package/build/dist/Server/Types/Workflow/Components/API/Patch.js +2 -1
  305. package/build/dist/Server/Types/Workflow/Components/API/Patch.js.map +1 -1
  306. package/build/dist/Server/Types/Workflow/Components/API/Post.js +2 -1
  307. package/build/dist/Server/Types/Workflow/Components/API/Post.js.map +1 -1
  308. package/build/dist/Server/Types/Workflow/Components/API/Put.js +2 -1
  309. package/build/dist/Server/Types/Workflow/Components/API/Put.js.map +1 -1
  310. package/build/dist/Server/Types/Workflow/Components/API/Utils.js +35 -2
  311. package/build/dist/Server/Types/Workflow/Components/API/Utils.js.map +1 -1
  312. package/build/dist/Server/Types/Workflow/Components/BaseModel/CreateManyBaseModel.js +15 -11
  313. package/build/dist/Server/Types/Workflow/Components/BaseModel/CreateManyBaseModel.js.map +1 -1
  314. package/build/dist/Server/Types/Workflow/Components/BaseModel/CreateOneBaseModel.js +2 -3
  315. package/build/dist/Server/Types/Workflow/Components/BaseModel/CreateOneBaseModel.js.map +1 -1
  316. package/build/dist/Server/Types/Workflow/Components/BaseModel/DeleteManyBaseModel.js +21 -10
  317. package/build/dist/Server/Types/Workflow/Components/BaseModel/DeleteManyBaseModel.js.map +1 -1
  318. package/build/dist/Server/Types/Workflow/Components/BaseModel/DeleteOneBaseModel.js +21 -10
  319. package/build/dist/Server/Types/Workflow/Components/BaseModel/DeleteOneBaseModel.js.map +1 -1
  320. package/build/dist/Server/Types/Workflow/Components/BaseModel/FindManyBaseModel.js +18 -13
  321. package/build/dist/Server/Types/Workflow/Components/BaseModel/FindManyBaseModel.js.map +1 -1
  322. package/build/dist/Server/Types/Workflow/Components/BaseModel/FindOneBaseModel.js +18 -13
  323. package/build/dist/Server/Types/Workflow/Components/BaseModel/FindOneBaseModel.js.map +1 -1
  324. package/build/dist/Server/Types/Workflow/Components/BaseModel/LogComponentError.js +43 -0
  325. package/build/dist/Server/Types/Workflow/Components/BaseModel/LogComponentError.js.map +1 -0
  326. package/build/dist/Server/Types/Workflow/Components/BaseModel/ModelArguments.js +116 -0
  327. package/build/dist/Server/Types/Workflow/Components/BaseModel/ModelArguments.js.map +1 -0
  328. package/build/dist/Server/Types/Workflow/Components/BaseModel/OnTriggerBaseModel.js +7 -0
  329. package/build/dist/Server/Types/Workflow/Components/BaseModel/OnTriggerBaseModel.js.map +1 -1
  330. package/build/dist/Server/Types/Workflow/Components/BaseModel/UpdateManyBaseModel.js +24 -13
  331. package/build/dist/Server/Types/Workflow/Components/BaseModel/UpdateManyBaseModel.js.map +1 -1
  332. package/build/dist/Server/Types/Workflow/Components/BaseModel/UpdateOneBaseModel.js +27 -13
  333. package/build/dist/Server/Types/Workflow/Components/BaseModel/UpdateOneBaseModel.js.map +1 -1
  334. package/build/dist/Server/Types/Workflow/Components/Discord/SendMessageToChannel.js +14 -6
  335. package/build/dist/Server/Types/Workflow/Components/Discord/SendMessageToChannel.js.map +1 -1
  336. package/build/dist/Server/Types/Workflow/Components/IncomingWebhookUtils.js +42 -0
  337. package/build/dist/Server/Types/Workflow/Components/IncomingWebhookUtils.js.map +1 -0
  338. package/build/dist/Server/Types/Workflow/Components/MicrosoftTeams/SendMessageToChannel.js +15 -6
  339. package/build/dist/Server/Types/Workflow/Components/MicrosoftTeams/SendMessageToChannel.js.map +1 -1
  340. package/build/dist/Server/Types/Workflow/Components/Slack/SendMessageToChannel.js +10 -2
  341. package/build/dist/Server/Types/Workflow/Components/Slack/SendMessageToChannel.js.map +1 -1
  342. package/build/dist/Server/Utils/AI/Chat/ObservabilityChatPrompt.js +1 -1
  343. package/build/dist/Server/Utils/AI/CodeFix/CodeAgentWorkspaceGuard.js +141 -0
  344. package/build/dist/Server/Utils/AI/CodeFix/CodeAgentWorkspaceGuard.js.map +1 -1
  345. package/build/dist/Server/Utils/AI/SRE/Insights/FixRouting.js.map +1 -1
  346. package/build/dist/Server/Utils/AI/SRE/TelemetryImprovementTaskTrigger.js +1 -1
  347. package/build/dist/Server/Utils/AI/Toolbox/CodeWriteTools.js +6 -6
  348. package/build/dist/Server/Utils/AI/Toolbox/CodeWriteTools.js.map +1 -1
  349. package/build/dist/Server/Utils/CodeRepository/GitHub/GitHub.js +39 -5
  350. package/build/dist/Server/Utils/CodeRepository/GitHub/GitHub.js.map +1 -1
  351. package/build/dist/Server/Utils/DataSource/EgressGuard.js +94 -10
  352. package/build/dist/Server/Utils/DataSource/EgressGuard.js.map +1 -1
  353. package/build/dist/Server/Utils/DataSource/HttpFetch.js +1 -19
  354. package/build/dist/Server/Utils/DataSource/HttpFetch.js.map +1 -1
  355. package/build/dist/Server/Utils/Execute.js +2 -2
  356. package/build/dist/Server/Utils/Execute.js.map +1 -1
  357. package/build/dist/Server/Utils/InlineImageAccessTokenSync.js +13 -0
  358. package/build/dist/Server/Utils/InlineImageAccessTokenSync.js.map +1 -1
  359. package/build/dist/Server/Utils/LLM/LLMService.js +43 -12
  360. package/build/dist/Server/Utils/LLM/LLMService.js.map +1 -1
  361. package/build/dist/Server/Utils/Monitor/MonitorAlert.js +44 -24
  362. package/build/dist/Server/Utils/Monitor/MonitorAlert.js.map +1 -1
  363. package/build/dist/Server/Utils/Monitor/MonitorDependencySuppression.js +158 -0
  364. package/build/dist/Server/Utils/Monitor/MonitorDependencySuppression.js.map +1 -0
  365. package/build/dist/Server/Utils/Monitor/MonitorIncident.js +50 -30
  366. package/build/dist/Server/Utils/Monitor/MonitorIncident.js.map +1 -1
  367. package/build/dist/Server/Utils/Monitor/MonitorResource.js +32 -0
  368. package/build/dist/Server/Utils/Monitor/MonitorResource.js.map +1 -1
  369. package/build/dist/Server/Utils/Response.js +112 -5
  370. package/build/dist/Server/Utils/Response.js.map +1 -1
  371. package/build/dist/Server/Utils/SSRFProtection.js +219 -53
  372. package/build/dist/Server/Utils/SSRFProtection.js.map +1 -1
  373. package/build/dist/Server/Utils/StartServer.js +7 -0
  374. package/build/dist/Server/Utils/StartServer.js.map +1 -1
  375. package/build/dist/Server/Utils/VM/VMRunner.js +73 -1
  376. package/build/dist/Server/Utils/VM/VMRunner.js.map +1 -1
  377. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +445 -43
  378. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
  379. package/build/dist/Server/Utils/Workspace/Slack/Slack.js +18 -1
  380. package/build/dist/Server/Utils/Workspace/Slack/Slack.js.map +1 -1
  381. package/build/dist/Server/Utils/Workspace/WorkspaceBase.js.map +1 -1
  382. package/build/dist/Types/AI/FixPullRequestCiStatus.js +1 -1
  383. package/build/dist/Types/AI/FixVerificationStatus.js +2 -2
  384. package/build/dist/Types/API/Hostname.js +73 -4
  385. package/build/dist/Types/API/Hostname.js.map +1 -1
  386. package/build/dist/Types/API/URL.js +42 -24
  387. package/build/dist/Types/API/URL.js.map +1 -1
  388. package/build/dist/Types/Billing/BalanceAdjustmentType.js +15 -0
  389. package/build/dist/Types/Billing/BalanceAdjustmentType.js.map +1 -0
  390. package/build/dist/Types/Billing/ProjectBalanceType.js +14 -0
  391. package/build/dist/Types/Billing/ProjectBalanceType.js.map +1 -0
  392. package/build/dist/Types/Domain.js +22 -0
  393. package/build/dist/Types/Domain.js.map +1 -1
  394. package/build/dist/Types/File/MimeType.js +11 -0
  395. package/build/dist/Types/File/MimeType.js.map +1 -1
  396. package/build/dist/Types/JSONFunctions.js +14 -0
  397. package/build/dist/Types/JSONFunctions.js.map +1 -1
  398. package/build/dist/Types/Workflow/Components/BaseModel.js +83 -28
  399. package/build/dist/Types/Workflow/Components/BaseModel.js.map +1 -1
  400. package/build/dist/UI/Components/GanttChart/ChartContainer.js +4 -3
  401. package/build/dist/UI/Components/GanttChart/ChartContainer.js.map +1 -1
  402. package/build/dist/UI/Components/Modal/ConfirmModal.js +3 -1
  403. package/build/dist/UI/Components/Modal/ConfirmModal.js.map +1 -1
  404. package/build/dist/UI/Components/Modal/Modal.js +24 -34
  405. package/build/dist/UI/Components/Modal/Modal.js.map +1 -1
  406. package/build/dist/UI/Components/ModelDelete/ModelDelete.js +11 -6
  407. package/build/dist/UI/Components/ModelDelete/ModelDelete.js.map +1 -1
  408. package/build/dist/UI/Components/SideOver/SideOver.js +105 -30
  409. package/build/dist/UI/Components/SideOver/SideOver.js.map +1 -1
  410. package/build/dist/UI/Utils/PageScrollLock.js +52 -0
  411. package/build/dist/UI/Utils/PageScrollLock.js.map +1 -0
  412. package/build/dist/Utils/Dashboard/HtmlWidgetDocument.js +4 -2
  413. package/build/dist/Utils/Dashboard/HtmlWidgetDocument.js.map +1 -1
  414. package/build/dist/Utils/Dashboard/VariableInterpolation.js +19 -1
  415. package/build/dist/Utils/Dashboard/VariableInterpolation.js.map +1 -1
  416. package/build/dist/Utils/LegacySerializedArray.js +51 -0
  417. package/build/dist/Utils/LegacySerializedArray.js.map +1 -0
  418. package/build/dist/Utils/Monitor/MonitorDependencyRule.js +47 -0
  419. package/build/dist/Utils/Monitor/MonitorDependencyRule.js.map +1 -0
  420. package/build/dist/Utils/Telemetry/SavedViewFilters.js +52 -0
  421. package/build/dist/Utils/Telemetry/SavedViewFilters.js.map +1 -0
  422. package/jest.config.json +3 -0
  423. package/package.json +1 -1
  424. package/tsconfig.json +3 -1
@@ -0,0 +1,702 @@
1
+ /*
2
+ * PasswordHash fails to COMPILE under ts-jest (TS 5.9 + @types/node Buffer
3
+ * mismatch) and DatabaseService (which every concrete service, including
4
+ * MonitorService, extends) imports it. Nothing password-related is under
5
+ * test here, so the module is replaced WITH A FACTORY — an automock would
6
+ * still require (and type-check) the real file.
7
+ */
8
+ jest.mock("../../../Server/Utils/PasswordHash", () => {
9
+ return {
10
+ __esModule: true,
11
+ default: {
12
+ hash: jest.fn(),
13
+ verify: jest.fn(),
14
+ generateSalt: jest.fn(),
15
+ needsUpgrade: jest.fn(),
16
+ applyPepper: jest.fn(),
17
+ },
18
+ };
19
+ });
20
+
21
+ import MonitorService from "../../../Server/Services/MonitorService";
22
+ import MonitorStatusService from "../../../Server/Services/MonitorStatusService";
23
+ import ProjectScopedReferenceValidator from "../../../Server/Utils/Database/ProjectScopedReferenceValidator";
24
+ import DatabaseService from "../../../Server/Services/DatabaseService";
25
+ import DatabaseBaseModel from "../../../Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel";
26
+ import Monitor from "../../../Models/DatabaseModels/Monitor";
27
+ import MonitorStatus from "../../../Models/DatabaseModels/MonitorStatus";
28
+ import { LIMIT_PER_PROJECT } from "../../../Types/Database/LimitMax";
29
+ import BadDataException from "../../../Types/Exception/BadDataException";
30
+ import ObjectID from "../../../Types/ObjectID";
31
+ import { afterEach, describe, expect, test } from "@jest/globals";
32
+
33
+ /*
34
+ * MonitorService.validateDependencyConfiguration guards the
35
+ * alert-dependency configuration (dependsOnMonitors +
36
+ * suppressAlertsWhenParentMonitorStatuses) on both the create and update
37
+ * write paths. These tests pin its contract without a database:
38
+ *
39
+ * - The method is multi-target: a bulk update hands it every matched
40
+ * monitor's {monitorId, projectId} pair. Existence/project checks are
41
+ * hoisted to once per DISTINCT project and one BFS serves every
42
+ * target's cycle check.
43
+ * - `undefined` means "not part of this write" and validates nothing;
44
+ * an empty array (clearing the config), `null` (TypeORM's clear-all
45
+ * for relation columns) and non-array garbage validate nothing —
46
+ * none of them may query.
47
+ * - Proposed ids arrive as relation objects, ObjectIDs or bare uuid
48
+ * strings; all three shapes are validated and every comparison is
49
+ * case-insensitive (Postgres matches uuids by value).
50
+ * - Self-dependency is rejected before any query, per target.
51
+ * - Parent existence and same-project checks run through the REAL
52
+ * ProjectScopedReferenceValidator (its findBy is what the mock
53
+ * serves), so the house error wording is exercised end-to-end.
54
+ * - The upward cycle walk (throwIfDependencyCycle, exercised through
55
+ * the public method) rejects direct and transitive cycles, fails
56
+ * CLOSED when a frontier level hits the query limit (truncation
57
+ * could hide a cycle), and terminates cleanly on acyclic chains.
58
+ * - On create (monitorId null) the self/cycle checks are skipped but
59
+ * existence and project checks still run.
60
+ * - Suppression statuses are checked through
61
+ * ProjectScopedReferenceValidator against MonitorStatusService (that
62
+ * validator call is spied — statuses live in a different table).
63
+ */
64
+
65
+ // The house workaround for @jest/globals vs @types/jest spy typing.
66
+ type SpyLike = {
67
+ mock: { calls: Array<Array<unknown>> };
68
+ mockRestore: () => void;
69
+ };
70
+
71
+ const PROJECT_ID: ObjectID = new ObjectID(
72
+ "99999999-9999-4999-8999-999999999999",
73
+ );
74
+ const OTHER_PROJECT_ID: ObjectID = new ObjectID(
75
+ "88888888-8888-4888-8888-888888888888",
76
+ );
77
+ const MONITOR_A_ID: ObjectID = new ObjectID(
78
+ "11111111-1111-4111-8111-111111111111",
79
+ );
80
+ const MONITOR_B_ID: ObjectID = new ObjectID(
81
+ "22222222-2222-4222-8222-222222222222",
82
+ );
83
+ const MONITOR_C_ID: ObjectID = new ObjectID(
84
+ "33333333-3333-4333-8333-333333333333",
85
+ );
86
+ const STATUS_1_ID: ObjectID = new ObjectID(
87
+ "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
88
+ );
89
+ const STATUS_2_ID: ObjectID = new ObjectID(
90
+ "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb",
91
+ );
92
+
93
+ // A bare id-only relation entry, the shape the API hands the hook.
94
+ type MakeMonitorRef = (id: ObjectID) => Monitor;
95
+
96
+ const makeMonitorRef: MakeMonitorRef = (id: ObjectID): Monitor => {
97
+ const monitor: Monitor = new Monitor();
98
+ monitor.id = id;
99
+ return monitor;
100
+ };
101
+
102
+ // A row as the mocked findBy returns it (existence check or cycle walk).
103
+ type MakeMonitorRow = (input: {
104
+ id: ObjectID;
105
+ projectId?: ObjectID | undefined;
106
+ dependsOn?: Array<ObjectID> | undefined;
107
+ }) => Monitor;
108
+
109
+ const makeMonitorRow: MakeMonitorRow = (input: {
110
+ id: ObjectID;
111
+ projectId?: ObjectID | undefined;
112
+ dependsOn?: Array<ObjectID> | undefined;
113
+ }): Monitor => {
114
+ const monitor: Monitor = new Monitor();
115
+ monitor.id = input.id;
116
+ if (input.projectId !== undefined) {
117
+ monitor.projectId = input.projectId;
118
+ }
119
+ if (input.dependsOn !== undefined) {
120
+ monitor.dependsOnMonitors = input.dependsOn.map((parentId: ObjectID) => {
121
+ return makeMonitorRef(parentId);
122
+ });
123
+ }
124
+ return monitor;
125
+ };
126
+
127
+ type MakeStatusRef = (id: ObjectID) => MonitorStatus;
128
+
129
+ const makeStatusRef: MakeStatusRef = (id: ObjectID): MonitorStatus => {
130
+ const status: MonitorStatus = new MonitorStatus();
131
+ status.id = id;
132
+ return status;
133
+ };
134
+
135
+ type ValidateTarget = {
136
+ monitorId: ObjectID | null;
137
+ projectId: ObjectID | null;
138
+ };
139
+
140
+ type ValidateInput = {
141
+ targets: Array<ValidateTarget>;
142
+ proposedParents: unknown;
143
+ proposedSuppressionStatuses: unknown;
144
+ };
145
+
146
+ function validate(input: ValidateInput): Promise<void> {
147
+ return MonitorService.validateDependencyConfiguration(input);
148
+ }
149
+
150
+ // The single-target shape the per-monitor create/update paths produce.
151
+ function singleTarget(
152
+ monitorId: ObjectID | null,
153
+ projectId: ObjectID | null = PROJECT_ID,
154
+ ): Array<ValidateTarget> {
155
+ return [{ monitorId, projectId }];
156
+ }
157
+
158
+ /*
159
+ * Every response the sequenced findBy mock should serve, in call order.
160
+ * With proposed parents present, call #1 is the house validator's
161
+ * existence/project lookup (select {_id, name, projectId}); the cycle
162
+ * walk's frontier queries (select {_id, dependsOnMonitors}) follow.
163
+ * Exhausting the sequence serves [] rather than falling through to the
164
+ * real (database-backed) findBy.
165
+ */
166
+ function spyOnFindBySequence(responses: Array<Array<Monitor>>): SpyLike {
167
+ let callIndex: number = 0;
168
+
169
+ return jest.spyOn(MonitorService, "findBy").mockImplementation((():
170
+ | Promise<Array<Monitor>>
171
+ | never => {
172
+ const response: Array<Monitor> = responses[callIndex] || [];
173
+ callIndex++;
174
+ return Promise.resolve(response);
175
+ }) as never) as unknown as SpyLike;
176
+ }
177
+
178
+ type ValidatorCall = {
179
+ projectId: ObjectID;
180
+ subject: string;
181
+ references: Array<{
182
+ modelName: string;
183
+ id: ObjectID | string | undefined | null;
184
+ service: DatabaseService<DatabaseBaseModel>;
185
+ }>;
186
+ };
187
+
188
+ /*
189
+ * Replaces the house validator with a recorder. Used ONLY where the
190
+ * validator would query a foreign table (MonitorStatusService) or where a
191
+ * test asserts it is never reached — the parent existence/project tests
192
+ * run the real validator so its wording is exercised.
193
+ */
194
+ function spyOnValidator(): Array<ValidatorCall> {
195
+ const calls: Array<ValidatorCall> = [];
196
+
197
+ jest
198
+ .spyOn(ProjectScopedReferenceValidator, "validateReferencesBelongToProject")
199
+ .mockImplementation((async (data: ValidatorCall): Promise<void> => {
200
+ calls.push(data);
201
+ }) as never);
202
+
203
+ return calls;
204
+ }
205
+
206
+ // Distinguishes the validator's existence lookups from cycle-walk queries.
207
+ function existenceLookupCalls(findBySpy: SpyLike): Array<Array<unknown>> {
208
+ return findBySpy.mock.calls.filter((call: Array<unknown>) => {
209
+ const select: Record<string, unknown> = (
210
+ call[0] as { select: Record<string, unknown> }
211
+ ).select;
212
+ return select["projectId"] === true;
213
+ });
214
+ }
215
+
216
+ describe("MonitorService.validateDependencyConfiguration", () => {
217
+ afterEach(() => {
218
+ jest.restoreAllMocks();
219
+ });
220
+
221
+ describe("nothing to validate", () => {
222
+ test("proposedParents undefined (not part of this write): no query, no throw", async () => {
223
+ const findBySpy: SpyLike = spyOnFindBySequence([]);
224
+ const validatorCalls: Array<ValidatorCall> = spyOnValidator();
225
+
226
+ await expect(
227
+ validate({
228
+ targets: singleTarget(MONITOR_A_ID),
229
+ proposedParents: undefined,
230
+ proposedSuppressionStatuses: undefined,
231
+ }),
232
+ ).resolves.toBeUndefined();
233
+
234
+ expect(findBySpy.mock.calls.length).toBe(0);
235
+ expect(validatorCalls.length).toBe(0);
236
+ });
237
+
238
+ test("proposedParents [] (clearing the config): no query, no throw", async () => {
239
+ const findBySpy: SpyLike = spyOnFindBySequence([]);
240
+
241
+ await expect(
242
+ validate({
243
+ targets: singleTarget(MONITOR_A_ID),
244
+ proposedParents: [],
245
+ proposedSuppressionStatuses: undefined,
246
+ }),
247
+ ).resolves.toBeUndefined();
248
+
249
+ expect(findBySpy.mock.calls.length).toBe(0);
250
+ });
251
+
252
+ test("proposedParents null (TypeORM clear-all for relation columns): no query, no throw", async () => {
253
+ const findBySpy: SpyLike = spyOnFindBySequence([]);
254
+
255
+ await expect(
256
+ validate({
257
+ targets: singleTarget(MONITOR_A_ID),
258
+ proposedParents: null,
259
+ proposedSuppressionStatuses: undefined,
260
+ }),
261
+ ).resolves.toBeUndefined();
262
+
263
+ expect(findBySpy.mock.calls.length).toBe(0);
264
+ });
265
+
266
+ test("proposedParents that is not an array at all: nothing to extract, no query, no throw", async () => {
267
+ const findBySpy: SpyLike = spyOnFindBySequence([]);
268
+
269
+ await expect(
270
+ validate({
271
+ targets: singleTarget(MONITOR_A_ID),
272
+ proposedParents: { _id: MONITOR_B_ID.toString() },
273
+ proposedSuppressionStatuses: undefined,
274
+ }),
275
+ ).resolves.toBeUndefined();
276
+
277
+ expect(findBySpy.mock.calls.length).toBe(0);
278
+ });
279
+ });
280
+
281
+ describe("self-dependency", () => {
282
+ test("a monitor listing itself as a parent is rejected before any query", async () => {
283
+ const findBySpy: SpyLike = spyOnFindBySequence([]);
284
+
285
+ const run: () => Promise<void> = () => {
286
+ return validate({
287
+ targets: singleTarget(MONITOR_A_ID),
288
+ proposedParents: [
289
+ makeMonitorRef(MONITOR_B_ID),
290
+ makeMonitorRef(MONITOR_A_ID),
291
+ ],
292
+ proposedSuppressionStatuses: undefined,
293
+ });
294
+ };
295
+
296
+ await expect(run()).rejects.toThrow(BadDataException);
297
+ await expect(run()).rejects.toThrow("A monitor cannot depend on itself.");
298
+ expect(findBySpy.mock.calls.length).toBe(0);
299
+ });
300
+
301
+ test("a bare uuid STRING naming the monitor itself is rejected (string-shaped payloads are not exempt)", async () => {
302
+ const findBySpy: SpyLike = spyOnFindBySequence([]);
303
+
304
+ await expect(
305
+ validate({
306
+ targets: singleTarget(MONITOR_A_ID),
307
+ proposedParents: [MONITOR_A_ID.toString()],
308
+ proposedSuppressionStatuses: undefined,
309
+ }),
310
+ ).rejects.toThrow("A monitor cannot depend on itself.");
311
+
312
+ expect(findBySpy.mock.calls.length).toBe(0);
313
+ });
314
+
315
+ test("an UPPERCASE uuid naming the monitor itself is rejected (uuids compare case-insensitively)", async () => {
316
+ const findBySpy: SpyLike = spyOnFindBySequence([]);
317
+
318
+ await expect(
319
+ validate({
320
+ targets: singleTarget(MONITOR_A_ID),
321
+ proposedParents: [
322
+ makeMonitorRef(new ObjectID(MONITOR_A_ID.toString().toUpperCase())),
323
+ ],
324
+ proposedSuppressionStatuses: undefined,
325
+ }),
326
+ ).rejects.toThrow("A monitor cannot depend on itself.");
327
+
328
+ expect(findBySpy.mock.calls.length).toBe(0);
329
+ });
330
+
331
+ test("multi-target bulk: a parent that is ANY of the targets is rejected", async () => {
332
+ const findBySpy: SpyLike = spyOnFindBySequence([]);
333
+
334
+ await expect(
335
+ validate({
336
+ targets: [
337
+ { monitorId: MONITOR_A_ID, projectId: PROJECT_ID },
338
+ { monitorId: MONITOR_B_ID, projectId: PROJECT_ID },
339
+ ],
340
+ proposedParents: [makeMonitorRef(MONITOR_B_ID)],
341
+ proposedSuppressionStatuses: undefined,
342
+ }),
343
+ ).rejects.toThrow("A monitor cannot depend on itself.");
344
+
345
+ expect(findBySpy.mock.calls.length).toBe(0);
346
+ });
347
+ });
348
+
349
+ describe("existence and project checks (through the real house validator)", () => {
350
+ test("a parent id matching no monitor is rejected with the house wording", async () => {
351
+ spyOnFindBySequence([[]]);
352
+
353
+ const run: () => Promise<void> = () => {
354
+ return validate({
355
+ targets: singleTarget(MONITOR_A_ID),
356
+ proposedParents: [makeMonitorRef(MONITOR_B_ID)],
357
+ proposedSuppressionStatuses: undefined,
358
+ });
359
+ };
360
+
361
+ await expect(run()).rejects.toThrow(BadDataException);
362
+ await expect(run()).rejects.toThrow(/do not exist/);
363
+ // The message names the model and echoes the offending id.
364
+ await expect(run()).rejects.toThrow(
365
+ new RegExp(`Monitor "${MONITOR_B_ID.toString()}"`),
366
+ );
367
+ });
368
+
369
+ test("a parent from another project is rejected with the house wording", async () => {
370
+ spyOnFindBySequence([
371
+ [makeMonitorRow({ id: MONITOR_B_ID, projectId: OTHER_PROJECT_ID })],
372
+ ]);
373
+
374
+ await expect(
375
+ validate({
376
+ targets: singleTarget(MONITOR_A_ID),
377
+ proposedParents: [makeMonitorRef(MONITOR_B_ID)],
378
+ proposedSuppressionStatuses: undefined,
379
+ }),
380
+ ).rejects.toThrow(/belong to a different project/);
381
+ });
382
+
383
+ test("a bare uuid STRING parent id is validated like any other shape", async () => {
384
+ spyOnFindBySequence([[]]);
385
+
386
+ await expect(
387
+ validate({
388
+ targets: singleTarget(MONITOR_A_ID),
389
+ proposedParents: [MONITOR_B_ID.toString()],
390
+ proposedSuppressionStatuses: undefined,
391
+ }),
392
+ ).rejects.toThrow(/do not exist/);
393
+ });
394
+
395
+ test("duplicate parent ids are deduped; the validator lookup runs first with the id/name/project select", async () => {
396
+ /*
397
+ * [B, B] must dedupe to one id; findBy returning that single row must
398
+ * satisfy the existence check. The walk then sees B with no
399
+ * dependencies and terminates.
400
+ */
401
+ const findBySpy: SpyLike = spyOnFindBySequence([
402
+ [makeMonitorRow({ id: MONITOR_B_ID, projectId: PROJECT_ID })],
403
+ [makeMonitorRow({ id: MONITOR_B_ID, dependsOn: [] })],
404
+ ]);
405
+
406
+ await expect(
407
+ validate({
408
+ targets: singleTarget(MONITOR_A_ID),
409
+ proposedParents: [
410
+ makeMonitorRef(MONITOR_B_ID),
411
+ makeMonitorRef(MONITOR_B_ID),
412
+ ],
413
+ proposedSuppressionStatuses: undefined,
414
+ }),
415
+ ).resolves.toBeUndefined();
416
+
417
+ expect(findBySpy.mock.calls.length).toBe(2);
418
+
419
+ // Call #1 is the validator's lookup: existence + tenant column.
420
+ const validatorSelect: Record<string, unknown> = (
421
+ findBySpy.mock.calls[0]![0] as { select: Record<string, unknown> }
422
+ ).select;
423
+ expect(validatorSelect).toEqual({
424
+ _id: true,
425
+ name: true,
426
+ projectId: true,
427
+ });
428
+
429
+ // Call #2 is the cycle walk: parents of the frontier.
430
+ const walkSelect: Record<string, unknown> = (
431
+ findBySpy.mock.calls[1]![0] as { select: Record<string, unknown> }
432
+ ).select;
433
+ expect(walkSelect).toEqual({
434
+ _id: true,
435
+ dependsOnMonitors: {
436
+ _id: true,
437
+ },
438
+ });
439
+ });
440
+
441
+ test("two targets in the SAME project: the existence lookup runs once, not once per target", async () => {
442
+ const findBySpy: SpyLike = spyOnFindBySequence([
443
+ // One hoisted existence/project lookup for the shared project.
444
+ [makeMonitorRow({ id: MONITOR_B_ID, projectId: PROJECT_ID })],
445
+ // One shared walk level: B has no parents of its own.
446
+ [makeMonitorRow({ id: MONITOR_B_ID, dependsOn: [] })],
447
+ ]);
448
+
449
+ await expect(
450
+ validate({
451
+ targets: [
452
+ { monitorId: MONITOR_A_ID, projectId: PROJECT_ID },
453
+ { monitorId: MONITOR_C_ID, projectId: PROJECT_ID },
454
+ ],
455
+ proposedParents: [makeMonitorRef(MONITOR_B_ID)],
456
+ proposedSuppressionStatuses: undefined,
457
+ }),
458
+ ).resolves.toBeUndefined();
459
+
460
+ expect(existenceLookupCalls(findBySpy).length).toBe(1);
461
+ expect(findBySpy.mock.calls.length).toBe(2);
462
+ });
463
+ });
464
+
465
+ describe("cycle detection (through the public method)", () => {
466
+ test("direct cycle: B already depends on A, so A cannot depend on B", async () => {
467
+ const findBySpy: SpyLike = spyOnFindBySequence([
468
+ // Validator existence/project lookup for the proposed parent B.
469
+ [makeMonitorRow({ id: MONITOR_B_ID, projectId: PROJECT_ID })],
470
+ // Cycle walk, frontier [B]: B's own parents include A.
471
+ [makeMonitorRow({ id: MONITOR_B_ID, dependsOn: [MONITOR_A_ID] })],
472
+ ]);
473
+
474
+ await expect(
475
+ validate({
476
+ targets: singleTarget(MONITOR_A_ID),
477
+ proposedParents: [makeMonitorRef(MONITOR_B_ID)],
478
+ proposedSuppressionStatuses: undefined,
479
+ }),
480
+ ).rejects.toThrow(
481
+ "This dependency would create a cycle: one of the selected monitors (or a monitor it depends on) already depends on this monitor.",
482
+ );
483
+
484
+ expect(findBySpy.mock.calls.length).toBe(2);
485
+ });
486
+
487
+ test("transitive cycle: A -> B -> C -> A is rejected", async () => {
488
+ const findBySpy: SpyLike = spyOnFindBySequence([
489
+ // Validator existence/project lookup for B.
490
+ [makeMonitorRow({ id: MONITOR_B_ID, projectId: PROJECT_ID })],
491
+ // Walk level 1, frontier [B]: B depends on C.
492
+ [makeMonitorRow({ id: MONITOR_B_ID, dependsOn: [MONITOR_C_ID] })],
493
+ // Walk level 2, frontier [C]: C depends on A — closes the cycle.
494
+ [makeMonitorRow({ id: MONITOR_C_ID, dependsOn: [MONITOR_A_ID] })],
495
+ ]);
496
+
497
+ await expect(
498
+ validate({
499
+ targets: singleTarget(MONITOR_A_ID),
500
+ proposedParents: [makeMonitorRef(MONITOR_B_ID)],
501
+ proposedSuppressionStatuses: undefined,
502
+ }),
503
+ ).rejects.toThrow("would create a cycle");
504
+
505
+ expect(findBySpy.mock.calls.length).toBe(3);
506
+ });
507
+
508
+ test("valid chain A -> B -> C terminates the walk without throwing", async () => {
509
+ const findBySpy: SpyLike = spyOnFindBySequence([
510
+ // Validator existence/project lookup for B.
511
+ [makeMonitorRow({ id: MONITOR_B_ID, projectId: PROJECT_ID })],
512
+ // Walk level 1, frontier [B]: B depends on C.
513
+ [makeMonitorRow({ id: MONITOR_B_ID, dependsOn: [MONITOR_C_ID] })],
514
+ // Walk level 2, frontier [C]: C depends on nothing — walk ends.
515
+ [makeMonitorRow({ id: MONITOR_C_ID, dependsOn: [] })],
516
+ ]);
517
+
518
+ await expect(
519
+ validate({
520
+ targets: singleTarget(MONITOR_A_ID),
521
+ proposedParents: [makeMonitorRef(MONITOR_B_ID)],
522
+ proposedSuppressionStatuses: undefined,
523
+ }),
524
+ ).resolves.toBeUndefined();
525
+
526
+ expect(findBySpy.mock.calls.length).toBe(3);
527
+ });
528
+
529
+ test("one BFS serves every target: a chain reaching the SECOND target of a bulk update is a cycle", async () => {
530
+ /*
531
+ * Targets [A, C], proposed parent B, and B already depends on C. The
532
+ * walk starts once from B and must recognize C — a target it did not
533
+ * start from — as closing a cycle.
534
+ */
535
+ const findBySpy: SpyLike = spyOnFindBySequence([
536
+ // Validator existence/project lookup for B.
537
+ [makeMonitorRow({ id: MONITOR_B_ID, projectId: PROJECT_ID })],
538
+ // Walk level 1, frontier [B]: B depends on C (the second target).
539
+ [makeMonitorRow({ id: MONITOR_B_ID, dependsOn: [MONITOR_C_ID] })],
540
+ ]);
541
+
542
+ await expect(
543
+ validate({
544
+ targets: [
545
+ { monitorId: MONITOR_A_ID, projectId: PROJECT_ID },
546
+ { monitorId: MONITOR_C_ID, projectId: PROJECT_ID },
547
+ ],
548
+ proposedParents: [makeMonitorRef(MONITOR_B_ID)],
549
+ proposedSuppressionStatuses: undefined,
550
+ }),
551
+ ).rejects.toThrow("would create a cycle");
552
+
553
+ // Exactly one walk (call #2) covered both targets.
554
+ expect(findBySpy.mock.calls.length).toBe(2);
555
+ });
556
+
557
+ test("fail-closed: a frontier level as wide as the query limit is refused rather than silently truncated", async () => {
558
+ /*
559
+ * The walk reads each frontier level with limit LIMIT_PER_PROJECT. A
560
+ * level that returns >= that many rows may have been truncated, and a
561
+ * cycle routed through the dropped remainder would be admitted — so
562
+ * the walk must refuse. Rows are never field-inspected before the
563
+ * length check, so lightweight casts suffice.
564
+ */
565
+ const truncatedLevel: Array<Monitor> = Array.from(
566
+ { length: LIMIT_PER_PROJECT },
567
+ () => {
568
+ return {} as unknown as Monitor;
569
+ },
570
+ );
571
+
572
+ spyOnFindBySequence([
573
+ // Validator existence/project lookup for B.
574
+ [makeMonitorRow({ id: MONITOR_B_ID, projectId: PROJECT_ID })],
575
+ // Walk level 1 comes back at the limit.
576
+ truncatedLevel,
577
+ ]);
578
+
579
+ await expect(
580
+ validate({
581
+ targets: singleTarget(MONITOR_A_ID),
582
+ proposedParents: [makeMonitorRef(MONITOR_B_ID)],
583
+ proposedSuppressionStatuses: undefined,
584
+ }),
585
+ ).rejects.toThrow(/too large to verify/);
586
+ });
587
+ });
588
+
589
+ describe("create path (monitorId null)", () => {
590
+ test("self/cycle checks are skipped but existence and project checks still run", async () => {
591
+ const findBySpy: SpyLike = spyOnFindBySequence([
592
+ [makeMonitorRow({ id: MONITOR_B_ID, projectId: PROJECT_ID })],
593
+ ]);
594
+
595
+ await expect(
596
+ validate({
597
+ targets: singleTarget(null),
598
+ proposedParents: [makeMonitorRef(MONITOR_B_ID)],
599
+ proposedSuppressionStatuses: undefined,
600
+ }),
601
+ ).resolves.toBeUndefined();
602
+
603
+ // Exactly the validator's existence query — no cycle walk on create.
604
+ expect(findBySpy.mock.calls.length).toBe(1);
605
+ expect(existenceLookupCalls(findBySpy).length).toBe(1);
606
+ });
607
+
608
+ test("a cross-project parent is still rejected on create", async () => {
609
+ spyOnFindBySequence([
610
+ [makeMonitorRow({ id: MONITOR_B_ID, projectId: OTHER_PROJECT_ID })],
611
+ ]);
612
+
613
+ await expect(
614
+ validate({
615
+ targets: singleTarget(null),
616
+ proposedParents: [makeMonitorRef(MONITOR_B_ID)],
617
+ proposedSuppressionStatuses: undefined,
618
+ }),
619
+ ).rejects.toThrow(/belong to a different project/);
620
+ });
621
+ });
622
+
623
+ describe("suppression statuses", () => {
624
+ test("each proposed status is checked against MonitorStatusService in the monitor's project", async () => {
625
+ const validatorCalls: Array<ValidatorCall> = spyOnValidator();
626
+
627
+ await expect(
628
+ validate({
629
+ targets: singleTarget(MONITOR_A_ID),
630
+ proposedParents: undefined,
631
+ proposedSuppressionStatuses: [
632
+ makeStatusRef(STATUS_1_ID),
633
+ makeStatusRef(STATUS_2_ID),
634
+ ],
635
+ }),
636
+ ).resolves.toBeUndefined();
637
+
638
+ expect(validatorCalls.length).toBe(1);
639
+
640
+ const call: ValidatorCall = validatorCalls[0]!;
641
+ expect(call.projectId.toString()).toBe(PROJECT_ID.toString());
642
+ expect(call.subject).toBe("monitor");
643
+ expect(
644
+ call.references.map(
645
+ (reference: {
646
+ modelName: string;
647
+ id: ObjectID | string | undefined | null;
648
+ service: DatabaseService<DatabaseBaseModel>;
649
+ }) => {
650
+ return {
651
+ modelName: reference.modelName,
652
+ id: reference.id?.toString(),
653
+ // Identity matters: the service picks the table the id is checked against.
654
+ serviceIsMonitorStatusService:
655
+ reference.service ===
656
+ (MonitorStatusService as unknown as DatabaseService<DatabaseBaseModel>),
657
+ };
658
+ },
659
+ ),
660
+ ).toEqual([
661
+ {
662
+ modelName: "Monitor Status",
663
+ id: STATUS_1_ID.toString(),
664
+ serviceIsMonitorStatusService: true,
665
+ },
666
+ {
667
+ modelName: "Monitor Status",
668
+ id: STATUS_2_ID.toString(),
669
+ serviceIsMonitorStatusService: true,
670
+ },
671
+ ]);
672
+ });
673
+
674
+ test("statuses undefined (not part of this write): the validator is never invoked", async () => {
675
+ const validatorCalls: Array<ValidatorCall> = spyOnValidator();
676
+
677
+ await expect(
678
+ validate({
679
+ targets: singleTarget(MONITOR_A_ID),
680
+ proposedParents: undefined,
681
+ proposedSuppressionStatuses: undefined,
682
+ }),
683
+ ).resolves.toBeUndefined();
684
+
685
+ expect(validatorCalls.length).toBe(0);
686
+ });
687
+
688
+ test("statuses without a projectId on any target: the validator is never invoked", async () => {
689
+ const validatorCalls: Array<ValidatorCall> = spyOnValidator();
690
+
691
+ await expect(
692
+ validate({
693
+ targets: singleTarget(MONITOR_A_ID, null),
694
+ proposedParents: undefined,
695
+ proposedSuppressionStatuses: [makeStatusRef(STATUS_1_ID)],
696
+ }),
697
+ ).resolves.toBeUndefined();
698
+
699
+ expect(validatorCalls.length).toBe(0);
700
+ });
701
+ });
702
+ });