@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,615 @@
1
+ import ProjectService, {
2
+ MAX_BALANCE_ADJUSTMENT_IN_USD_CENTS,
3
+ ProjectBalanceAdjustmentResult,
4
+ } from "../../../Server/Services/ProjectService";
5
+ import Project from "../../../Models/DatabaseModels/Project";
6
+ import ProjectBalanceType from "../../../Types/Billing/ProjectBalanceType";
7
+ import BalanceAdjustmentType from "../../../Types/Billing/BalanceAdjustmentType";
8
+ import ObjectID from "../../../Types/ObjectID";
9
+ import logger from "../../../Server/Utils/Logger";
10
+ import { describe, expect, it, afterEach } from "@jest/globals";
11
+
12
+ /*
13
+ * ProjectService.adjustBalance is the only way a project's prepaid balances
14
+ * move without a payment behind them. Two things matter and are asserted
15
+ * throughout: the write is a single atomic delta (SMS sends and AI runs are
16
+ * deducting from the same column concurrently, so a read-then-write would eat
17
+ * whichever charge landed in between), and every guard that stands between a
18
+ * mistyped number and a customer's balance actually fires.
19
+ *
20
+ * Everything below the service boundary is spied: no database.
21
+ */
22
+
23
+ jest.mock("../../../Server/EnvironmentConfig", () => {
24
+ return {
25
+ ...jest.requireActual("../../../Server/EnvironmentConfig"),
26
+ IsBillingEnabled: true,
27
+ };
28
+ });
29
+
30
+ const PROJECT_ID: ObjectID = new ObjectID(
31
+ "22222222-2222-4222-8222-222222222222",
32
+ );
33
+
34
+ const ADMIN_USER_ID: ObjectID = new ObjectID(
35
+ "33333333-3333-4333-8333-333333333333",
36
+ );
37
+
38
+ const SMS_COLUMN: string = "smsOrCallCurrentBalanceInUSDCents";
39
+ const AI_COLUMN: string = "aiCurrentBalanceInUSDCents";
40
+
41
+ interface AdjustBalanceSpies {
42
+ findOneById: jest.SpyInstance;
43
+ atomicAdd: jest.SpyInstance;
44
+ }
45
+
46
+ function setup(data?: {
47
+ project?: Partial<Project> | null;
48
+ }): AdjustBalanceSpies {
49
+ const project: Partial<Project> | null =
50
+ data && "project" in data
51
+ ? data.project!
52
+ : ({
53
+ id: PROJECT_ID,
54
+ _id: PROJECT_ID.toString(),
55
+ [SMS_COLUMN]: 5000,
56
+ [AI_COLUMN]: 2500,
57
+ } as unknown as Partial<Project>);
58
+
59
+ return {
60
+ findOneById: jest
61
+ .spyOn(ProjectService, "findOneById")
62
+ .mockResolvedValue(project as Project | null),
63
+ atomicAdd: jest
64
+ .spyOn(ProjectService, "atomicAddToColumnsByIdWithoutHooks")
65
+ .mockResolvedValue(undefined),
66
+ };
67
+ }
68
+
69
+ type AdjustArgs = Parameters<typeof ProjectService.adjustBalance>[0];
70
+
71
+ function adjustArgs(overrides?: Partial<AdjustArgs>): AdjustArgs {
72
+ return {
73
+ projectId: PROJECT_ID,
74
+ balanceType: ProjectBalanceType.SmsOrCall,
75
+ adjustmentType: BalanceAdjustmentType.Add,
76
+ amountInUSDCents: 1000,
77
+ reason: "Refund for the SMS charged during the 12 Jan outage",
78
+ adjustedByUserId: ADMIN_USER_ID,
79
+ ...overrides,
80
+ };
81
+ }
82
+
83
+ describe("ProjectService.adjustBalance", () => {
84
+ afterEach(() => {
85
+ jest.restoreAllMocks();
86
+ });
87
+
88
+ describe("adding to a balance", () => {
89
+ it("writes a single positive atomic delta rather than an absolute value", async () => {
90
+ const spies: AdjustBalanceSpies = setup();
91
+
92
+ await ProjectService.adjustBalance(
93
+ adjustArgs({ amountInUSDCents: 1000 }),
94
+ );
95
+
96
+ expect(spies.atomicAdd).toHaveBeenCalledTimes(1);
97
+ expect(spies.atomicAdd.mock.calls[0][0].id).toEqual(PROJECT_ID);
98
+ expect(spies.atomicAdd.mock.calls[0][0].add).toEqual({
99
+ [SMS_COLUMN]: 1000,
100
+ });
101
+ });
102
+
103
+ it("reports the balance before and after", async () => {
104
+ setup();
105
+
106
+ const result: ProjectBalanceAdjustmentResult =
107
+ await ProjectService.adjustBalance(
108
+ adjustArgs({ amountInUSDCents: 1000 }),
109
+ );
110
+
111
+ expect(result).toEqual({
112
+ previousBalanceInUSDCents: 5000,
113
+ newBalanceInUSDCents: 6000,
114
+ });
115
+ });
116
+
117
+ it("targets the AI column when the AI balance is chosen", async () => {
118
+ const spies: AdjustBalanceSpies = setup();
119
+
120
+ const result: ProjectBalanceAdjustmentResult =
121
+ await ProjectService.adjustBalance(
122
+ adjustArgs({
123
+ balanceType: ProjectBalanceType.AI,
124
+ amountInUSDCents: 750,
125
+ }),
126
+ );
127
+
128
+ expect(spies.atomicAdd.mock.calls[0][0].add).toEqual({
129
+ [AI_COLUMN]: 750,
130
+ });
131
+ expect(result).toEqual({
132
+ previousBalanceInUSDCents: 2500,
133
+ newBalanceInUSDCents: 3250,
134
+ });
135
+ });
136
+
137
+ it("treats a missing balance column as zero", async () => {
138
+ setup({
139
+ project: {
140
+ id: PROJECT_ID,
141
+ _id: PROJECT_ID.toString(),
142
+ } as unknown as Partial<Project>,
143
+ });
144
+
145
+ const result: ProjectBalanceAdjustmentResult =
146
+ await ProjectService.adjustBalance(
147
+ adjustArgs({ amountInUSDCents: 1000 }),
148
+ );
149
+
150
+ expect(result).toEqual({
151
+ previousBalanceInUSDCents: 0,
152
+ newBalanceInUSDCents: 1000,
153
+ });
154
+ });
155
+ });
156
+
157
+ describe("deducting from a balance", () => {
158
+ it("writes a negative atomic delta", async () => {
159
+ const spies: AdjustBalanceSpies = setup();
160
+
161
+ await ProjectService.adjustBalance(
162
+ adjustArgs({
163
+ adjustmentType: BalanceAdjustmentType.Deduct,
164
+ amountInUSDCents: 1000,
165
+ }),
166
+ );
167
+
168
+ expect(spies.atomicAdd.mock.calls[0][0].add).toEqual({
169
+ [SMS_COLUMN]: -1000,
170
+ });
171
+ });
172
+
173
+ it("allows deducting the balance down to exactly zero", async () => {
174
+ setup();
175
+
176
+ const result: ProjectBalanceAdjustmentResult =
177
+ await ProjectService.adjustBalance(
178
+ adjustArgs({
179
+ adjustmentType: BalanceAdjustmentType.Deduct,
180
+ amountInUSDCents: 5000,
181
+ }),
182
+ );
183
+
184
+ expect(result.newBalanceInUSDCents).toBe(0);
185
+ });
186
+
187
+ it("refuses to overdraw the balance, and writes nothing", async () => {
188
+ /*
189
+ * Usage is allowed to push a balance negative; a manual deduction that
190
+ * does is a typo every time, and the customer would then have to pay off
191
+ * a debt staff invented.
192
+ */
193
+ const spies: AdjustBalanceSpies = setup();
194
+
195
+ await expect(
196
+ ProjectService.adjustBalance(
197
+ adjustArgs({
198
+ adjustmentType: BalanceAdjustmentType.Deduct,
199
+ amountInUSDCents: 5001,
200
+ }),
201
+ ),
202
+ ).rejects.toThrow("Cannot deduct 50.01 USD - the balance is only 50 USD");
203
+
204
+ expect(spies.atomicAdd).not.toHaveBeenCalled();
205
+ });
206
+
207
+ it("checks the overdraw against the balance being adjusted, not the other one", async () => {
208
+ /*
209
+ * The AI balance here is 2500 and the SMS balance 5000. Deducting 4000
210
+ * of AI must fail even though the project holds 7500 across both.
211
+ */
212
+ const spies: AdjustBalanceSpies = setup();
213
+
214
+ await expect(
215
+ ProjectService.adjustBalance(
216
+ adjustArgs({
217
+ balanceType: ProjectBalanceType.AI,
218
+ adjustmentType: BalanceAdjustmentType.Deduct,
219
+ amountInUSDCents: 4000,
220
+ }),
221
+ ),
222
+ ).rejects.toThrow("Cannot deduct 40 USD - the balance is only 25 USD");
223
+
224
+ expect(spies.atomicAdd).not.toHaveBeenCalled();
225
+ });
226
+ });
227
+
228
+ describe("setting a balance", () => {
229
+ it("assigns the absolute value instead of a delta", async () => {
230
+ const spies: AdjustBalanceSpies = setup();
231
+
232
+ const result: ProjectBalanceAdjustmentResult =
233
+ await ProjectService.adjustBalance(
234
+ adjustArgs({
235
+ adjustmentType: BalanceAdjustmentType.Set,
236
+ amountInUSDCents: 250,
237
+ }),
238
+ );
239
+
240
+ expect(spies.atomicAdd.mock.calls[0][0].add).toEqual({});
241
+ expect(spies.atomicAdd.mock.calls[0][0].set).toMatchObject({
242
+ [SMS_COLUMN]: 250,
243
+ });
244
+ expect(result).toEqual({
245
+ previousBalanceInUSDCents: 5000,
246
+ newBalanceInUSDCents: 250,
247
+ });
248
+ });
249
+
250
+ it("allows setting a balance to zero", async () => {
251
+ /*
252
+ * Unlike add and deduct, zero is a real instruction here - "this project
253
+ * should have no balance" - so it must not be rejected as a no-op.
254
+ */
255
+ const spies: AdjustBalanceSpies = setup();
256
+
257
+ const result: ProjectBalanceAdjustmentResult =
258
+ await ProjectService.adjustBalance(
259
+ adjustArgs({
260
+ adjustmentType: BalanceAdjustmentType.Set,
261
+ amountInUSDCents: 0,
262
+ }),
263
+ );
264
+
265
+ expect(result.newBalanceInUSDCents).toBe(0);
266
+ expect(spies.atomicAdd.mock.calls[0][0].set).toMatchObject({
267
+ [SMS_COLUMN]: 0,
268
+ });
269
+ });
270
+ });
271
+
272
+ describe("low balance notification latches", () => {
273
+ /*
274
+ * The paid recharge paths clear these flags when they top a project up.
275
+ * A manual top-up has to do the same, or the flags keep claiming the
276
+ * owners were already warned - about the shortfall this adjustment fixed -
277
+ * and the next genuine low balance passes in silence.
278
+ */
279
+
280
+ it("resets the SMS latches when the SMS balance goes up", async () => {
281
+ const spies: AdjustBalanceSpies = setup();
282
+
283
+ await ProjectService.adjustBalance(
284
+ adjustArgs({ amountInUSDCents: 1000 }),
285
+ );
286
+
287
+ expect(spies.atomicAdd.mock.calls[0][0].set).toEqual({
288
+ lowCallAndSMSBalanceNotificationSentToOwners: false,
289
+ failedCallAndSMSBalanceChargeNotificationSentToOwners: false,
290
+ notEnabledSmsOrCallNotificationSentToOwners: false,
291
+ });
292
+ });
293
+
294
+ it("resets the AI latches when the AI balance goes up", async () => {
295
+ const spies: AdjustBalanceSpies = setup();
296
+
297
+ await ProjectService.adjustBalance(
298
+ adjustArgs({
299
+ balanceType: ProjectBalanceType.AI,
300
+ amountInUSDCents: 1000,
301
+ }),
302
+ );
303
+
304
+ expect(spies.atomicAdd.mock.calls[0][0].set).toEqual({
305
+ lowAiBalanceNotificationSentToOwners: false,
306
+ failedAiBalanceChargeNotificationSentToOwners: false,
307
+ notEnabledAiNotificationSentToOwners: false,
308
+ });
309
+ });
310
+
311
+ it("does not reset the other balance's latches", async () => {
312
+ const spies: AdjustBalanceSpies = setup();
313
+
314
+ await ProjectService.adjustBalance(
315
+ adjustArgs({
316
+ balanceType: ProjectBalanceType.AI,
317
+ amountInUSDCents: 1000,
318
+ }),
319
+ );
320
+
321
+ expect(spies.atomicAdd.mock.calls[0][0].set).not.toHaveProperty(
322
+ "lowCallAndSMSBalanceNotificationSentToOwners",
323
+ );
324
+ });
325
+
326
+ it("leaves the latches alone when the balance goes down", async () => {
327
+ // Nobody has been un-warned by a deduction.
328
+ const spies: AdjustBalanceSpies = setup();
329
+
330
+ await ProjectService.adjustBalance(
331
+ adjustArgs({
332
+ adjustmentType: BalanceAdjustmentType.Deduct,
333
+ amountInUSDCents: 1000,
334
+ }),
335
+ );
336
+
337
+ expect(spies.atomicAdd.mock.calls[0][0].set).toEqual({});
338
+ });
339
+
340
+ it("resets the latches when Set raises the balance", async () => {
341
+ const spies: AdjustBalanceSpies = setup();
342
+
343
+ await ProjectService.adjustBalance(
344
+ adjustArgs({
345
+ adjustmentType: BalanceAdjustmentType.Set,
346
+ amountInUSDCents: 9000,
347
+ }),
348
+ );
349
+
350
+ expect(spies.atomicAdd.mock.calls[0][0].set).toMatchObject({
351
+ lowCallAndSMSBalanceNotificationSentToOwners: false,
352
+ });
353
+ });
354
+
355
+ it("does not reset the latches when Set lowers the balance", async () => {
356
+ const spies: AdjustBalanceSpies = setup();
357
+
358
+ await ProjectService.adjustBalance(
359
+ adjustArgs({
360
+ adjustmentType: BalanceAdjustmentType.Set,
361
+ amountInUSDCents: 100,
362
+ }),
363
+ );
364
+
365
+ expect(spies.atomicAdd.mock.calls[0][0].set).toEqual({
366
+ [SMS_COLUMN]: 100,
367
+ });
368
+ });
369
+ });
370
+
371
+ describe("amount validation", () => {
372
+ it.each([
373
+ ["not a number", "twenty" as unknown as number],
374
+ ["NaN", Number.NaN],
375
+ ["Infinity", Number.POSITIVE_INFINITY],
376
+ ["-Infinity", Number.NEGATIVE_INFINITY],
377
+ ])(
378
+ "rejects an amount that is %s",
379
+ async (_label: string, amount: number) => {
380
+ const spies: AdjustBalanceSpies = setup();
381
+
382
+ await expect(
383
+ ProjectService.adjustBalance(
384
+ adjustArgs({ amountInUSDCents: amount }),
385
+ ),
386
+ ).rejects.toThrow("Adjustment amount is not a valid number");
387
+
388
+ expect(spies.atomicAdd).not.toHaveBeenCalled();
389
+ },
390
+ );
391
+
392
+ it("rejects a fractional cent", async () => {
393
+ /*
394
+ * A fraction of a cent cannot be stored, and rounding it here would make
395
+ * the balance disagree with the amount the audit log records.
396
+ */
397
+ const spies: AdjustBalanceSpies = setup();
398
+
399
+ await expect(
400
+ ProjectService.adjustBalance(adjustArgs({ amountInUSDCents: 10.5 })),
401
+ ).rejects.toThrow("Adjustment amount must be a whole number of cents");
402
+
403
+ expect(spies.atomicAdd).not.toHaveBeenCalled();
404
+ });
405
+
406
+ it("rejects a negative amount", async () => {
407
+ /*
408
+ * Direction is carried by adjustmentType. A negative amount alongside
409
+ * Deduct would otherwise quietly ADD to the balance.
410
+ */
411
+ const spies: AdjustBalanceSpies = setup();
412
+
413
+ await expect(
414
+ ProjectService.adjustBalance(
415
+ adjustArgs({
416
+ adjustmentType: BalanceAdjustmentType.Deduct,
417
+ amountInUSDCents: -1000,
418
+ }),
419
+ ),
420
+ ).rejects.toThrow("Adjustment amount cannot be negative");
421
+
422
+ expect(spies.atomicAdd).not.toHaveBeenCalled();
423
+ });
424
+
425
+ it.each([BalanceAdjustmentType.Add, BalanceAdjustmentType.Deduct])(
426
+ "rejects a zero amount for %s",
427
+ async (adjustmentType: BalanceAdjustmentType) => {
428
+ const spies: AdjustBalanceSpies = setup();
429
+
430
+ await expect(
431
+ ProjectService.adjustBalance(
432
+ adjustArgs({ adjustmentType, amountInUSDCents: 0 }),
433
+ ),
434
+ ).rejects.toThrow("Adjustment amount must be greater than zero");
435
+
436
+ expect(spies.atomicAdd).not.toHaveBeenCalled();
437
+ },
438
+ );
439
+
440
+ it("rejects an amount over the single-adjustment ceiling", async () => {
441
+ // Staff type dollars into a cents column; the classic slip is 100x.
442
+ const spies: AdjustBalanceSpies = setup();
443
+
444
+ await expect(
445
+ ProjectService.adjustBalance(
446
+ adjustArgs({
447
+ amountInUSDCents: MAX_BALANCE_ADJUSTMENT_IN_USD_CENTS + 1,
448
+ }),
449
+ ),
450
+ ).rejects.toThrow(
451
+ "Adjustment amount cannot be more than 10000 USD in a single adjustment",
452
+ );
453
+
454
+ expect(spies.atomicAdd).not.toHaveBeenCalled();
455
+ });
456
+
457
+ it("allows an amount exactly at the ceiling", async () => {
458
+ const spies: AdjustBalanceSpies = setup();
459
+
460
+ await ProjectService.adjustBalance(
461
+ adjustArgs({ amountInUSDCents: MAX_BALANCE_ADJUSTMENT_IN_USD_CENTS }),
462
+ );
463
+
464
+ expect(spies.atomicAdd).toHaveBeenCalledTimes(1);
465
+ });
466
+
467
+ it("applies the ceiling to Set as well", async () => {
468
+ const spies: AdjustBalanceSpies = setup();
469
+
470
+ await expect(
471
+ ProjectService.adjustBalance(
472
+ adjustArgs({
473
+ adjustmentType: BalanceAdjustmentType.Set,
474
+ amountInUSDCents: MAX_BALANCE_ADJUSTMENT_IN_USD_CENTS + 1,
475
+ }),
476
+ ),
477
+ ).rejects.toThrow("cannot be more than 10000 USD");
478
+
479
+ expect(spies.atomicAdd).not.toHaveBeenCalled();
480
+ });
481
+ });
482
+
483
+ describe("reason validation", () => {
484
+ it.each([
485
+ ["empty", ""],
486
+ ["whitespace only", " "],
487
+ ])("rejects a %s reason", async (_label: string, reason: string) => {
488
+ const spies: AdjustBalanceSpies = setup();
489
+
490
+ await expect(
491
+ ProjectService.adjustBalance(adjustArgs({ reason })),
492
+ ).rejects.toThrow("A reason is required to adjust the balance");
493
+
494
+ expect(spies.atomicAdd).not.toHaveBeenCalled();
495
+ });
496
+
497
+ it("rejects a missing reason", async () => {
498
+ const spies: AdjustBalanceSpies = setup();
499
+
500
+ await expect(
501
+ ProjectService.adjustBalance(
502
+ adjustArgs({ reason: undefined as unknown as string }),
503
+ ),
504
+ ).rejects.toThrow("A reason is required to adjust the balance");
505
+
506
+ expect(spies.atomicAdd).not.toHaveBeenCalled();
507
+ });
508
+ });
509
+
510
+ describe("balance type validation", () => {
511
+ it("rejects an unknown balance type", async () => {
512
+ const spies: AdjustBalanceSpies = setup();
513
+
514
+ await expect(
515
+ ProjectService.adjustBalance(
516
+ adjustArgs({
517
+ balanceType: "Bitcoin" as unknown as ProjectBalanceType,
518
+ }),
519
+ ),
520
+ ).rejects.toThrow("Invalid balance type: Bitcoin");
521
+
522
+ expect(spies.atomicAdd).not.toHaveBeenCalled();
523
+ });
524
+ });
525
+
526
+ describe("the project", () => {
527
+ it("rejects an unknown project", async () => {
528
+ const spies: AdjustBalanceSpies = setup({ project: null });
529
+
530
+ await expect(ProjectService.adjustBalance(adjustArgs())).rejects.toThrow(
531
+ "Project not found",
532
+ );
533
+
534
+ expect(spies.atomicAdd).not.toHaveBeenCalled();
535
+ });
536
+
537
+ it("reads only the balance column it is about to move", async () => {
538
+ const spies: AdjustBalanceSpies = setup();
539
+
540
+ await ProjectService.adjustBalance(
541
+ adjustArgs({ balanceType: ProjectBalanceType.AI }),
542
+ );
543
+
544
+ expect(spies.findOneById).toHaveBeenCalledWith({
545
+ id: PROJECT_ID,
546
+ select: {
547
+ _id: true,
548
+ [AI_COLUMN]: true,
549
+ },
550
+ props: {
551
+ isRoot: true,
552
+ },
553
+ });
554
+ });
555
+ });
556
+
557
+ describe("audit trail", () => {
558
+ it("logs the adjustment, the acting admin and the stated reason", async () => {
559
+ /*
560
+ * There is no admin-action table, so this log line is the only record
561
+ * that staff moved a customer's balance.
562
+ */
563
+ setup();
564
+
565
+ const info: jest.SpyInstance = jest
566
+ .spyOn(logger, "info")
567
+ .mockImplementation(() => {});
568
+
569
+ await ProjectService.adjustBalance(
570
+ adjustArgs({
571
+ amountInUSDCents: 1000,
572
+ reason: "Goodwill credit agreed on ticket 4821",
573
+ }),
574
+ );
575
+
576
+ const line: string = String(info.mock.calls[0]?.[0]);
577
+
578
+ expect(line).toContain(PROJECT_ID.toString());
579
+ expect(line).toContain(ADMIN_USER_ID.toString());
580
+ expect(line).toContain("Goodwill credit agreed on ticket 4821");
581
+ expect(line).toContain("5000");
582
+ expect(line).toContain("6000");
583
+ });
584
+
585
+ it("records an unknown admin rather than failing when the identity is absent", async () => {
586
+ setup();
587
+
588
+ const info: jest.SpyInstance = jest
589
+ .spyOn(logger, "info")
590
+ .mockImplementation(() => {});
591
+
592
+ await ProjectService.adjustBalance(
593
+ adjustArgs({ adjustedByUserId: undefined }),
594
+ );
595
+
596
+ expect(String(info.mock.calls[0]?.[0])).toContain("unknown");
597
+ });
598
+
599
+ it("logs the trimmed reason", async () => {
600
+ setup();
601
+
602
+ const info: jest.SpyInstance = jest
603
+ .spyOn(logger, "info")
604
+ .mockImplementation(() => {});
605
+
606
+ await ProjectService.adjustBalance(
607
+ adjustArgs({ reason: " padded reason " }),
608
+ );
609
+
610
+ expect(String(info.mock.calls[0]?.[0])).toContain(
611
+ "Reason: padded reason",
612
+ );
613
+ });
614
+ });
615
+ });