@oneuptime/common 14.0.7 → 14.0.8

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 (440) hide show
  1. package/Models/DatabaseModels/Incident.ts +267 -1
  2. package/Models/DatabaseModels/IncidentCustomField.ts +202 -0
  3. package/Models/DatabaseModels/IncidentEpisode.ts +30 -0
  4. package/Models/DatabaseModels/IncidentTemplate.ts +126 -1
  5. package/Models/DatabaseModels/StatusPage.ts +55 -0
  6. package/Models/DatabaseModels/StatusPageSubscriber.ts +128 -0
  7. package/Server/API/IncidentAPI.ts +162 -0
  8. package/Server/API/StatusPageAPI.ts +487 -171
  9. package/Server/API/StatusPageSubscriberAPI.ts +63 -15
  10. package/Server/Infrastructure/Postgres/SchemaMigrations/1795300000000-SkipHiddenIncidentCreatedNotifications.ts +49 -0
  11. package/Server/Infrastructure/Postgres/SchemaMigrations/1795400000000-AddIncidentStatusPageScope.ts +108 -0
  12. package/Server/Infrastructure/Postgres/SchemaMigrations/1795500000000-AddIncidentTemplateStatusPageScopeFlag.ts +33 -0
  13. package/Server/Infrastructure/Postgres/SchemaMigrations/1795600000000-AddStatusPageSubscriberUnsubscribeToken.ts +51 -0
  14. package/Server/Infrastructure/Postgres/SchemaMigrations/1795700000000-AddStatusPageSubscriberIsAddedByTeam.ts +33 -0
  15. package/Server/Infrastructure/Postgres/SchemaMigrations/1795800000000-AddIncidentCustomFieldCreateAndNotificationSettings.ts +168 -0
  16. package/Server/Infrastructure/Postgres/SchemaMigrations/1795900000000-AddSubscriberNotificationClaimedAt.ts +44 -0
  17. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +14 -0
  18. package/Server/Infrastructure/Semaphore.ts +10 -0
  19. package/Server/Middleware/IdentityRateLimit.ts +108 -3
  20. package/Server/Middleware/SubscriberNotificationPreviewRateLimit.ts +237 -0
  21. package/Server/Middleware/SubscriberNotificationTestSendRateLimit.ts +304 -0
  22. package/Server/Services/DatabaseService.ts +93 -10
  23. package/Server/Services/IncidentCustomFieldService.ts +276 -1
  24. package/Server/Services/IncidentEpisodePublicNoteService.ts +28 -0
  25. package/Server/Services/IncidentEpisodeService.ts +15 -0
  26. package/Server/Services/IncidentEpisodeStateTimelineService.ts +27 -1
  27. package/Server/Services/IncidentPublicNoteService.ts +30 -1
  28. package/Server/Services/IncidentService.ts +1535 -65
  29. package/Server/Services/IncidentStateTimelineService.ts +13 -0
  30. package/Server/Services/IncidentTemplateService.ts +109 -0
  31. package/Server/Services/ScheduledMaintenancePublicNoteService.ts +30 -1
  32. package/Server/Services/ScheduledMaintenanceService.ts +68 -18
  33. package/Server/Services/SmsService.ts +10 -0
  34. package/Server/Services/StatusPageService.ts +65 -8
  35. package/Server/Services/StatusPageSubscriberNotificationTemplateService.ts +299 -37
  36. package/Server/Services/StatusPageSubscriberNotificationTemplateStatusPageService.ts +129 -0
  37. package/Server/Services/StatusPageSubscriberService.ts +1161 -64
  38. package/Server/Types/Markdown.ts +153 -6
  39. package/Server/Types/Workflow/Components/Email.ts +61 -2
  40. package/Server/Utils/CustomField/CustomFieldRename.ts +378 -0
  41. package/Server/Utils/DataSource/EgressGuard.ts +66 -21
  42. package/Server/Utils/Mail/PinnedSmtpSocket.ts +130 -0
  43. package/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.ts +10 -0
  44. package/Server/Utils/SSRFProtection.ts +76 -16
  45. package/Server/Utils/StatusPage/IncidentStatusPageScope.ts +948 -0
  46. package/Server/Utils/StatusPage/IncidentSubscriberAudienceBuilder.ts +656 -0
  47. package/Server/Utils/StatusPage/IncidentTemplateVariableBuilder.ts +805 -0
  48. package/Server/Utils/StatusPage/StatusPageExclusion.ts +18 -0
  49. package/Server/Utils/StatusPage/StatusPageReadAccess.ts +125 -0
  50. package/Server/Utils/StatusPage/StatusPageSubscriberUnsubscribeNotice.ts +151 -0
  51. package/Server/Utils/StatusPage/StatusPageSubscriberUnsubscribeToken.ts +70 -0
  52. package/Server/Utils/StatusPage/SubscriberIncidentEmailBuilder.ts +421 -0
  53. package/Server/Utils/StatusPage/SubscriberNotificationClaim.ts +107 -0
  54. package/Server/Utils/StatusPage/SubscriberNotificationDeliveryRecord.ts +1049 -0
  55. package/Server/Utils/StatusPage/SubscriberNotificationFanOut.ts +180 -0
  56. package/Server/Utils/StatusPage/SubscriberNotificationPreviewBuilder.ts +839 -0
  57. package/Server/Utils/StatusPage/SubscriberNotificationResendAccess.ts +393 -0
  58. package/Server/Utils/StatusPage/SubscriberNotificationRunLimit.ts +246 -0
  59. package/Server/Utils/StatusPage/SubscriberNotificationTiming.ts +133 -0
  60. package/Server/Utils/StatusPage/SubscriberTemplateIncidentRecordAccess.ts +215 -0
  61. package/Server/Utils/StatusPageResource.ts +51 -20
  62. package/Server/Utils/VM/VMAPI.ts +1 -0
  63. package/Server/Utils/VM/VMRunner.ts +10 -0
  64. package/Server/Utils/Workflow/OAuth2TokenClient.ts +10 -0
  65. package/Server/Views/Partials/SensitiveUrlToken.ejs +8 -5
  66. package/Tests/App/Accounts/RegisterVerifyEmail.test.tsx +748 -0
  67. package/Tests/App/Accounts/ResendVerificationEmail.test.tsx +1222 -0
  68. package/Tests/App/Accounts/VerificationEmailResendUtils.test.ts +797 -0
  69. package/Tests/App/Accounts/VerifyEmailPending.test.tsx +538 -0
  70. package/Tests/App/Accounts/VerifyEmailResend.test.tsx +779 -0
  71. package/Tests/App/Dashboard/CustomFieldsSettingsIncidentOptions.test.tsx +357 -0
  72. package/Tests/App/Dashboard/EventNotes.test.tsx +776 -0
  73. package/Tests/App/Dashboard/EventNotesPages.test.tsx +119 -0
  74. package/Tests/App/Dashboard/EventNotesUtil.test.ts +116 -17
  75. package/Tests/App/Dashboard/EventOverviewPages.test.tsx +242 -5
  76. package/Tests/App/Dashboard/IncidentChangeStateNotifyDefault.test.tsx +91 -1
  77. package/Tests/App/Dashboard/IncidentCreateDetailsStep.test.tsx +643 -0
  78. package/Tests/App/Dashboard/IncidentCreateFromAlerts.test.tsx +8 -1
  79. package/Tests/App/Dashboard/IncidentCreateStatusPageScope.test.tsx +601 -0
  80. package/Tests/App/Dashboard/IncidentCustomFieldTemplateVariables.test.tsx +386 -0
  81. package/Tests/App/Dashboard/IncidentNoteTemplatePlaceholders.test.tsx +124 -0
  82. package/Tests/App/Dashboard/IncidentNoteTemplateVariablesLoader.test.ts +305 -0
  83. package/Tests/App/Dashboard/IncidentOverviewNotifyDefault.test.tsx +112 -0
  84. package/Tests/App/Dashboard/IncidentSettingsRenotifyOnPublish.test.tsx +674 -0
  85. package/Tests/App/Dashboard/IncidentSettingsStatusPageScope.test.tsx +1051 -0
  86. package/Tests/App/Dashboard/IncidentTemplateCustomFields.test.tsx +384 -0
  87. package/Tests/App/Dashboard/IncidentTemplateStatusPageScopeCard.test.tsx +215 -0
  88. package/Tests/App/Dashboard/PublicNotePagesNotifyDefault.test.tsx +113 -1
  89. package/Tests/App/Dashboard/SubscriberAudienceSummary.test.tsx +512 -0
  90. package/Tests/App/Dashboard/SubscriberNotificationPreviewModal.test.tsx +837 -0
  91. package/Tests/App/Dashboard/SubscriberNotificationStatusLabel.test.tsx +88 -0
  92. package/Tests/App/Dashboard/SubscriberNotificationStatusResend.test.tsx +473 -0
  93. package/Tests/App/Dashboard/SubscriberTemplateLivePreview.test.tsx +196 -0
  94. package/Tests/App/Dashboard/TeamAddedSubscribersUnsubscribedNotice.test.tsx +248 -0
  95. package/Tests/App/SensitiveUrlTokenBootstrap.test.ts +64 -3
  96. package/Tests/App/StatusPage/StatusPageUnsubscribePage.test.tsx +412 -0
  97. package/Tests/Models/IncidentCustomFieldApiSchemaContract.test.ts +217 -0
  98. package/Tests/Models/IncidentCustomFieldCreateSettingsColumns.test.ts +252 -0
  99. package/Tests/Models/StatusPageSubscriberUnsubscribeColumns.test.ts +218 -0
  100. package/Tests/Server/API/ExpiredSessionRouteGuards.test.ts +7 -0
  101. package/Tests/Server/API/IncidentSubscriberAudienceAPI.test.ts +1408 -0
  102. package/Tests/Server/API/StatusPageIncidentScope.test.ts +1868 -0
  103. package/Tests/Server/API/StatusPageIncidentScopePostgres.test.ts +790 -0
  104. package/Tests/Server/API/StatusPageManageSubscriptionRecipient.test.ts +10 -1
  105. package/Tests/Server/API/StatusPageManageSubscriptionTemplateVariables.test.ts +206 -18
  106. package/Tests/Server/API/StatusPagePostmortemVisibility.test.ts +14 -2
  107. package/Tests/Server/API/StatusPageSubscriberCreateResponse.test.ts +217 -0
  108. package/Tests/Server/API/StatusPageSubscriberOwnership.test.ts +103 -2
  109. package/Tests/Server/API/StatusPageUnsubscribeAPI.test.ts +396 -0
  110. package/Tests/Server/Infrastructure/Postgres/AddIncidentStatusPageScopeMigration.test.ts +395 -0
  111. package/Tests/Server/Infrastructure/Postgres/AddIncidentTemplateStatusPageScopeFlagMigration.test.ts +150 -0
  112. package/Tests/Server/Infrastructure/Postgres/AddSubscriberNotificationClaimedAtMigration.test.ts +156 -0
  113. package/Tests/Server/Infrastructure/Postgres/SkipHiddenIncidentCreatedNotificationsMigration.test.ts +410 -0
  114. package/Tests/Server/Middleware/IdentityRateLimit.test.ts +551 -1
  115. package/Tests/Server/Middleware/SubscriberNotificationPreviewRateLimit.test.ts +238 -0
  116. package/Tests/Server/Middleware/SubscriberNotificationTestSendRateLimit.test.ts +245 -0
  117. package/Tests/Server/Services/AddStatusPageSubscriberUnsubscribeTokenMigration.test.ts +152 -0
  118. package/Tests/Server/Services/CrossProjectRelationWriteGuard.test.ts +221 -36
  119. package/Tests/Server/Services/DatabaseServiceCompareAndSetColumns.test.ts +191 -0
  120. package/Tests/Server/Services/IncidentCreatedRenotifyOnPublish.test.ts +681 -0
  121. package/Tests/Server/Services/IncidentCreatedResend.test.ts +775 -0
  122. package/Tests/Server/Services/IncidentCustomFieldRenamePostgres.test.ts +713 -0
  123. package/Tests/Server/Services/IncidentCustomFieldService.test.ts +751 -0
  124. package/Tests/Server/Services/IncidentCustomFieldValueValidation.test.ts +556 -0
  125. package/Tests/Server/Services/IncidentStatusPageScope.test.ts +2971 -0
  126. package/Tests/Server/Services/IncidentStatusPageScopeFeed.test.ts +131 -0
  127. package/Tests/Server/Services/IncidentStatusPageScopePostgres.test.ts +625 -0
  128. package/Tests/Server/Services/IncidentTemplateCustomFieldsCopy.test.ts +311 -0
  129. package/Tests/Server/Services/IncidentTemplateStatusPageScope.test.ts +363 -0
  130. package/Tests/Server/Services/PublicNoteEditFeedEventType.test.ts +213 -0
  131. package/Tests/Server/Services/PublicNoteResendAccess.test.ts +994 -0
  132. package/Tests/Server/Services/ScheduledMaintenanceSubscriberTemplateFormats.test.ts +289 -9
  133. package/Tests/Server/Services/SmsServiceFailIfNotSent.test.ts +67 -0
  134. package/Tests/Server/Services/StatusPageReportIncidentCounts.test.ts +610 -0
  135. package/Tests/Server/Services/StatusPageSubscriberAudienceCounts.test.ts +201 -0
  136. package/Tests/Server/Services/StatusPageSubscriberAudienceCountsPostgres.test.ts +271 -0
  137. package/Tests/Server/Services/StatusPageSubscriberConfirmationTemplateEscaping.test.ts +200 -0
  138. package/Tests/Server/Services/StatusPageSubscriberNotificationLogRedaction.test.ts +54 -0
  139. package/Tests/Server/Services/StatusPageSubscriberNotificationTemplateCompile.test.ts +37 -0
  140. package/Tests/Server/Services/StatusPageSubscriberNotificationTemplateIncidentRecordAccess.test.ts +772 -0
  141. package/Tests/Server/Services/StatusPageSubscriberNotificationTemplateLookup.test.ts +506 -0
  142. package/Tests/Server/Services/StatusPageSubscriberNotificationTemplateLookupPostgres.test.ts +261 -0
  143. package/Tests/Server/Services/StatusPageSubscriberReport.test.ts +21 -5
  144. package/Tests/Server/Services/StatusPageSubscriberUnsubscribe.test.ts +1618 -0
  145. package/Tests/Server/Services/StatusPageSubscriberUnsubscribeLinkSenders.test.ts +373 -0
  146. package/Tests/Server/Services/StatusPageSubscriberUnsubscribePostgres.test.ts +388 -0
  147. package/Tests/Server/Services/SubscriberNotificationInFlightRequeue.test.ts +500 -0
  148. package/Tests/Server/Types/MarkdownEmailLinks.test.ts +320 -0
  149. package/Tests/Server/Types/Workflow/Components/Email.test.ts +491 -1
  150. package/Tests/Server/Types/Workflow/Components/EmailSmtpPinning.test.ts +398 -0
  151. package/Tests/Server/Utils/CustomField/CustomFieldRename.test.ts +586 -0
  152. package/Tests/Server/Utils/DataSource/EgressGuard.test.ts +246 -10
  153. package/Tests/Server/Utils/DataSource/EgressGuardDeploymentConfig.test.ts +226 -0
  154. package/Tests/Server/Utils/DataSource/EgressGuardPinning.test.ts +131 -0
  155. package/Tests/Server/Utils/DataSource/HttpFetch.test.ts +4 -0
  156. package/Tests/Server/Utils/EgressPolicyEnvironment.ts +45 -0
  157. package/Tests/Server/Utils/Mail/PinnedSmtpSocket.test.ts +409 -0
  158. package/Tests/Server/Utils/Monitor/MonitorCriteriaObservationBuilderUnits.test.ts +23 -0
  159. package/Tests/Server/Utils/SSRFProtection.test.ts +4 -0
  160. package/Tests/Server/Utils/SSRFProtectionCloudServiceAddresses.test.ts +4 -0
  161. package/Tests/Server/Utils/SSRFProtectionPrivateNetwork.test.ts +4 -0
  162. package/Tests/Server/Utils/SSRFProtectionResolutionDetail.test.ts +314 -0
  163. package/Tests/Server/Utils/StatusPage/IncidentStatusPageScope.test.ts +1699 -0
  164. package/Tests/Server/Utils/StatusPage/IncidentStatusPageScopeQueriesPostgres.test.ts +508 -0
  165. package/Tests/Server/Utils/StatusPage/IncidentSubscriberAudienceCallerPermission.test.ts +246 -0
  166. package/Tests/Server/Utils/StatusPage/IncidentTemplateVariableBuilder.test.ts +1348 -0
  167. package/Tests/Server/Utils/StatusPage/StatusPageReadAccess.test.ts +221 -0
  168. package/Tests/Server/Utils/StatusPage/StatusPageSubscriberUnsubscribeNotice.test.ts +132 -0
  169. package/Tests/Server/Utils/StatusPage/StatusPageSubscriberUnsubscribeToken.test.ts +155 -0
  170. package/Tests/Server/Utils/StatusPage/SubscriberIncidentEmailBuilder.test.ts +708 -0
  171. package/Tests/Server/Utils/StatusPage/SubscriberNotificationClaim.test.ts +255 -0
  172. package/Tests/Server/Utils/StatusPage/SubscriberNotificationClaimPostgres.test.ts +528 -0
  173. package/Tests/Server/Utils/StatusPage/SubscriberNotificationDeliveryRecord.test.ts +1238 -0
  174. package/Tests/Server/Utils/StatusPage/SubscriberNotificationFanOut.test.ts +472 -0
  175. package/Tests/Server/Utils/StatusPage/SubscriberNotificationPreviewBuilder.test.ts +1168 -0
  176. package/Tests/Server/Utils/StatusPage/SubscriberNotificationRunLimit.test.ts +275 -0
  177. package/Tests/Server/Utils/StatusPage/SubscriberNotificationTiming.test.ts +131 -0
  178. package/Tests/Server/Utils/StatusPageResource.test.ts +98 -0
  179. package/Tests/Server/Utils/VM/VMRunnerPrivateNetworkWiring.test.ts +17 -0
  180. package/Tests/Server/Utils/VM/VMRunnerSsrf.test.ts +75 -0
  181. package/Tests/Types/CustomField/CustomFieldSavedViews.test.ts +247 -0
  182. package/Tests/Types/CustomField/CustomFieldTemplateValues.test.ts +186 -0
  183. package/Tests/Types/CustomField/CustomFieldTypeCorrespondence.test.ts +48 -0
  184. package/Tests/Types/CustomField/CustomFieldValueFormat.test.ts +215 -0
  185. package/Tests/Types/CustomField/CustomFieldValueValidator.test.ts +343 -0
  186. package/Tests/Types/CustomField/CustomFieldVariableKey.test.ts +364 -0
  187. package/Tests/Types/SafeHtml.test.ts +102 -0
  188. package/Tests/Types/StatusPage/IncidentCreatedRenotify.test.ts +419 -0
  189. package/Tests/Types/StatusPage/IncidentCreatedResend.test.ts +118 -0
  190. package/Tests/Types/StatusPage/IncidentScopeAddedPagesNotification.test.ts +558 -0
  191. package/Tests/Types/StatusPage/IncidentSubscriberAudience.test.ts +284 -0
  192. package/Tests/Types/StatusPage/StatusPageSubscriberUnsubscribe.test.ts +341 -0
  193. package/Tests/Types/StatusPage/SubscriberNotificationPreview.test.ts +196 -0
  194. package/Tests/Types/StatusPage/SubscriberNotificationResend.test.ts +201 -0
  195. package/Tests/Types/StatusPage/SubscriberNotificationTemplateCompiler.test.ts +277 -0
  196. package/Tests/Types/StatusPage/SubscriberNotificationTemplatePreview.test.ts +179 -0
  197. package/Tests/Types/StatusPage/SubscriberNotificationTemplateVariables.test.ts +324 -0
  198. package/Tests/Types/StatusPage/SubscriberUpdateNotification.test.ts +60 -13
  199. package/Tests/UI/Components/CustomFields/CustomFieldFormFields.test.ts +432 -0
  200. package/Tests/UI/Components/CustomFields/CustomFieldModelFormFields.test.tsx +685 -0
  201. package/Tests/UI/Components/CustomFields/CustomFieldsDetailOrder.test.tsx +469 -0
  202. package/Tests/UI/Components/Forms/ModelFormCustomFieldInputs.test.tsx +325 -0
  203. package/Tests/UI/Components/Forms/ModelFormDropdownOptionsRefused.test.tsx +358 -0
  204. package/Tests/UI/Components/ModelTable/CustomFieldColumns.test.tsx +22 -0
  205. package/Tests/UI/Monitor/NetworkPathView.test.tsx +289 -0
  206. package/Tests/Utils/Incident/IncidentNoteTemplateVariables.test.ts +623 -0
  207. package/Tests/Utils/Markdown/MarkdownEscape.test.ts +89 -0
  208. package/Tests/Utils/ProbeNetworkFailureUtil.test.ts +992 -0
  209. package/Tests/Utils/RequestFailedDetails.test.ts +575 -0
  210. package/Types/CustomField/CustomFieldOrder.ts +55 -0
  211. package/Types/CustomField/CustomFieldSavedViews.ts +225 -0
  212. package/Types/CustomField/CustomFieldTemplateMerge.ts +71 -0
  213. package/Types/CustomField/CustomFieldType.ts +14 -0
  214. package/Types/CustomField/CustomFieldValueFormat.ts +161 -0
  215. package/Types/CustomField/CustomFieldValueValidator.ts +488 -0
  216. package/Types/CustomField/CustomFieldVariableKey.ts +211 -0
  217. package/Types/SafeHtml.ts +93 -0
  218. package/Types/StatusPage/IncidentCreatedRenotify.ts +224 -0
  219. package/Types/StatusPage/IncidentCreatedResend.ts +108 -0
  220. package/Types/StatusPage/IncidentScopeAddedPagesNotification.ts +343 -0
  221. package/Types/StatusPage/IncidentSubscriberAudience.ts +332 -0
  222. package/Types/StatusPage/StatusPageSubscriberUnsubscribe.ts +405 -0
  223. package/Types/StatusPage/SubscriberNotificationInterruption.ts +32 -0
  224. package/Types/StatusPage/SubscriberNotificationPreview.ts +322 -0
  225. package/Types/StatusPage/SubscriberNotificationResend.ts +132 -0
  226. package/Types/StatusPage/SubscriberNotificationTemplateCompiler.ts +180 -0
  227. package/Types/StatusPage/SubscriberNotificationTemplatePreview.ts +301 -0
  228. package/Types/StatusPage/SubscriberNotificationTemplateVariables.ts +217 -4
  229. package/Types/StatusPage/SubscriberUpdateNotification.ts +32 -11
  230. package/UI/Components/CustomFields/CustomFieldFormFields.ts +317 -0
  231. package/UI/Components/CustomFields/CustomFieldModelFormFields.tsx +395 -0
  232. package/UI/Components/CustomFields/CustomFieldsDetail.tsx +95 -55
  233. package/UI/Components/Forms/ModelForm.tsx +70 -8
  234. package/UI/Components/Forms/Validation.ts +1 -1
  235. package/UI/Components/ModelFormModal/ModelFormModal.tsx +2 -4
  236. package/UI/Components/ModelTable/AnalyticsModelTable.tsx +2 -6
  237. package/UI/Components/ModelTable/BaseModelTable.tsx +9 -8
  238. package/UI/Components/ModelTable/CustomFieldColumns.tsx +14 -1
  239. package/UI/Components/ModelTable/ModelTable.tsx +6 -7
  240. package/UI/Utils/SensitiveUrlToken.ts +10 -5
  241. package/Utils/API.ts +247 -2
  242. package/Utils/Incident/IncidentNoteTemplateVariables.ts +304 -0
  243. package/Utils/Markdown/MarkdownEscape.ts +65 -0
  244. package/Utils/ProbeNetworkFailureUtil.ts +673 -0
  245. package/build/dist/Models/DatabaseModels/Incident.js +267 -1
  246. package/build/dist/Models/DatabaseModels/Incident.js.map +1 -1
  247. package/build/dist/Models/DatabaseModels/IncidentCustomField.js +207 -1
  248. package/build/dist/Models/DatabaseModels/IncidentCustomField.js.map +1 -1
  249. package/build/dist/Models/DatabaseModels/IncidentEpisode.js +31 -0
  250. package/build/dist/Models/DatabaseModels/IncidentEpisode.js.map +1 -1
  251. package/build/dist/Models/DatabaseModels/IncidentTemplate.js +124 -1
  252. package/build/dist/Models/DatabaseModels/IncidentTemplate.js.map +1 -1
  253. package/build/dist/Models/DatabaseModels/StatusPage.js +56 -0
  254. package/build/dist/Models/DatabaseModels/StatusPage.js.map +1 -1
  255. package/build/dist/Models/DatabaseModels/StatusPageSubscriber.js +131 -0
  256. package/build/dist/Models/DatabaseModels/StatusPageSubscriber.js.map +1 -1
  257. package/build/dist/Server/API/IncidentAPI.js +100 -1
  258. package/build/dist/Server/API/IncidentAPI.js.map +1 -1
  259. package/build/dist/Server/API/StatusPageAPI.js +417 -176
  260. package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
  261. package/build/dist/Server/API/StatusPageSubscriberAPI.js +52 -13
  262. package/build/dist/Server/API/StatusPageSubscriberAPI.js.map +1 -1
  263. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795300000000-SkipHiddenIncidentCreatedNotifications.js +41 -0
  264. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795300000000-SkipHiddenIncidentCreatedNotifications.js.map +1 -0
  265. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795400000000-AddIncidentStatusPageScope.js +57 -0
  266. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795400000000-AddIncidentStatusPageScope.js.map +1 -0
  267. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795500000000-AddIncidentTemplateStatusPageScopeFlag.js +23 -0
  268. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795500000000-AddIncidentTemplateStatusPageScopeFlag.js.map +1 -0
  269. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795600000000-AddStatusPageSubscriberUnsubscribeToken.js +40 -0
  270. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795600000000-AddStatusPageSubscriberUnsubscribeToken.js.map +1 -0
  271. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795700000000-AddStatusPageSubscriberIsAddedByTeam.js +26 -0
  272. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795700000000-AddStatusPageSubscriberIsAddedByTeam.js.map +1 -0
  273. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795800000000-AddIncidentCustomFieldCreateAndNotificationSettings.js +89 -0
  274. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795800000000-AddIncidentCustomFieldCreateAndNotificationSettings.js.map +1 -0
  275. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795900000000-AddSubscriberNotificationClaimedAt.js +29 -0
  276. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795900000000-AddSubscriberNotificationClaimedAt.js.map +1 -0
  277. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +14 -0
  278. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  279. package/build/dist/Server/Infrastructure/Semaphore.js +3 -0
  280. package/build/dist/Server/Infrastructure/Semaphore.js.map +1 -1
  281. package/build/dist/Server/Middleware/IdentityRateLimit.js +89 -3
  282. package/build/dist/Server/Middleware/IdentityRateLimit.js.map +1 -1
  283. package/build/dist/Server/Middleware/SubscriberNotificationPreviewRateLimit.js +149 -0
  284. package/build/dist/Server/Middleware/SubscriberNotificationPreviewRateLimit.js.map +1 -0
  285. package/build/dist/Server/Middleware/SubscriberNotificationTestSendRateLimit.js +190 -0
  286. package/build/dist/Server/Middleware/SubscriberNotificationTestSendRateLimit.js.map +1 -0
  287. package/build/dist/Server/Services/DatabaseService.js +56 -7
  288. package/build/dist/Server/Services/DatabaseService.js.map +1 -1
  289. package/build/dist/Server/Services/IncidentCustomFieldService.js +204 -8
  290. package/build/dist/Server/Services/IncidentCustomFieldService.js.map +1 -1
  291. package/build/dist/Server/Services/IncidentEpisodePublicNoteService.js +24 -0
  292. package/build/dist/Server/Services/IncidentEpisodePublicNoteService.js.map +1 -1
  293. package/build/dist/Server/Services/IncidentEpisodeService.js +14 -0
  294. package/build/dist/Server/Services/IncidentEpisodeService.js.map +1 -1
  295. package/build/dist/Server/Services/IncidentEpisodeStateTimelineService.js +26 -0
  296. package/build/dist/Server/Services/IncidentEpisodeStateTimelineService.js.map +1 -1
  297. package/build/dist/Server/Services/IncidentPublicNoteService.js +26 -1
  298. package/build/dist/Server/Services/IncidentPublicNoteService.js.map +1 -1
  299. package/build/dist/Server/Services/IncidentService.js +1052 -0
  300. package/build/dist/Server/Services/IncidentService.js.map +1 -1
  301. package/build/dist/Server/Services/IncidentStateTimelineService.js +12 -0
  302. package/build/dist/Server/Services/IncidentStateTimelineService.js.map +1 -1
  303. package/build/dist/Server/Services/IncidentTemplateService.js +83 -0
  304. package/build/dist/Server/Services/IncidentTemplateService.js.map +1 -1
  305. package/build/dist/Server/Services/ScheduledMaintenancePublicNoteService.js +26 -1
  306. package/build/dist/Server/Services/ScheduledMaintenancePublicNoteService.js.map +1 -1
  307. package/build/dist/Server/Services/ScheduledMaintenanceService.js +47 -17
  308. package/build/dist/Server/Services/ScheduledMaintenanceService.js.map +1 -1
  309. package/build/dist/Server/Services/SmsService.js +2 -22
  310. package/build/dist/Server/Services/SmsService.js.map +1 -1
  311. package/build/dist/Server/Services/StatusPageService.js +47 -9
  312. package/build/dist/Server/Services/StatusPageService.js.map +1 -1
  313. package/build/dist/Server/Services/StatusPageSubscriberNotificationTemplateService.js +237 -32
  314. package/build/dist/Server/Services/StatusPageSubscriberNotificationTemplateService.js.map +1 -1
  315. package/build/dist/Server/Services/StatusPageSubscriberNotificationTemplateStatusPageService.js +109 -0
  316. package/build/dist/Server/Services/StatusPageSubscriberNotificationTemplateStatusPageService.js.map +1 -1
  317. package/build/dist/Server/Services/StatusPageSubscriberService.js +925 -99
  318. package/build/dist/Server/Services/StatusPageSubscriberService.js.map +1 -1
  319. package/build/dist/Server/Types/Markdown.js +110 -6
  320. package/build/dist/Server/Types/Markdown.js.map +1 -1
  321. package/build/dist/Server/Types/Workflow/Components/Email.js +55 -6
  322. package/build/dist/Server/Types/Workflow/Components/Email.js.map +1 -1
  323. package/build/dist/Server/Utils/CustomField/CustomFieldRename.js +211 -0
  324. package/build/dist/Server/Utils/CustomField/CustomFieldRename.js.map +1 -0
  325. package/build/dist/Server/Utils/DataSource/EgressGuard.js +41 -12
  326. package/build/dist/Server/Utils/DataSource/EgressGuard.js.map +1 -1
  327. package/build/dist/Server/Utils/Mail/PinnedSmtpSocket.js +101 -0
  328. package/build/dist/Server/Utils/Mail/PinnedSmtpSocket.js.map +1 -0
  329. package/build/dist/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.js +9 -0
  330. package/build/dist/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.js.map +1 -1
  331. package/build/dist/Server/Utils/SSRFProtection.js +48 -6
  332. package/build/dist/Server/Utils/SSRFProtection.js.map +1 -1
  333. package/build/dist/Server/Utils/StatusPage/IncidentStatusPageScope.js +631 -0
  334. package/build/dist/Server/Utils/StatusPage/IncidentStatusPageScope.js.map +1 -0
  335. package/build/dist/Server/Utils/StatusPage/IncidentSubscriberAudienceBuilder.js +403 -0
  336. package/build/dist/Server/Utils/StatusPage/IncidentSubscriberAudienceBuilder.js.map +1 -0
  337. package/build/dist/Server/Utils/StatusPage/IncidentTemplateVariableBuilder.js +451 -0
  338. package/build/dist/Server/Utils/StatusPage/IncidentTemplateVariableBuilder.js.map +1 -0
  339. package/build/dist/Server/Utils/StatusPage/StatusPageExclusion.js +13 -0
  340. package/build/dist/Server/Utils/StatusPage/StatusPageExclusion.js.map +1 -0
  341. package/build/dist/Server/Utils/StatusPage/StatusPageReadAccess.js +91 -0
  342. package/build/dist/Server/Utils/StatusPage/StatusPageReadAccess.js.map +1 -0
  343. package/build/dist/Server/Utils/StatusPage/StatusPageSubscriberUnsubscribeNotice.js +101 -0
  344. package/build/dist/Server/Utils/StatusPage/StatusPageSubscriberUnsubscribeNotice.js.map +1 -0
  345. package/build/dist/Server/Utils/StatusPage/StatusPageSubscriberUnsubscribeToken.js +47 -0
  346. package/build/dist/Server/Utils/StatusPage/StatusPageSubscriberUnsubscribeToken.js.map +1 -0
  347. package/build/dist/Server/Utils/StatusPage/SubscriberIncidentEmailBuilder.js +273 -0
  348. package/build/dist/Server/Utils/StatusPage/SubscriberIncidentEmailBuilder.js.map +1 -0
  349. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationClaim.js +73 -0
  350. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationClaim.js.map +1 -0
  351. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationDeliveryRecord.js +722 -0
  352. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationDeliveryRecord.js.map +1 -0
  353. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationFanOut.js +102 -0
  354. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationFanOut.js.map +1 -0
  355. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationPreviewBuilder.js +527 -0
  356. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationPreviewBuilder.js.map +1 -0
  357. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationResendAccess.js +278 -0
  358. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationResendAccess.js.map +1 -0
  359. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationRunLimit.js +137 -0
  360. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationRunLimit.js.map +1 -0
  361. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationTiming.js +103 -0
  362. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationTiming.js.map +1 -0
  363. package/build/dist/Server/Utils/StatusPage/SubscriberTemplateIncidentRecordAccess.js +114 -0
  364. package/build/dist/Server/Utils/StatusPage/SubscriberTemplateIncidentRecordAccess.js.map +1 -0
  365. package/build/dist/Server/Utils/StatusPageResource.js +39 -7
  366. package/build/dist/Server/Utils/StatusPageResource.js.map +1 -1
  367. package/build/dist/Server/Utils/VM/VMAPI.js.map +1 -1
  368. package/build/dist/Server/Utils/VM/VMRunner.js +1 -0
  369. package/build/dist/Server/Utils/VM/VMRunner.js.map +1 -1
  370. package/build/dist/Server/Utils/Workflow/OAuth2TokenClient.js +3 -0
  371. package/build/dist/Server/Utils/Workflow/OAuth2TokenClient.js.map +1 -1
  372. package/build/dist/Types/CustomField/CustomFieldOrder.js +36 -0
  373. package/build/dist/Types/CustomField/CustomFieldOrder.js.map +1 -0
  374. package/build/dist/Types/CustomField/CustomFieldSavedViews.js +135 -0
  375. package/build/dist/Types/CustomField/CustomFieldSavedViews.js.map +1 -0
  376. package/build/dist/Types/CustomField/CustomFieldTemplateMerge.js +27 -0
  377. package/build/dist/Types/CustomField/CustomFieldTemplateMerge.js.map +1 -0
  378. package/build/dist/Types/CustomField/CustomFieldType.js +12 -0
  379. package/build/dist/Types/CustomField/CustomFieldType.js.map +1 -1
  380. package/build/dist/Types/CustomField/CustomFieldValueFormat.js +126 -0
  381. package/build/dist/Types/CustomField/CustomFieldValueFormat.js.map +1 -0
  382. package/build/dist/Types/CustomField/CustomFieldValueValidator.js +280 -0
  383. package/build/dist/Types/CustomField/CustomFieldValueValidator.js.map +1 -0
  384. package/build/dist/Types/CustomField/CustomFieldVariableKey.js +150 -0
  385. package/build/dist/Types/CustomField/CustomFieldVariableKey.js.map +1 -0
  386. package/build/dist/Types/SafeHtml.js +84 -0
  387. package/build/dist/Types/SafeHtml.js.map +1 -0
  388. package/build/dist/Types/StatusPage/IncidentCreatedRenotify.js +117 -0
  389. package/build/dist/Types/StatusPage/IncidentCreatedRenotify.js.map +1 -0
  390. package/build/dist/Types/StatusPage/IncidentCreatedResend.js +86 -0
  391. package/build/dist/Types/StatusPage/IncidentCreatedResend.js.map +1 -0
  392. package/build/dist/Types/StatusPage/IncidentScopeAddedPagesNotification.js +246 -0
  393. package/build/dist/Types/StatusPage/IncidentScopeAddedPagesNotification.js.map +1 -0
  394. package/build/dist/Types/StatusPage/IncidentSubscriberAudience.js +183 -0
  395. package/build/dist/Types/StatusPage/IncidentSubscriberAudience.js.map +1 -0
  396. package/build/dist/Types/StatusPage/StatusPageSubscriberUnsubscribe.js +277 -0
  397. package/build/dist/Types/StatusPage/StatusPageSubscriberUnsubscribe.js.map +1 -0
  398. package/build/dist/Types/StatusPage/SubscriberNotificationInterruption.js +27 -0
  399. package/build/dist/Types/StatusPage/SubscriberNotificationInterruption.js.map +1 -0
  400. package/build/dist/Types/StatusPage/SubscriberNotificationPreview.js +190 -0
  401. package/build/dist/Types/StatusPage/SubscriberNotificationPreview.js.map +1 -0
  402. package/build/dist/Types/StatusPage/SubscriberNotificationResend.js +102 -0
  403. package/build/dist/Types/StatusPage/SubscriberNotificationResend.js.map +1 -0
  404. package/build/dist/Types/StatusPage/SubscriberNotificationTemplateCompiler.js +97 -0
  405. package/build/dist/Types/StatusPage/SubscriberNotificationTemplateCompiler.js.map +1 -0
  406. package/build/dist/Types/StatusPage/SubscriberNotificationTemplatePreview.js +175 -0
  407. package/build/dist/Types/StatusPage/SubscriberNotificationTemplatePreview.js.map +1 -0
  408. package/build/dist/Types/StatusPage/SubscriberNotificationTemplateVariables.js +144 -4
  409. package/build/dist/Types/StatusPage/SubscriberNotificationTemplateVariables.js.map +1 -1
  410. package/build/dist/Types/StatusPage/SubscriberUpdateNotification.js +29 -9
  411. package/build/dist/Types/StatusPage/SubscriberUpdateNotification.js.map +1 -1
  412. package/build/dist/UI/Components/CustomFields/CustomFieldFormFields.js +169 -0
  413. package/build/dist/UI/Components/CustomFields/CustomFieldFormFields.js.map +1 -0
  414. package/build/dist/UI/Components/CustomFields/CustomFieldModelFormFields.js +212 -0
  415. package/build/dist/UI/Components/CustomFields/CustomFieldModelFormFields.js.map +1 -0
  416. package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js +64 -49
  417. package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js.map +1 -1
  418. package/build/dist/UI/Components/Forms/ModelForm.js +39 -5
  419. package/build/dist/UI/Components/Forms/ModelForm.js.map +1 -1
  420. package/build/dist/UI/Components/Forms/Validation.js +1 -1
  421. package/build/dist/UI/Components/Forms/Validation.js.map +1 -1
  422. package/build/dist/UI/Components/ModelFormModal/ModelFormModal.js.map +1 -1
  423. package/build/dist/UI/Components/ModelTable/AnalyticsModelTable.js.map +1 -1
  424. package/build/dist/UI/Components/ModelTable/BaseModelTable.js +2 -2
  425. package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
  426. package/build/dist/UI/Components/ModelTable/CustomFieldColumns.js +13 -1
  427. package/build/dist/UI/Components/ModelTable/CustomFieldColumns.js.map +1 -1
  428. package/build/dist/UI/Components/ModelTable/ModelTable.js +1 -1
  429. package/build/dist/UI/Components/ModelTable/ModelTable.js.map +1 -1
  430. package/build/dist/UI/Utils/SensitiveUrlToken.js +10 -5
  431. package/build/dist/UI/Utils/SensitiveUrlToken.js.map +1 -1
  432. package/build/dist/Utils/API.js +177 -1
  433. package/build/dist/Utils/API.js.map +1 -1
  434. package/build/dist/Utils/Incident/IncidentNoteTemplateVariables.js +155 -0
  435. package/build/dist/Utils/Incident/IncidentNoteTemplateVariables.js.map +1 -0
  436. package/build/dist/Utils/Markdown/MarkdownEscape.js +48 -0
  437. package/build/dist/Utils/Markdown/MarkdownEscape.js.map +1 -1
  438. package/build/dist/Utils/ProbeNetworkFailureUtil.js +460 -0
  439. package/build/dist/Utils/ProbeNetworkFailureUtil.js.map +1 -0
  440. package/package.json +1 -1
