@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,459 @@
1
+ import ProjectAPI from "../../../Server/API/ProjectAPI";
2
+ import MasterAdminAuthorization from "../../../Server/Middleware/MasterAdminAuthorization";
3
+ import UserMiddleware from "../../../Server/Middleware/UserAuthorization";
4
+ import JSONWebToken from "../../../Server/Utils/JsonWebToken";
5
+ import ProjectService from "../../../Server/Services/ProjectService";
6
+ import {
7
+ NextFunction,
8
+ OneUptimeRequest,
9
+ OneUptimeResponse,
10
+ } from "../../../Server/Utils/Express";
11
+ import Response from "../../../Server/Utils/Response";
12
+ import { mockRouter } from "./Helpers";
13
+ import { describe, expect, it } from "@jest/globals";
14
+ import JSONWebTokenData from "../../../Types/JsonWebTokenData";
15
+ import ObjectID from "../../../Types/ObjectID";
16
+ import BadDataException from "../../../Types/Exception/BadDataException";
17
+ import ProjectBalanceType from "../../../Types/Billing/ProjectBalanceType";
18
+ import BalanceAdjustmentType from "../../../Types/Billing/BalanceAdjustmentType";
19
+
20
+ /*
21
+ * The adjust-balance endpoint hands out (or claws back) paid service with no
22
+ * payment behind it. Two properties are load-bearing and asserted here: it is
23
+ * reachable only by a master admin session, and nothing that fails to parse
24
+ * cleanly into a whole number of cents ever reaches the service.
25
+ */
26
+
27
+ jest.mock("../../../Server/EnvironmentConfig", () => {
28
+ return {
29
+ ...jest.requireActual("../../../Server/EnvironmentConfig"),
30
+ IsBillingEnabled: true,
31
+ };
32
+ });
33
+
34
+ jest.mock("../../../Server/Services/BillingService");
35
+
36
+ jest.mock("../../../Server/Utils/Express", () => {
37
+ return {
38
+ getRouter: () => {
39
+ return mockRouter;
40
+ },
41
+ };
42
+ });
43
+
44
+ jest.mock("../../../Server/Utils/Response", () => {
45
+ return {
46
+ sendEntityArrayResponse: jest.fn(),
47
+ sendJsonObjectResponse: jest.fn(),
48
+ sendEmptySuccessResponse: jest.fn(),
49
+ sendEntityResponse: jest.fn(),
50
+ };
51
+ });
52
+
53
+ jest.mock("../../../Server/Services/TeamMemberService");
54
+ jest.mock("../../../Server/Services/ProjectService");
55
+
56
+ const ROUTE: string = "/project/:id/adjust-balance";
57
+
58
+ describe("PUT /project/:id/adjust-balance", () => {
59
+ const projectId: ObjectID = ObjectID.generate();
60
+
61
+ let mockRequest: OneUptimeRequest;
62
+ let mockResponse: OneUptimeResponse;
63
+ let nextFunction: NextFunction;
64
+
65
+ function validBody(overrides?: Record<string, unknown>): {
66
+ data: Record<string, unknown>;
67
+ } {
68
+ return {
69
+ data: {
70
+ balanceType: ProjectBalanceType.SmsOrCall,
71
+ adjustmentType: BalanceAdjustmentType.Add,
72
+ amountInUSD: 20,
73
+ reason: "Goodwill credit agreed on ticket 4821",
74
+ ...overrides,
75
+ },
76
+ };
77
+ }
78
+
79
+ async function callRoute(): Promise<void> {
80
+ await mockRouter
81
+ .match("put", ROUTE)
82
+ .handlerFunction(mockRequest, mockResponse, nextFunction);
83
+ }
84
+
85
+ beforeEach(() => {
86
+ new ProjectAPI();
87
+ mockRequest = {} as OneUptimeRequest;
88
+ mockResponse = {
89
+ send: jest.fn(),
90
+ json: jest.fn(),
91
+ status: jest.fn().mockReturnThis(),
92
+ } as unknown as OneUptimeResponse;
93
+ nextFunction = jest.fn();
94
+
95
+ mockRequest.params = { id: projectId.toString() };
96
+
97
+ ProjectService.adjustBalance = jest.fn().mockResolvedValue({
98
+ previousBalanceInUSDCents: 5000,
99
+ newBalanceInUSDCents: 7000,
100
+ });
101
+
102
+ /*
103
+ * Response is module-mocked once for the whole file, so its call log would
104
+ * otherwise carry over between tests.
105
+ */
106
+ (Response.sendJsonObjectResponse as jest.Mock).mockClear();
107
+ });
108
+
109
+ afterEach(() => {
110
+ jest.restoreAllMocks();
111
+ });
112
+
113
+ describe("authorization", () => {
114
+ it("sits behind the master admin middleware", () => {
115
+ /*
116
+ * Moving a balance must not be reachable with project-level billing
117
+ * permissions - a project owner could otherwise credit themselves - and
118
+ * not with the static master API key either, only a master admin
119
+ * session.
120
+ */
121
+ expect(mockRouter.match("put", ROUTE).middleware).toBe(
122
+ MasterAdminAuthorization.isAuthorizedMasterAdminMiddleware,
123
+ );
124
+ });
125
+
126
+ it("does not require a tenant id, since master admins act across projects", async () => {
127
+ mockRequest.body = validBody();
128
+ delete (mockRequest as { tenantId?: ObjectID }).tenantId;
129
+
130
+ await callRoute();
131
+
132
+ expect(nextFunction).not.toHaveBeenCalled();
133
+ expect(ProjectService.adjustBalance).toHaveBeenCalled();
134
+ });
135
+ });
136
+
137
+ describe("the happy path", () => {
138
+ it("passes the adjustment through to the service in cents", async () => {
139
+ mockRequest.body = validBody();
140
+
141
+ await callRoute();
142
+
143
+ expect(nextFunction).not.toHaveBeenCalled();
144
+ expect(ProjectService.adjustBalance).toHaveBeenCalledWith(
145
+ expect.objectContaining({
146
+ projectId: projectId,
147
+ balanceType: ProjectBalanceType.SmsOrCall,
148
+ adjustmentType: BalanceAdjustmentType.Add,
149
+ amountInUSDCents: 2000,
150
+ reason: "Goodwill credit agreed on ticket 4821",
151
+ }),
152
+ );
153
+ });
154
+
155
+ it("responds with the balance before and after", async () => {
156
+ mockRequest.body = validBody();
157
+
158
+ await callRoute();
159
+
160
+ expect(Response.sendJsonObjectResponse).toHaveBeenCalledWith(
161
+ mockRequest,
162
+ mockResponse,
163
+ {
164
+ previousBalanceInUSDCents: 5000,
165
+ newBalanceInUSDCents: 7000,
166
+ },
167
+ );
168
+ });
169
+
170
+ it.each([[ProjectBalanceType.SmsOrCall], [ProjectBalanceType.AI]])(
171
+ "accepts the %s balance",
172
+ async (balanceType: ProjectBalanceType) => {
173
+ mockRequest.body = validBody({ balanceType });
174
+
175
+ await callRoute();
176
+
177
+ expect(nextFunction).not.toHaveBeenCalled();
178
+ expect(ProjectService.adjustBalance).toHaveBeenCalledWith(
179
+ expect.objectContaining({ balanceType }),
180
+ );
181
+ },
182
+ );
183
+
184
+ it.each([
185
+ [BalanceAdjustmentType.Add],
186
+ [BalanceAdjustmentType.Deduct],
187
+ [BalanceAdjustmentType.Set],
188
+ ])(
189
+ "accepts the %s adjustment",
190
+ async (adjustmentType: BalanceAdjustmentType) => {
191
+ mockRequest.body = validBody({ adjustmentType });
192
+
193
+ await callRoute();
194
+
195
+ expect(nextFunction).not.toHaveBeenCalled();
196
+ expect(ProjectService.adjustBalance).toHaveBeenCalledWith(
197
+ expect.objectContaining({ adjustmentType }),
198
+ );
199
+ },
200
+ );
201
+
202
+ it("lets a Set of zero through, unlike an Add of zero", async () => {
203
+ /*
204
+ * "This project should have no balance" is a real instruction, so the
205
+ * route must not treat the amount as missing. The service owns the rule
206
+ * that Add and Deduct need a positive amount.
207
+ */
208
+ mockRequest.body = validBody({
209
+ adjustmentType: BalanceAdjustmentType.Set,
210
+ amountInUSD: 0,
211
+ });
212
+
213
+ await callRoute();
214
+
215
+ expect(nextFunction).not.toHaveBeenCalled();
216
+ expect(ProjectService.adjustBalance).toHaveBeenCalledWith(
217
+ expect.objectContaining({ amountInUSDCents: 0 }),
218
+ );
219
+ });
220
+ });
221
+
222
+ describe("dollars to cents", () => {
223
+ it.each([
224
+ [20, 2000],
225
+ [20.15, 2015],
226
+ [0.01, 1],
227
+ [1234.56, 123456],
228
+ [99.99, 9999],
229
+ ])(
230
+ "converts %s USD to %s cents without floating point drift",
231
+ async (amountInUSD: number, expectedCents: number) => {
232
+ /*
233
+ * 20.15 * 100 is 2014.9999999999998 in binary floating point. Left
234
+ * untouched that truncates to 2014 - a cent short on every such
235
+ * adjustment.
236
+ */
237
+ mockRequest.body = validBody({ amountInUSD });
238
+
239
+ await callRoute();
240
+
241
+ expect(ProjectService.adjustBalance).toHaveBeenCalledWith(
242
+ expect.objectContaining({ amountInUSDCents: expectedCents }),
243
+ );
244
+ },
245
+ );
246
+
247
+ it("accepts a numeric string, since JSON bodies carry one", async () => {
248
+ mockRequest.body = validBody({ amountInUSD: "20.15" });
249
+
250
+ await callRoute();
251
+
252
+ expect(nextFunction).not.toHaveBeenCalled();
253
+ expect(ProjectService.adjustBalance).toHaveBeenCalledWith(
254
+ expect.objectContaining({ amountInUSDCents: 2015 }),
255
+ );
256
+ });
257
+ });
258
+
259
+ describe("balance type validation", () => {
260
+ it.each([
261
+ ["missing", undefined],
262
+ ["an unknown value", "Bitcoin"],
263
+ ["an empty string", ""],
264
+ ["a number", 1],
265
+ ["an object", { type: "AI" }],
266
+ ["an array", ["AI"]],
267
+ ["a lowercase near-miss", "ai"],
268
+ ])(
269
+ "rejects a balance type that is %s",
270
+ async (_label: string, balanceType: unknown) => {
271
+ mockRequest.body = validBody({ balanceType });
272
+
273
+ await callRoute();
274
+
275
+ expect(nextFunction).toHaveBeenCalledWith(
276
+ new BadDataException("Balance type must be one of: SmsOrCall, AI"),
277
+ );
278
+ expect(ProjectService.adjustBalance).not.toHaveBeenCalled();
279
+ },
280
+ );
281
+ });
282
+
283
+ describe("adjustment type validation", () => {
284
+ it.each([
285
+ ["missing", undefined],
286
+ ["an unknown value", "Multiply"],
287
+ ["an empty string", ""],
288
+ ["a number", 1],
289
+ ["an object", { type: "Add" }],
290
+ ["a lowercase near-miss", "add"],
291
+ ])(
292
+ "rejects an adjustment type that is %s",
293
+ async (_label: string, adjustmentType: unknown) => {
294
+ mockRequest.body = validBody({ adjustmentType });
295
+
296
+ await callRoute();
297
+
298
+ expect(nextFunction).toHaveBeenCalledWith(
299
+ new BadDataException(
300
+ "Adjustment type must be one of: Add, Deduct, Set",
301
+ ),
302
+ );
303
+ expect(ProjectService.adjustBalance).not.toHaveBeenCalled();
304
+ },
305
+ );
306
+ });
307
+
308
+ describe("amount validation", () => {
309
+ it.each([
310
+ ["missing", undefined],
311
+ ["null", null],
312
+ ["a boolean", true],
313
+ ["an object", { amount: 20 }],
314
+ ["an array", [20]],
315
+ ])(
316
+ "rejects an amount that is %s",
317
+ async (_label: string, amountInUSD: unknown) => {
318
+ /*
319
+ * Number([]) is 0 and Number([20]) is 20 - a bare Number() coercion
320
+ * would read these as real instructions, so the type is checked first.
321
+ */
322
+ mockRequest.body = validBody({ amountInUSD });
323
+
324
+ await callRoute();
325
+
326
+ expect(nextFunction).toHaveBeenCalledWith(
327
+ new BadDataException("Amount is not a valid number"),
328
+ );
329
+ expect(ProjectService.adjustBalance).not.toHaveBeenCalled();
330
+ },
331
+ );
332
+
333
+ it.each([
334
+ ["an empty string", ""],
335
+ ["whitespace only", " "],
336
+ ["not numeric", "twenty"],
337
+ ["NaN", Number.NaN],
338
+ ["Infinity", Number.POSITIVE_INFINITY],
339
+ ])(
340
+ "rejects an amount that is %s",
341
+ async (_label: string, amountInUSD: unknown) => {
342
+ mockRequest.body = validBody({ amountInUSD });
343
+
344
+ await callRoute();
345
+
346
+ expect(nextFunction).toHaveBeenCalledWith(
347
+ new BadDataException("Amount is not a valid number"),
348
+ );
349
+ expect(ProjectService.adjustBalance).not.toHaveBeenCalled();
350
+ },
351
+ );
352
+ });
353
+
354
+ describe("reason validation", () => {
355
+ it.each([
356
+ ["missing", undefined],
357
+ ["empty", ""],
358
+ ["whitespace only", " "],
359
+ ["a number", 42],
360
+ ["an object", { reason: "because" }],
361
+ ])(
362
+ "rejects a reason that is %s",
363
+ async (_label: string, reason: unknown) => {
364
+ // The reason is the entire audit trail for the adjustment.
365
+ mockRequest.body = validBody({ reason });
366
+
367
+ await callRoute();
368
+
369
+ expect(nextFunction).toHaveBeenCalledWith(
370
+ new BadDataException("A reason is required to adjust the balance"),
371
+ );
372
+ expect(ProjectService.adjustBalance).not.toHaveBeenCalled();
373
+ },
374
+ );
375
+ });
376
+
377
+ describe("the request body", () => {
378
+ it("rejects a body with no data object", async () => {
379
+ mockRequest.body = {};
380
+
381
+ await callRoute();
382
+
383
+ expect(nextFunction).toHaveBeenCalledWith(
384
+ new BadDataException("Balance type must be one of: SmsOrCall, AI"),
385
+ );
386
+ expect(ProjectService.adjustBalance).not.toHaveBeenCalled();
387
+ });
388
+
389
+ it("rejects an entirely absent body", async () => {
390
+ mockRequest.body = undefined as unknown as JSON;
391
+
392
+ await callRoute();
393
+
394
+ expect(nextFunction).toHaveBeenCalledWith(
395
+ new BadDataException("Balance type must be one of: SmsOrCall, AI"),
396
+ );
397
+ expect(ProjectService.adjustBalance).not.toHaveBeenCalled();
398
+ });
399
+ });
400
+
401
+ describe("audit trail", () => {
402
+ it("records which master admin moved the balance", async () => {
403
+ const adminUserId: ObjectID = ObjectID.generate();
404
+
405
+ jest
406
+ .spyOn(UserMiddleware, "getAccessTokenFromExpressRequest")
407
+ .mockReturnValue("a-master-admin-token");
408
+ jest.spyOn(JSONWebToken, "decode").mockReturnValue({
409
+ userId: adminUserId,
410
+ } as JSONWebTokenData);
411
+
412
+ mockRequest.body = validBody();
413
+
414
+ await callRoute();
415
+
416
+ expect(ProjectService.adjustBalance).toHaveBeenCalledWith(
417
+ expect.objectContaining({ adjustedByUserId: adminUserId }),
418
+ );
419
+ });
420
+
421
+ it("still adjusts when the acting admin cannot be resolved", async () => {
422
+ /*
423
+ * The identity is only used for the audit log line - the middleware has
424
+ * already authorized the request, so a decode failure must not block it.
425
+ */
426
+ jest
427
+ .spyOn(UserMiddleware, "getAccessTokenFromExpressRequest")
428
+ .mockImplementation(() => {
429
+ throw new Error("no cookie on this request");
430
+ });
431
+
432
+ mockRequest.body = validBody();
433
+
434
+ await callRoute();
435
+
436
+ expect(nextFunction).not.toHaveBeenCalled();
437
+ expect(ProjectService.adjustBalance).toHaveBeenCalledWith(
438
+ expect.objectContaining({ adjustedByUserId: undefined }),
439
+ );
440
+ });
441
+ });
442
+
443
+ describe("service errors", () => {
444
+ it("forwards them to next and sends no response", async () => {
445
+ const serviceError: BadDataException = new BadDataException(
446
+ "Cannot deduct 50.01 USD - the balance is only 50 USD",
447
+ );
448
+
449
+ ProjectService.adjustBalance = jest.fn().mockRejectedValue(serviceError);
450
+
451
+ mockRequest.body = validBody();
452
+
453
+ await callRoute();
454
+
455
+ expect(nextFunction).toHaveBeenCalledWith(serviceError);
456
+ expect(Response.sendJsonObjectResponse).not.toHaveBeenCalled();
457
+ });
458
+ });
459
+ });
@@ -0,0 +1,177 @@
1
+ import ProjectAPI from "../../../Server/API/ProjectAPI";
2
+ import ProjectService from "../../../Server/Services/ProjectService";
3
+ import {
4
+ NextFunction,
5
+ OneUptimeRequest,
6
+ OneUptimeResponse,
7
+ } from "../../../Server/Utils/Express";
8
+ import { mockRouter } from "./Helpers";
9
+ import { describe, expect, it } from "@jest/globals";
10
+ import ObjectID from "../../../Types/ObjectID";
11
+ import BadDataException from "../../../Types/Exception/BadDataException";
12
+ import ProjectBalanceType from "../../../Types/Billing/ProjectBalanceType";
13
+ import BalanceAdjustmentType from "../../../Types/Billing/BalanceAdjustmentType";
14
+
15
+ /*
16
+ * The self-hosted path. ProjectAPIAdjustBalance.test.ts module-mocks
17
+ * IsBillingEnabled to true for its whole file, so the billing-disabled
18
+ * behaviour needs its own module registry - hence a separate test file rather
19
+ * than another describe.
20
+ *
21
+ * Self-hosted installs have no prepaid balances to correct: nothing meters
22
+ * against them and nothing recharges them, so the whole feature is hidden in
23
+ * the UI. The API must refuse too rather than trust that.
24
+ */
25
+
26
+ jest.mock("../../../Server/EnvironmentConfig", () => {
27
+ return {
28
+ ...jest.requireActual("../../../Server/EnvironmentConfig"),
29
+ IsBillingEnabled: false,
30
+ };
31
+ });
32
+
33
+ jest.mock("../../../Server/Services/BillingService");
34
+
35
+ jest.mock("../../../Server/Utils/Express", () => {
36
+ return {
37
+ getRouter: () => {
38
+ return mockRouter;
39
+ },
40
+ };
41
+ });
42
+
43
+ jest.mock("../../../Server/Utils/Response", () => {
44
+ return {
45
+ sendEntityArrayResponse: jest.fn(),
46
+ sendJsonObjectResponse: jest.fn(),
47
+ sendEmptySuccessResponse: jest.fn(),
48
+ sendEntityResponse: jest.fn(),
49
+ };
50
+ });
51
+
52
+ jest.mock("../../../Server/Services/TeamMemberService");
53
+ jest.mock("../../../Server/Services/ProjectService");
54
+
55
+ const ROUTE: string = "/project/:id/adjust-balance";
56
+
57
+ describe("Adjust balance when billing is disabled", () => {
58
+ const billingDisabledError: BadDataException = new BadDataException(
59
+ "Billing is not enabled for this server",
60
+ );
61
+
62
+ let mockRequest: OneUptimeRequest;
63
+ let mockResponse: OneUptimeResponse;
64
+ let nextFunction: NextFunction;
65
+
66
+ beforeEach(() => {
67
+ new ProjectAPI();
68
+ mockRequest = {} as OneUptimeRequest;
69
+ mockResponse = {
70
+ send: jest.fn(),
71
+ json: jest.fn(),
72
+ status: jest.fn().mockReturnThis(),
73
+ } as unknown as OneUptimeResponse;
74
+ nextFunction = jest.fn();
75
+
76
+ mockRequest.params = { id: ObjectID.generate().toString() };
77
+ mockRequest.body = {
78
+ data: {
79
+ balanceType: ProjectBalanceType.SmsOrCall,
80
+ adjustmentType: BalanceAdjustmentType.Add,
81
+ amountInUSD: 20,
82
+ reason: "Goodwill credit",
83
+ },
84
+ };
85
+
86
+ ProjectService.adjustBalance = jest.fn().mockResolvedValue({
87
+ previousBalanceInUSDCents: 0,
88
+ newBalanceInUSDCents: 2000,
89
+ });
90
+ });
91
+
92
+ afterEach(() => {
93
+ jest.restoreAllMocks();
94
+ });
95
+
96
+ describe("PUT /project/:id/adjust-balance", () => {
97
+ it("rejects the request", async () => {
98
+ await mockRouter
99
+ .match("put", ROUTE)
100
+ .handlerFunction(mockRequest, mockResponse, nextFunction);
101
+
102
+ expect(nextFunction).toHaveBeenCalledWith(billingDisabledError);
103
+ });
104
+
105
+ it("does not reach the service", async () => {
106
+ await mockRouter
107
+ .match("put", ROUTE)
108
+ .handlerFunction(mockRequest, mockResponse, nextFunction);
109
+
110
+ expect(ProjectService.adjustBalance).not.toHaveBeenCalled();
111
+ });
112
+
113
+ it("rejects before validating the request body", async () => {
114
+ /*
115
+ * A self-hosted admin poking the endpoint should be told billing is off,
116
+ * not that their balance type is unrecognised.
117
+ */
118
+ mockRequest.body = {};
119
+
120
+ await mockRouter
121
+ .match("put", ROUTE)
122
+ .handlerFunction(mockRequest, mockResponse, nextFunction);
123
+
124
+ expect(nextFunction).toHaveBeenCalledWith(billingDisabledError);
125
+ });
126
+ });
127
+
128
+ describe("ProjectService.adjustBalance", () => {
129
+ it("refuses to move a balance", async () => {
130
+ /*
131
+ * The real service, not the auto-mock: this asserts the guard inside
132
+ * ProjectService itself, so the endpoint is not the only thing standing
133
+ * between a self-hosted install and a balance write.
134
+ */
135
+ const realProjectService: typeof ProjectService = (
136
+ jest.requireActual("../../../Server/Services/ProjectService") as {
137
+ default: typeof ProjectService;
138
+ }
139
+ ).default;
140
+
141
+ await expect(
142
+ realProjectService.adjustBalance({
143
+ projectId: ObjectID.generate(),
144
+ balanceType: ProjectBalanceType.SmsOrCall,
145
+ adjustmentType: BalanceAdjustmentType.Add,
146
+ amountInUSDCents: 2000,
147
+ reason: "Goodwill credit",
148
+ }),
149
+ ).rejects.toThrow("Billing is not enabled for this server");
150
+ });
151
+
152
+ it("refuses before it reads the project", async () => {
153
+ const realProjectService: typeof ProjectService = (
154
+ jest.requireActual("../../../Server/Services/ProjectService") as {
155
+ default: typeof ProjectService;
156
+ }
157
+ ).default;
158
+
159
+ const findOneById: jest.SpyInstance = jest.spyOn(
160
+ realProjectService,
161
+ "findOneById",
162
+ );
163
+
164
+ await expect(
165
+ realProjectService.adjustBalance({
166
+ projectId: ObjectID.generate(),
167
+ balanceType: ProjectBalanceType.AI,
168
+ adjustmentType: BalanceAdjustmentType.Set,
169
+ amountInUSDCents: 0,
170
+ reason: "Zeroing out",
171
+ }),
172
+ ).rejects.toThrow("Billing is not enabled for this server");
173
+
174
+ expect(findOneById).not.toHaveBeenCalled();
175
+ });
176
+ });
177
+ });