@@ -0,0 +1,751 @@
1
+ import IncidentCustomField from "../../../Models/DatabaseModels/IncidentCustomField";
2
+ import TableView from "../../../Models/DatabaseModels/TableView";
3
+ import CustomFieldMappingService from "../../../Server/Services/CustomFieldMappingService";
4
+ import IncidentCustomFieldService from "../../../Server/Services/IncidentCustomFieldService";
5
+ import IncidentService from "../../../Server/Services/IncidentService";
6
+ import IncidentTemplateService from "../../../Server/Services/IncidentTemplateService";
7
+ import TableViewService from "../../../Server/Services/TableViewService";
8
+ import CreateBy from "../../../Server/Types/Database/CreateBy";
9
+ import { OnCreate, OnUpdate } from "../../../Server/Types/Database/Hooks";
10
+ import UpdateBy from "../../../Server/Types/Database/UpdateBy";
11
+ import * as MappingValidator from "../../../Server/Utils/CustomField/CustomFieldMappingValidator";
12
+ import DatabaseCommonInteractionProps from "../../../Types/BaseDatabase/DatabaseCommonInteractionProps";
13
+ import PositiveNumber from "../../../Types/PositiveNumber";
14
+ import CustomFieldType from "../../../Types/CustomField/CustomFieldType";
15
+ import BadDataException from "../../../Types/Exception/BadDataException";
16
+ import { JSONObject } from "../../../Types/JSON";
17
+ import ObjectID from "../../../Types/ObjectID";
18
+ import getJestMockFunction, { MockFunction } from "../../MockType";
19
+ import {
20
+ afterEach,
21
+ beforeEach,
22
+ describe,
23
+ expect,
24
+ jest,
25
+ test,
26
+ } from "@jest/globals";
27
+
28
+ /*
29
+ * IncidentCustomFieldService looks after the two things an incident custom
30
+ * field is known by besides its settings:
31
+ *
32
+ * - its template key (variableKey): made from the name on create, unique
33
+ * in the project, whatever the client sent replaced, and never changed
34
+ * by an update;
35
+ * - its name, which incidents and incident templates store its values
36
+ * under. A rename moves those values - with raw SQL, so no "On Update
37
+ * Incident" workflow runs for any incident - and rewrites the saved
38
+ * views of the incidents list that name the field. A rename onto
39
+ * another field's name, or of several fields at once, is refused.
40
+ *
41
+ * Every read and write is stubbed; the SQL itself runs against Postgres in
42
+ * CustomFieldRenamePostgres.test.ts.
43
+ */
44
+
45
+ const projectId: ObjectID = new ObjectID(
46
+ "11111111-1111-4111-8111-111111111111",
47
+ );
48
+ const fieldId: ObjectID = new ObjectID("22222222-2222-4222-8222-222222222222");
49
+
50
+ const ADMIN_PROPS: DatabaseCommonInteractionProps = {
51
+ tenantId: projectId,
52
+ userId: new ObjectID("33333333-3333-4333-8333-333333333333"),
53
+ };
54
+
55
+ type OnBeforeCreate = (
56
+ createBy: CreateBy<IncidentCustomField>,
57
+ ) => Promise<OnCreate<IncidentCustomField>>;
58
+
59
+ type OnBeforeUpdate = (
60
+ updateBy: UpdateBy<IncidentCustomField>,
61
+ ) => Promise<OnUpdate<IncidentCustomField>>;
62
+
63
+ type OnUpdateSuccess = (
64
+ onUpdate: OnUpdate<IncidentCustomField>,
65
+ updatedItemIds: Array<ObjectID>,
66
+ ) => Promise<OnUpdate<IncidentCustomField>>;
67
+
68
+ const service: {
69
+ onBeforeCreate: OnBeforeCreate;
70
+ onBeforeUpdate: OnBeforeUpdate;
71
+ onUpdateSuccess: OnUpdateSuccess;
72
+ } = IncidentCustomFieldService as unknown as {
73
+ onBeforeCreate: OnBeforeCreate;
74
+ onBeforeUpdate: OnBeforeUpdate;
75
+ onUpdateSuccess: OnUpdateSuccess;
76
+ };
77
+
78
+ function field(data: {
79
+ id?: ObjectID;
80
+ name?: string;
81
+ variableKey?: string;
82
+ project?: ObjectID;
83
+ }): IncidentCustomField {
84
+ const item: IncidentCustomField = new IncidentCustomField();
85
+
86
+ if (data.id) {
87
+ item._id = data.id.toString();
88
+ }
89
+
90
+ if (data.name !== undefined) {
91
+ item.name = data.name;
92
+ }
93
+
94
+ if (data.variableKey !== undefined) {
95
+ item.variableKey = data.variableKey;
96
+ }
97
+
98
+ item.projectId = data.project || projectId;
99
+
100
+ return item;
101
+ }
102
+
103
+ interface FakeRepository {
104
+ query: MockFunction;
105
+ repository: unknown;
106
+ }
107
+
108
+ /*
109
+ * The tables a rename writes to, by name. The statements of one rename run
110
+ * in one transaction, on whichever table's manager opened it; its manager
111
+ * hands each statement to the fake of the table it names.
112
+ */
113
+ const fakeTables: Record<string, FakeRepository> = {};
114
+ let transactions: MockFunction;
115
+
116
+ function fakeTableFor(sql: string): FakeRepository {
117
+ const table: string | undefined = Object.keys(fakeTables).find(
118
+ (name: string): boolean => {
119
+ return sql.includes(`UPDATE "${name}"`);
120
+ },
121
+ );
122
+
123
+ if (!table) {
124
+ throw new Error(`No fake table for: ${sql}`);
125
+ }
126
+
127
+ return fakeTables[table]!;
128
+ }
129
+
130
+ const transactionManager: { query: MockFunction } = {
131
+ query: getJestMockFunction(),
132
+ };
133
+
134
+ // Enough of a TypeORM repository for the raw rename statement.
135
+ function fakeRepository(tableName: string, moved: number): FakeRepository {
136
+ const query: MockFunction = getJestMockFunction();
137
+ query.mockResolvedValue([{ moved: moved, cleared: 0 }] as never);
138
+
139
+ const fake: FakeRepository = {
140
+ query: query,
141
+ repository: {
142
+ metadata: {
143
+ tableName: tableName,
144
+ name: tableName,
145
+ findColumnWithPropertyName: (propertyName: string) => {
146
+ return { databaseName: propertyName };
147
+ },
148
+ },
149
+ manager: {
150
+ query: query,
151
+ transaction: transactions,
152
+ },
153
+ },
154
+ };
155
+
156
+ fakeTables[tableName] = fake;
157
+ return fake;
158
+ }
159
+
160
+ let findBy: MockFunction;
161
+ let countBy: MockFunction;
162
+
163
+ beforeEach(() => {
164
+ findBy = getJestMockFunction();
165
+ findBy.mockResolvedValue([] as never);
166
+ jest
167
+ .spyOn(IncidentCustomFieldService, "findBy")
168
+ .mockImplementation(findBy as never);
169
+
170
+ countBy = getJestMockFunction();
171
+ countBy.mockResolvedValue(new PositiveNumber(0) as never);
172
+ jest
173
+ .spyOn(IncidentCustomFieldService, "countBy")
174
+ .mockImplementation(countBy as never);
175
+
176
+ jest
177
+ .spyOn(MappingValidator, "validateCustomFieldMappingOnCreate")
178
+ .mockResolvedValue(undefined as never);
179
+ jest
180
+ .spyOn(MappingValidator, "validateCustomFieldMappingOnUpdate")
181
+ .mockResolvedValue(undefined as never);
182
+ jest
183
+ .spyOn(CustomFieldMappingService, "backfillProject")
184
+ .mockResolvedValue(undefined as never);
185
+ });
186
+
187
+ afterEach(() => {
188
+ jest.restoreAllMocks();
189
+ });
190
+
191
+ describe("IncidentCustomFieldService.onBeforeCreate: the template key", () => {
192
+ async function create(
193
+ data: Partial<IncidentCustomField>,
194
+ props: DatabaseCommonInteractionProps = ADMIN_PROPS,
195
+ ): Promise<IncidentCustomField> {
196
+ const item: IncidentCustomField = new IncidentCustomField();
197
+ Object.assign(item, data);
198
+
199
+ const onCreate: OnCreate<IncidentCustomField> =
200
+ await service.onBeforeCreate({ data: item, props });
201
+
202
+ return onCreate.createBy.data;
203
+ }
204
+
205
+ test("is made from the name", async () => {
206
+ const created: IncidentCustomField = await create({
207
+ name: "Expected Resolution",
208
+ customFieldType: CustomFieldType.DateTime,
209
+ });
210
+
211
+ expect(created.variableKey).toBe("expected_resolution");
212
+ });
213
+
214
+ test("is numbered past the keys the project's other fields hold", async () => {
215
+ findBy.mockResolvedValue([
216
+ field({
217
+ name: "Expected Resolution",
218
+ variableKey: "expected_resolution",
219
+ }),
220
+ field({
221
+ name: "expected-resolution",
222
+ variableKey: "expected_resolution_2",
223
+ }),
224
+ field({ name: "Legacy" }),
225
+ ] as never);
226
+
227
+ const created: IncidentCustomField = await create({
228
+ name: "EXPECTED RESOLUTION!",
229
+ });
230
+
231
+ expect(created.variableKey).toBe("expected_resolution_3");
232
+
233
+ const read: JSONObject = findBy.mock.calls[0]![0] as JSONObject;
234
+ expect(read["query"]).toEqual({ projectId: projectId });
235
+ expect(read["select"]).toEqual({ variableKey: true });
236
+ expect((read["props"] as JSONObject)["isRoot"]).toBe(true);
237
+ });
238
+
239
+ test("replaces whatever key the client sent", async () => {
240
+ const created: IncidentCustomField = await create({
241
+ name: "Impact",
242
+ variableKey: "Not A Valid-Key {{x}}",
243
+ });
244
+
245
+ expect(created.variableKey).toBe("impact");
246
+ });
247
+
248
+ test("a non-ASCII name gets the fallback key", async () => {
249
+ findBy.mockResolvedValue([field({ variableKey: "field" })] as never);
250
+
251
+ const created: IncidentCustomField = await create({ name: "影响" });
252
+
253
+ expect(created.variableKey).toBe("field_2");
254
+ });
255
+
256
+ test("uses the row's project for a root create without a tenant", async () => {
257
+ const otherProject: ObjectID = new ObjectID(
258
+ "44444444-4444-4444-8444-444444444444",
259
+ );
260
+
261
+ await create({ name: "Impact", projectId: otherProject }, { isRoot: true });
262
+
263
+ expect((findBy.mock.calls[0]![0] as JSONObject)["query"]).toEqual({
264
+ projectId: otherProject,
265
+ });
266
+ });
267
+ });
268
+
269
+ describe("IncidentCustomFieldService.onBeforeUpdate", () => {
270
+ function update(
271
+ data: JSONObject,
272
+ props: DatabaseCommonInteractionProps = ADMIN_PROPS,
273
+ query: JSONObject = { _id: fieldId.toString() },
274
+ ): UpdateBy<IncidentCustomField> {
275
+ return {
276
+ query: query as never,
277
+ data: data as never,
278
+ props: props,
279
+ limit: 1,
280
+ skip: 0,
281
+ };
282
+ }
283
+
284
+ test("drops the template key: it never changes after create", async () => {
285
+ const onUpdate: OnUpdate<IncidentCustomField> =
286
+ await service.onBeforeUpdate(
287
+ update({ description: "Now documented", variableKey: "hijacked" }),
288
+ );
289
+
290
+ expect(onUpdate.updateBy.data).toEqual({ description: "Now documented" });
291
+ expect(onUpdate.carryForward).toBeNull();
292
+ // No name in the write: nothing about renaming is read.
293
+ expect(findBy).not.toHaveBeenCalled();
294
+ });
295
+
296
+ test("drops the template key for root writes too", async () => {
297
+ const onUpdate: OnUpdate<IncidentCustomField> =
298
+ await service.onBeforeUpdate(
299
+ update({ variableKey: "hijacked" }, { isRoot: true }),
300
+ );
301
+
302
+ expect(onUpdate.updateBy.data).toEqual({});
303
+ });
304
+
305
+ test("records the old name of a field being renamed", async () => {
306
+ findBy.mockResolvedValue([field({ id: fieldId, name: "Impact" })] as never);
307
+
308
+ const onUpdate: OnUpdate<IncidentCustomField> =
309
+ await service.onBeforeUpdate(update({ name: "Business Impact" }));
310
+
311
+ expect(onUpdate.carryForward).toEqual({
312
+ [fieldId.toString()]: { oldName: "Impact", projectId: projectId },
313
+ });
314
+
315
+ const read: JSONObject = findBy.mock.calls[0]![0] as JSONObject;
316
+ // Read as root, but only within the caller's project.
317
+ expect(read["query"]).toEqual({
318
+ _id: fieldId.toString(),
319
+ projectId: projectId,
320
+ });
321
+ expect((read["props"] as JSONObject)["isRoot"]).toBe(true);
322
+ });
323
+
324
+ test("a save that keeps the name is not a rename", async () => {
325
+ findBy.mockResolvedValue([field({ id: fieldId, name: "Impact" })] as never);
326
+
327
+ const onUpdate: OnUpdate<IncidentCustomField> =
328
+ await service.onBeforeUpdate(
329
+ update({ name: "Impact", description: "edited" }),
330
+ );
331
+
332
+ expect(onUpdate.carryForward).toBeNull();
333
+ expect(countBy).not.toHaveBeenCalled();
334
+ });
335
+
336
+ test("refuses a rename onto another field's name", async () => {
337
+ findBy.mockResolvedValue([field({ id: fieldId, name: "Impact" })] as never);
338
+ countBy.mockResolvedValue(new PositiveNumber(1) as never);
339
+
340
+ await expect(
341
+ service.onBeforeUpdate(update({ name: "severity" })),
342
+ ).rejects.toThrow(BadDataException);
343
+
344
+ await expect(
345
+ service.onBeforeUpdate(update({ name: "severity" })),
346
+ ).rejects.toThrow(
347
+ "Another incident custom field already has this name. Choose a different name.",
348
+ );
349
+
350
+ const clashQuery: JSONObject = (countBy.mock.calls[0]![0] as JSONObject)[
351
+ "query"
352
+ ] as JSONObject;
353
+ expect(clashQuery["projectId"]).toBe(projectId);
354
+ expect(clashQuery["name"]).toBeDefined();
355
+ expect(clashQuery["_id"]).toBeDefined();
356
+ });
357
+
358
+ test("allows changing only the case of a name", async () => {
359
+ findBy.mockResolvedValue([field({ id: fieldId, name: "impact" })] as never);
360
+
361
+ const onUpdate: OnUpdate<IncidentCustomField> =
362
+ await service.onBeforeUpdate(update({ name: "Impact" }));
363
+
364
+ expect(onUpdate.carryForward).toEqual({
365
+ [fieldId.toString()]: { oldName: "impact", projectId: projectId },
366
+ });
367
+ });
368
+
369
+ test("refuses renaming several fields in one write", async () => {
370
+ findBy.mockResolvedValue([
371
+ field({ id: fieldId, name: "Impact" }),
372
+ field({
373
+ id: new ObjectID("55555555-5555-4555-8555-555555555555"),
374
+ name: "Region",
375
+ }),
376
+ ] as never);
377
+
378
+ await expect(
379
+ service.onBeforeUpdate(
380
+ update({ name: "Merged" }, ADMIN_PROPS, {
381
+ projectId: projectId.toString(),
382
+ }),
383
+ ),
384
+ ).rejects.toThrow(
385
+ "Incident custom fields can only be renamed one at a time.",
386
+ );
387
+ });
388
+
389
+ test("a rename that matches no field is nothing to prepare", async () => {
390
+ findBy.mockResolvedValue([] as never);
391
+
392
+ const onUpdate: OnUpdate<IncidentCustomField> =
393
+ await service.onBeforeUpdate(update({ name: "Anything" }));
394
+
395
+ expect(onUpdate.carryForward).toBeNull();
396
+ });
397
+ });
398
+
399
+ describe("IncidentCustomFieldService.onUpdateSuccess: moving a renamed field's values", () => {
400
+ let incidentTable: FakeRepository;
401
+ let templateTable: FakeRepository;
402
+ let tableViewFindBy: MockFunction;
403
+ let tableViewWrite: MockFunction;
404
+ let workflowTriggers: Array<MockFunction>;
405
+ let hookedWrites: Array<MockFunction>;
406
+
407
+ let restoreName: MockFunction;
408
+
409
+ beforeEach(() => {
410
+ transactionManager.query = getJestMockFunction();
411
+ transactionManager.query.mockImplementation(((
412
+ sql: string,
413
+ parameters: Array<string>,
414
+ ) => {
415
+ return fakeTableFor(sql).query(sql, parameters);
416
+ }) as never);
417
+
418
+ transactions = getJestMockFunction();
419
+ transactions.mockImplementation((async (
420
+ run: (manager: unknown) => Promise<unknown>,
421
+ ) => {
422
+ return await run(transactionManager);
423
+ }) as never);
424
+
425
+ incidentTable = fakeRepository("Incident", 12);
426
+ templateTable = fakeRepository("IncidentTemplate", 2);
427
+
428
+ restoreName = getJestMockFunction();
429
+ restoreName.mockResolvedValue(undefined as never);
430
+ jest
431
+ .spyOn(IncidentCustomFieldService, "updateColumnsByIdWithoutHooks")
432
+ .mockImplementation(restoreName as never);
433
+
434
+ jest
435
+ .spyOn(IncidentService, "getRepository")
436
+ .mockReturnValue(incidentTable.repository as never);
437
+ jest
438
+ .spyOn(IncidentTemplateService, "getRepository")
439
+ .mockReturnValue(templateTable.repository as never);
440
+
441
+ tableViewFindBy = getJestMockFunction();
442
+ tableViewFindBy.mockResolvedValue([] as never);
443
+ jest
444
+ .spyOn(TableViewService, "findBy")
445
+ .mockImplementation(tableViewFindBy as never);
446
+
447
+ tableViewWrite = getJestMockFunction();
448
+ tableViewWrite.mockResolvedValue(undefined as never);
449
+ jest
450
+ .spyOn(TableViewService, "updateColumnsByIdWithoutHooks")
451
+ .mockImplementation(tableViewWrite as never);
452
+
453
+ // The workflow trigger path of every table a rename writes to.
454
+ workflowTriggers = [
455
+ IncidentService,
456
+ IncidentTemplateService,
457
+ TableViewService,
458
+ ].map((target: { onTriggerWorkflow: unknown }) => {
459
+ const trigger: MockFunction = getJestMockFunction();
460
+ trigger.mockResolvedValue(undefined as never);
461
+ jest
462
+ .spyOn(target as never, "onTriggerWorkflow" as never)
463
+ .mockImplementation(trigger as never);
464
+ return trigger;
465
+ });
466
+
467
+ // And every hooked write that would reach that path.
468
+ hookedWrites = [];
469
+ for (const target of [IncidentService, IncidentTemplateService]) {
470
+ for (const method of ["updateBy", "updateOneBy", "updateOneById"]) {
471
+ const write: MockFunction = getJestMockFunction();
472
+ write.mockResolvedValue(undefined as never);
473
+ jest
474
+ .spyOn(target as never, method as never)
475
+ .mockImplementation(write as never);
476
+ hookedWrites.push(write);
477
+ }
478
+ }
479
+ });
480
+
481
+ function onUpdate(
482
+ carryForward: unknown,
483
+ props: DatabaseCommonInteractionProps = ADMIN_PROPS,
484
+ ): OnUpdate<IncidentCustomField> {
485
+ return {
486
+ updateBy: {
487
+ query: { _id: fieldId.toString() } as never,
488
+ data: { name: "Business Impact" } as never,
489
+ props: props,
490
+ limit: 1,
491
+ skip: 0,
492
+ },
493
+ carryForward: carryForward,
494
+ };
495
+ }
496
+
497
+ const RENAME: JSONObject = {
498
+ [fieldId.toString()]: { oldName: "Impact", projectId: projectId },
499
+ } as unknown as JSONObject;
500
+
501
+ test("moves the values in Incident and IncidentTemplate with one raw statement each", async () => {
502
+ // The name as saved, read back from the store.
503
+ findBy.mockResolvedValue([
504
+ field({ id: fieldId, name: "Business Impact" }),
505
+ ] as never);
506
+
507
+ await service.onUpdateSuccess(onUpdate(RENAME), [fieldId]);
508
+
509
+ for (const [table, fake] of [
510
+ ["Incident", incidentTable],
511
+ ["IncidentTemplate", templateTable],
512
+ ] as Array<[string, FakeRepository]>) {
513
+ expect(fake.query).toHaveBeenCalledTimes(1);
514
+
515
+ const [sql, parameters] = fake.query.mock.calls[0] as [
516
+ string,
517
+ Array<string>,
518
+ ];
519
+
520
+ expect(sql).toContain(`UPDATE "${table}"`);
521
+ expect(sql).toContain(
522
+ `THEN ("customFields" - $2::text) || jsonb_build_object($3::text, "customFields" -> $2::text)`,
523
+ );
524
+ expect(sql).toContain(`WHERE "projectId" = $1`);
525
+ expect(sql).toContain(`jsonb_typeof("customFields") = 'object'`);
526
+ expect(sql).toContain(`jsonb_exists("customFields", $2::text)`);
527
+ // No version bump, no updatedAt: a rename is not an edit of the record.
528
+ expect(sql).not.toContain("version");
529
+ expect(sql).not.toContain("updatedAt");
530
+ expect(parameters).toEqual([
531
+ projectId.toString(),
532
+ "Impact",
533
+ "Business Impact",
534
+ ]);
535
+ }
536
+ });
537
+
538
+ /*
539
+ * Deleting a field leaves its values behind. A record that holds a value
540
+ * under the new name but none under the old one holds a deleted field's
541
+ * answer, which the renamed field must not show or send to subscribers.
542
+ */
543
+ test("clears what a record holds under the new name when it holds nothing under the old", async () => {
544
+ findBy.mockResolvedValue([
545
+ field({ id: fieldId, name: "Business Impact" }),
546
+ ] as never);
547
+
548
+ await service.onUpdateSuccess(onUpdate(RENAME), [fieldId]);
549
+
550
+ for (const fake of [incidentTable, templateTable]) {
551
+ const [sql] = fake.query.mock.calls[0] as [string, Array<string>];
552
+
553
+ expect(sql).toContain(`WHEN jsonb_exists("customFields", $2::text)`);
554
+ expect(sql).toContain(`ELSE "customFields" - $3::text`);
555
+ expect(sql).toMatch(
556
+ /jsonb_exists\("customFields", \$2::text\)\s+OR jsonb_exists\("customFields", \$3::text\)/,
557
+ );
558
+ }
559
+ });
560
+
561
+ test("moves every table's values in one transaction", async () => {
562
+ findBy.mockResolvedValue([
563
+ field({ id: fieldId, name: "Business Impact" }),
564
+ ] as never);
565
+
566
+ await service.onUpdateSuccess(onUpdate(RENAME), [fieldId]);
567
+
568
+ expect(transactions).toHaveBeenCalledTimes(1);
569
+ expect(transactionManager.query).toHaveBeenCalledTimes(2);
570
+ expect(restoreName).not.toHaveBeenCalled();
571
+ });
572
+
573
+ test("starts no workflow and makes no hooked write", async () => {
574
+ findBy.mockResolvedValue([
575
+ field({ id: fieldId, name: "Business Impact" }),
576
+ ] as never);
577
+
578
+ await service.onUpdateSuccess(onUpdate(RENAME), [fieldId]);
579
+
580
+ for (const trigger of workflowTriggers) {
581
+ expect(trigger).not.toHaveBeenCalled();
582
+ }
583
+
584
+ for (const write of hookedWrites) {
585
+ expect(write).not.toHaveBeenCalled();
586
+ }
587
+ });
588
+
589
+ test("rewrites the incidents list's saved views that name the field", async () => {
590
+ findBy.mockResolvedValue([
591
+ field({ id: fieldId, name: "Business Impact" }),
592
+ ] as never);
593
+
594
+ const namesTheField: TableView = new TableView();
595
+ namesTheField._id = "66666666-6666-4666-8666-666666666666";
596
+ namesTheField.columns = {
597
+ order: ["title", "customFields.Impact"],
598
+ hidden: [],
599
+ };
600
+ namesTheField.facets = {
601
+ facetSelections: { "customField:Impact": ["High"] },
602
+ facetOperators: {},
603
+ };
604
+ namesTheField.query = {} as never;
605
+
606
+ const doesNot: TableView = new TableView();
607
+ doesNot._id = "77777777-7777-4777-8777-777777777777";
608
+ doesNot.columns = { order: ["title"], hidden: [] };
609
+
610
+ tableViewFindBy.mockResolvedValue([namesTheField, doesNot] as never);
611
+
612
+ await service.onUpdateSuccess(onUpdate(RENAME), [fieldId]);
613
+
614
+ const read: JSONObject = tableViewFindBy.mock.calls[0]![0] as JSONObject;
615
+ expect((read["query"] as JSONObject)["projectId"]).toBe(projectId);
616
+ expect((read["props"] as JSONObject)["isRoot"]).toBe(true);
617
+
618
+ expect(tableViewWrite).toHaveBeenCalledTimes(1);
619
+
620
+ const write: JSONObject = tableViewWrite.mock.calls[0]![0] as JSONObject;
621
+ expect((write["id"] as ObjectID).toString()).toBe(
622
+ "66666666-6666-4666-8666-666666666666",
623
+ );
624
+ expect(write["data"]).toEqual({
625
+ columns: { order: ["title", "customFields.Business Impact"], hidden: [] },
626
+ facets: {
627
+ facetSelections: { "customField:Business Impact": ["High"] },
628
+ facetOperators: {},
629
+ },
630
+ });
631
+ // Compare-and-set on what was read, without touching updatedAt.
632
+ expect(write["expectedData"]).toEqual({
633
+ columns: namesTheField.columns,
634
+ facets: namesTheField.facets,
635
+ });
636
+ expect(write["skipUpdateDateColumn"]).toBe(true);
637
+ });
638
+
639
+ test("moves the values before the mapping backfill runs", async () => {
640
+ findBy.mockResolvedValue([
641
+ field({ id: fieldId, name: "Business Impact" }),
642
+ ] as never);
643
+
644
+ const order: Array<string> = [];
645
+
646
+ incidentTable.query.mockImplementation((() => {
647
+ order.push("move");
648
+ return Promise.resolve([{ moved: 1, cleared: 0 }]);
649
+ }) as never);
650
+
651
+ (
652
+ CustomFieldMappingService.backfillProject as unknown as MockFunction
653
+ ).mockImplementation((() => {
654
+ order.push("backfill");
655
+ return Promise.resolve();
656
+ }) as never);
657
+
658
+ await service.onUpdateSuccess(onUpdate(RENAME), [fieldId]);
659
+
660
+ expect(order).toEqual(["move", "backfill"]);
661
+ });
662
+
663
+ test("does nothing for an update that renamed nothing", async () => {
664
+ await service.onUpdateSuccess(onUpdate(null), [fieldId]);
665
+
666
+ expect(incidentTable.query).not.toHaveBeenCalled();
667
+ expect(templateTable.query).not.toHaveBeenCalled();
668
+ expect(tableViewFindBy).not.toHaveBeenCalled();
669
+ });
670
+
671
+ test("does nothing when the rename did not write the field", async () => {
672
+ await service.onUpdateSuccess(onUpdate(RENAME), []);
673
+
674
+ expect(incidentTable.query).not.toHaveBeenCalled();
675
+ });
676
+
677
+ test("does nothing when the stored name is still the old one", async () => {
678
+ findBy.mockResolvedValue([field({ id: fieldId, name: "Impact" })] as never);
679
+
680
+ await service.onUpdateSuccess(onUpdate(RENAME), [fieldId]);
681
+
682
+ expect(incidentTable.query).not.toHaveBeenCalled();
683
+ });
684
+
685
+ test("a failed move is reported, not swallowed", async () => {
686
+ findBy.mockResolvedValue([
687
+ field({ id: fieldId, name: "Business Impact" }),
688
+ ] as never);
689
+ incidentTable.query.mockRejectedValue(
690
+ new Error("connection lost") as never,
691
+ );
692
+
693
+ await expect(
694
+ service.onUpdateSuccess(onUpdate(RENAME), [fieldId]),
695
+ ).rejects.toThrow("connection lost");
696
+ });
697
+
698
+ /*
699
+ * No value moved, so the field takes its old name back: left renamed it
700
+ * would show none of its values, and renaming it back would clear them.
701
+ */
702
+ test("a failed move gives the field its old name back, if nobody renamed it since", async () => {
703
+ findBy.mockResolvedValue([
704
+ field({ id: fieldId, name: "Business Impact" }),
705
+ ] as never);
706
+ templateTable.query.mockRejectedValue(
707
+ new Error("connection lost") as never,
708
+ );
709
+
710
+ await expect(
711
+ service.onUpdateSuccess(onUpdate(RENAME), [fieldId]),
712
+ ).rejects.toThrow("connection lost");
713
+
714
+ expect(restoreName).toHaveBeenCalledTimes(1);
715
+
716
+ const restore: JSONObject = restoreName.mock.calls[0]![0] as JSONObject;
717
+ expect((restore["id"] as ObjectID).toString()).toBe(fieldId.toString());
718
+ expect(restore["data"]).toEqual({ name: "Impact" });
719
+ expect(restore["expectedData"]).toEqual({ name: "Business Impact" });
720
+
721
+ // Nothing after the values ran: the saved views keep the old name too.
722
+ expect(tableViewFindBy).not.toHaveBeenCalled();
723
+ });
724
+
725
+ test("a failure to restore the name still reports the move's failure", async () => {
726
+ findBy.mockResolvedValue([
727
+ field({ id: fieldId, name: "Business Impact" }),
728
+ ] as never);
729
+ incidentTable.query.mockRejectedValue(
730
+ new Error("connection lost") as never,
731
+ );
732
+ restoreName.mockRejectedValue(new Error("still down") as never);
733
+
734
+ await expect(
735
+ service.onUpdateSuccess(onUpdate(RENAME), [fieldId]),
736
+ ).rejects.toThrow("connection lost");
737
+ });
738
+
739
+ test("a saved view that could not be rewritten keeps the rename: the values moved", async () => {
740
+ findBy.mockResolvedValue([
741
+ field({ id: fieldId, name: "Business Impact" }),
742
+ ] as never);
743
+ tableViewFindBy.mockRejectedValue(new Error("views down") as never);
744
+
745
+ await expect(
746
+ service.onUpdateSuccess(onUpdate(RENAME), [fieldId]),
747
+ ).rejects.toThrow("views down");
748
+
749
+ expect(restoreName).not.toHaveBeenCalled();
750
+ });
751
+ });