@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,994 @@
1
+ import IncidentEpisodePublicNoteService from "../../../Server/Services/IncidentEpisodePublicNoteService";
2
+ import IncidentPublicNoteService from "../../../Server/Services/IncidentPublicNoteService";
3
+ import ScheduledMaintenancePublicNoteService from "../../../Server/Services/ScheduledMaintenancePublicNoteService";
4
+ import DatabaseService from "../../../Server/Services/DatabaseService";
5
+ import AuditLogService from "../../../Server/Services/AuditLogService";
6
+ import { OnUpdate } from "../../../Server/Types/Database/Hooks";
7
+ import BasePermission from "../../../Server/Types/Database/Permissions/BasePermission";
8
+ import UpdateBy from "../../../Server/Types/Database/UpdateBy";
9
+ import SubscriberNotificationResendAccess from "../../../Server/Utils/StatusPage/SubscriberNotificationResendAccess";
10
+ import BaseModel from "../../../Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel";
11
+ import Incident from "../../../Models/DatabaseModels/Incident";
12
+ import IncidentEpisodePublicNote from "../../../Models/DatabaseModels/IncidentEpisodePublicNote";
13
+ import IncidentPublicNote from "../../../Models/DatabaseModels/IncidentPublicNote";
14
+ import ScheduledMaintenancePublicNote from "../../../Models/DatabaseModels/ScheduledMaintenancePublicNote";
15
+ import DatabaseCommonInteractionProps from "../../../Types/BaseDatabase/DatabaseCommonInteractionProps";
16
+ import BadDataException from "../../../Types/Exception/BadDataException";
17
+ import NotAuthorizedException from "../../../Types/Exception/NotAuthorizedException";
18
+ import { JSONObject } from "../../../Types/JSON";
19
+ import ObjectID from "../../../Types/ObjectID";
20
+ import Permission, {
21
+ UserTenantAccessPermission,
22
+ } from "../../../Types/Permission";
23
+ import StatusPageSubscriberNotificationStatus from "../../../Types/StatusPage/StatusPageSubscriberNotificationStatus";
24
+ import SubscriberNotificationResend from "../../../Types/StatusPage/SubscriberNotificationResend";
25
+ import SubscriberUpdateNotification from "../../../Types/StatusPage/SubscriberUpdateNotification";
26
+ import getJestMockFunction, { MockFunction } from "../../MockType";
27
+ import {
28
+ afterEach,
29
+ beforeEach,
30
+ describe,
31
+ expect,
32
+ jest,
33
+ test,
34
+ } from "@jest/globals";
35
+
36
+ /*
37
+ * Sending a public note's 'posted' notification again - Retry after a
38
+ * failure, and Resend after a success on the incident's notes - is an update
39
+ * that writes Pending back into subscriberNotificationStatusOnNoteCreated.
40
+ * These tests pin the server half, for the incident, incident episode and
41
+ * scheduled maintenance public notes that share the dashboard's notes feed:
42
+ *
43
+ * - it needs the permission to post a note that notifies subscribers as
44
+ * well as the note's edit permission, so a role that could not post the
45
+ * note cannot send it again, and a role that can only read notes cannot
46
+ * either;
47
+ * - permissions are checked before the note is read, so a refused caller
48
+ * never learns what state its notification is in;
49
+ * - a note posted without notifying subscribers (which the job never sends,
50
+ * so it would sit in Pending forever) and a notification being sent right
51
+ * now are refused, with the reason;
52
+ * - the notes are read with the caller's own permissions and the update's
53
+ * own query;
54
+ * - root (the workers) is never checked, and nothing else is looked at.
55
+ */
56
+
57
+ interface ServiceCase {
58
+ name: string;
59
+ service: DatabaseService<BaseModel>;
60
+ modelType: { new (): BaseModel };
61
+ // A role that may post and edit the note.
62
+ memberRole: Permission;
63
+ // A role that may only read notes.
64
+ viewerRole: Permission;
65
+ createPermission: Permission;
66
+ editPermission: Permission;
67
+ readPermission: Permission;
68
+ }
69
+
70
+ const SERVICE_CASES: Array<ServiceCase> = [
71
+ {
72
+ name: "IncidentPublicNote",
73
+ service: IncidentPublicNoteService as unknown as DatabaseService<BaseModel>,
74
+ modelType: IncidentPublicNote,
75
+ memberRole: Permission.IncidentMember,
76
+ viewerRole: Permission.IncidentViewer,
77
+ createPermission: Permission.CreateIncidentPublicNote,
78
+ editPermission: Permission.EditIncidentPublicNote,
79
+ readPermission: Permission.ReadIncidentPublicNote,
80
+ },
81
+ {
82
+ name: "IncidentEpisodePublicNote",
83
+ service:
84
+ IncidentEpisodePublicNoteService as unknown as DatabaseService<BaseModel>,
85
+ modelType: IncidentEpisodePublicNote,
86
+ memberRole: Permission.IncidentMember,
87
+ viewerRole: Permission.IncidentViewer,
88
+ createPermission: Permission.CreateIncidentEpisodePublicNote,
89
+ editPermission: Permission.EditIncidentEpisodePublicNote,
90
+ readPermission: Permission.ReadIncidentEpisodePublicNote,
91
+ },
92
+ {
93
+ name: "ScheduledMaintenancePublicNote",
94
+ service:
95
+ ScheduledMaintenancePublicNoteService as unknown as DatabaseService<BaseModel>,
96
+ modelType: ScheduledMaintenancePublicNote,
97
+ memberRole: Permission.ScheduledMaintenanceMember,
98
+ viewerRole: Permission.ScheduledMaintenanceViewer,
99
+ createPermission: Permission.CreateScheduledMaintenancePublicNote,
100
+ editPermission: Permission.EditScheduledMaintenancePublicNote,
101
+ readPermission: Permission.ReadScheduledMaintenancePublicNote,
102
+ },
103
+ ];
104
+
105
+ const PROJECT_ID: ObjectID = new ObjectID(
106
+ "88888888-8888-4888-8888-888888888888",
107
+ );
108
+ const USER_ID: ObjectID = new ObjectID("99999999-9999-4999-8999-999999999999");
109
+ const NOTE_ID: string = "77777777-7777-4777-8777-777777777777";
110
+ const OTHER_NOTE_ID: string = "77777777-7777-4777-8777-777777777778";
111
+
112
+ function makeProps(
113
+ permissions: Array<Permission>,
114
+ ): DatabaseCommonInteractionProps {
115
+ const tenantPermission: UserTenantAccessPermission = {
116
+ projectId: PROJECT_ID,
117
+ _type: "UserTenantAccessPermission",
118
+ permissions: permissions.map((permission: Permission) => {
119
+ return {
120
+ _type: "UserPermission",
121
+ permission: permission,
122
+ labelIds: [],
123
+ isBlockPermission: false,
124
+ };
125
+ }),
126
+ };
127
+
128
+ return {
129
+ userId: USER_ID,
130
+ tenantId: PROJECT_ID,
131
+ userTenantAccessPermission: {
132
+ [PROJECT_ID.toString()]: tenantPermission,
133
+ },
134
+ };
135
+ }
136
+
137
+ function storedNote(
138
+ serviceCase: ServiceCase,
139
+ overrides: JSONObject = {},
140
+ ): BaseModel {
141
+ const note: BaseModel = new serviceCase.modelType();
142
+ note._id = NOTE_ID;
143
+ Object.assign(note, {
144
+ subscriberNotificationStatusOnNoteCreated:
145
+ StatusPageSubscriberNotificationStatus.Success,
146
+ shouldStatusPageSubscribersBeNotifiedOnNoteCreated: true,
147
+ ...overrides,
148
+ });
149
+ return note;
150
+ }
151
+
152
+ const RESEND: JSONObject = {
153
+ subscriberNotificationStatusOnNoteCreated:
154
+ StatusPageSubscriberNotificationStatus.Pending,
155
+ subscriberNotificationStatusMessage: null,
156
+ };
157
+
158
+ let storedNotes: Array<BaseModel> = [];
159
+ let noteFindBy: MockFunction;
160
+
161
+ function update(data: {
162
+ props: DatabaseCommonInteractionProps;
163
+ data?: JSONObject;
164
+ miscDataProps?: JSONObject;
165
+ query?: JSONObject;
166
+ }): UpdateBy<BaseModel> {
167
+ return {
168
+ query: (data.query || { _id: NOTE_ID }) as never,
169
+ data: (data.data || { ...RESEND }) as never,
170
+ props: data.props,
171
+ miscDataProps: data.miscDataProps,
172
+ limit: 1,
173
+ skip: 0,
174
+ };
175
+ }
176
+
177
+ async function runBeforeUpdate(
178
+ serviceCase: ServiceCase,
179
+ updateBy: UpdateBy<BaseModel>,
180
+ ): Promise<OnUpdate<BaseModel>> {
181
+ return await (
182
+ serviceCase.service as unknown as {
183
+ onBeforeUpdate: (
184
+ updateBy: UpdateBy<BaseModel>,
185
+ ) => Promise<OnUpdate<BaseModel>>;
186
+ }
187
+ ).onBeforeUpdate(updateBy);
188
+ }
189
+
190
+ describe.each(SERVICE_CASES)(
191
+ "$name: sending the posted notification again",
192
+ (serviceCase: ServiceCase) => {
193
+ beforeEach(() => {
194
+ storedNotes = [storedNote(serviceCase)];
195
+
196
+ noteFindBy = getJestMockFunction();
197
+ noteFindBy.mockImplementation(() => {
198
+ return Promise.resolve(storedNotes);
199
+ });
200
+ jest
201
+ .spyOn(serviceCase.service, "findBy")
202
+ .mockImplementation(noteFindBy as never);
203
+ });
204
+
205
+ afterEach(() => {
206
+ jest.restoreAllMocks();
207
+ });
208
+
209
+ test.each([
210
+ StatusPageSubscriberNotificationStatus.Success,
211
+ StatusPageSubscriberNotificationStatus.Failed,
212
+ ])(
213
+ "a member may send a %s notification again, and the update goes through as written",
214
+ async (status: StatusPageSubscriberNotificationStatus) => {
215
+ storedNotes = [
216
+ storedNote(serviceCase, {
217
+ subscriberNotificationStatusOnNoteCreated: status,
218
+ }),
219
+ ];
220
+
221
+ const result: OnUpdate<BaseModel> = await runBeforeUpdate(
222
+ serviceCase,
223
+ update({ props: makeProps([serviceCase.memberRole]) }),
224
+ );
225
+
226
+ expect(result.updateBy.data).toEqual(RESEND);
227
+ },
228
+ );
229
+
230
+ test("reads the notes with the caller's own permissions and the update's own query", async () => {
231
+ const props: DatabaseCommonInteractionProps = makeProps([
232
+ serviceCase.memberRole,
233
+ ]);
234
+
235
+ await runBeforeUpdate(serviceCase, update({ props }));
236
+
237
+ expect(noteFindBy).toHaveBeenCalledTimes(1);
238
+
239
+ const findBy: {
240
+ query: JSONObject;
241
+ select: JSONObject;
242
+ props: DatabaseCommonInteractionProps;
243
+ } = noteFindBy.mock.calls[0]![0] as {
244
+ query: JSONObject;
245
+ select: JSONObject;
246
+ props: DatabaseCommonInteractionProps;
247
+ };
248
+
249
+ expect(findBy.query).toEqual({ _id: NOTE_ID });
250
+ expect(findBy.props).toBe(props);
251
+ expect(findBy.select).toEqual({
252
+ _id: true,
253
+ subscriberNotificationStatusOnNoteCreated: true,
254
+ shouldStatusPageSubscribersBeNotifiedOnNoteCreated: true,
255
+ });
256
+ });
257
+
258
+ test("a note posted without notifying subscribers is refused, with the reason", async () => {
259
+ storedNotes = [
260
+ storedNote(serviceCase, {
261
+ subscriberNotificationStatusOnNoteCreated:
262
+ StatusPageSubscriberNotificationStatus.Skipped,
263
+ shouldStatusPageSubscribersBeNotifiedOnNoteCreated: false,
264
+ }),
265
+ ];
266
+
267
+ await expect(
268
+ runBeforeUpdate(
269
+ serviceCase,
270
+ update({ props: makeProps([serviceCase.memberRole]) }),
271
+ ),
272
+ ).rejects.toThrow(
273
+ new BadDataException(
274
+ SubscriberNotificationResend.notePostedWithoutNotifyingMessage,
275
+ ),
276
+ );
277
+ });
278
+
279
+ test("a notification being sent right now is refused: its send would overwrite the request", async () => {
280
+ storedNotes = [
281
+ storedNote(serviceCase, {
282
+ subscriberNotificationStatusOnNoteCreated:
283
+ StatusPageSubscriberNotificationStatus.InProgress,
284
+ }),
285
+ ];
286
+
287
+ await expect(
288
+ runBeforeUpdate(
289
+ serviceCase,
290
+ update({ props: makeProps([serviceCase.memberRole]) }),
291
+ ),
292
+ ).rejects.toThrow(SubscriberNotificationResend.beingSentMessage);
293
+ });
294
+
295
+ test("a bulk resend is refused when any matched note cannot be sent again", async () => {
296
+ const quiet: BaseModel = storedNote(serviceCase, {
297
+ shouldStatusPageSubscribersBeNotifiedOnNoteCreated: false,
298
+ });
299
+ quiet._id = OTHER_NOTE_ID;
300
+ storedNotes = [storedNote(serviceCase), quiet];
301
+
302
+ await expect(
303
+ runBeforeUpdate(
304
+ serviceCase,
305
+ update({
306
+ props: makeProps([serviceCase.memberRole]),
307
+ query: { projectId: PROJECT_ID.toString() },
308
+ }),
309
+ ),
310
+ ).rejects.toThrow(
311
+ SubscriberNotificationResend.notePostedWithoutNotifyingMessage,
312
+ );
313
+ });
314
+
315
+ test("a role that can only read notes may not, and learns nothing about the note", async () => {
316
+ storedNotes = [
317
+ storedNote(serviceCase, {
318
+ shouldStatusPageSubscribersBeNotifiedOnNoteCreated: false,
319
+ }),
320
+ ];
321
+
322
+ await expect(
323
+ runBeforeUpdate(
324
+ serviceCase,
325
+ update({
326
+ props: makeProps([
327
+ serviceCase.viewerRole,
328
+ serviceCase.readPermission,
329
+ ]),
330
+ }),
331
+ ),
332
+ ).rejects.toThrow(
333
+ new NotAuthorizedException(
334
+ SubscriberNotificationResend.noPermissionToResendNoteMessage,
335
+ ),
336
+ );
337
+ expect(noteFindBy).not.toHaveBeenCalled();
338
+ });
339
+
340
+ test("a role that may edit notes but could not post one may not", async () => {
341
+ await expect(
342
+ runBeforeUpdate(
343
+ serviceCase,
344
+ update({
345
+ props: makeProps([
346
+ serviceCase.editPermission,
347
+ serviceCase.readPermission,
348
+ ]),
349
+ }),
350
+ ),
351
+ ).rejects.toThrow(
352
+ SubscriberNotificationResend.noPermissionToResendNoteMessage,
353
+ );
354
+ expect(noteFindBy).not.toHaveBeenCalled();
355
+ });
356
+
357
+ test("a role that may post notes but not edit them may not", async () => {
358
+ await expect(
359
+ runBeforeUpdate(
360
+ serviceCase,
361
+ update({
362
+ props: makeProps([
363
+ serviceCase.createPermission,
364
+ serviceCase.readPermission,
365
+ ]),
366
+ }),
367
+ ),
368
+ ).rejects.toThrow(
369
+ SubscriberNotificationResend.noPermissionToResendNoteMessage,
370
+ );
371
+ expect(noteFindBy).not.toHaveBeenCalled();
372
+ });
373
+
374
+ test("a custom role that may post and edit notes may", async () => {
375
+ await expect(
376
+ runBeforeUpdate(
377
+ serviceCase,
378
+ update({
379
+ props: makeProps([
380
+ serviceCase.createPermission,
381
+ serviceCase.editPermission,
382
+ serviceCase.readPermission,
383
+ ]),
384
+ }),
385
+ ),
386
+ ).resolves.toBeDefined();
387
+ });
388
+
389
+ test("a master admin skips the permission checks, not the checks on the note", async () => {
390
+ storedNotes = [
391
+ storedNote(serviceCase, {
392
+ shouldStatusPageSubscribersBeNotifiedOnNoteCreated: false,
393
+ }),
394
+ ];
395
+
396
+ await expect(
397
+ runBeforeUpdate(
398
+ serviceCase,
399
+ update({ props: { userId: USER_ID, isMasterAdmin: true } }),
400
+ ),
401
+ ).rejects.toThrow(
402
+ SubscriberNotificationResend.notePostedWithoutNotifyingMessage,
403
+ );
404
+ });
405
+
406
+ test("root - the workers - is never checked, and nothing is read", async () => {
407
+ storedNotes = [
408
+ storedNote(serviceCase, {
409
+ shouldStatusPageSubscribersBeNotifiedOnNoteCreated: false,
410
+ }),
411
+ ];
412
+
413
+ await runBeforeUpdate(serviceCase, update({ props: { isRoot: true } }));
414
+
415
+ expect(noteFindBy).not.toHaveBeenCalled();
416
+ });
417
+
418
+ test("an edit that does not send it again is not looked at", async () => {
419
+ await runBeforeUpdate(
420
+ serviceCase,
421
+ update({
422
+ props: makeProps([serviceCase.viewerRole]),
423
+ data: { note: "Typo fixed." },
424
+ }),
425
+ );
426
+
427
+ expect(noteFindBy).not.toHaveBeenCalled();
428
+ });
429
+
430
+ test("retrying the update notification needs the permission to post notifying notes too", async () => {
431
+ await expect(
432
+ runBeforeUpdate(
433
+ serviceCase,
434
+ update({
435
+ props: makeProps([
436
+ serviceCase.editPermission,
437
+ serviceCase.readPermission,
438
+ ]),
439
+ data: {
440
+ subscriberNotificationStatusOnNoteUpdated:
441
+ StatusPageSubscriberNotificationStatus.Pending,
442
+ subscriberNotificationStatusMessageOnNoteUpdated:
443
+ SubscriberUpdateNotification.resendQueuedMessage,
444
+ },
445
+ }),
446
+ ),
447
+ ).rejects.toThrow(
448
+ new NotAuthorizedException(
449
+ SubscriberNotificationResend.noPermissionToNotifyAboutEditMessage,
450
+ ),
451
+ );
452
+ expect(noteFindBy).not.toHaveBeenCalled();
453
+ });
454
+ },
455
+ );
456
+
457
+ /*
458
+ * Telling subscribers about an edit - the edit carries the notify-on-edit
459
+ * request (SubscriberUpdateNotification), or writes Pending into the note's
460
+ * 'updated' status, as the dashboard's Retry of a failed update does - tells
461
+ * every subscriber what the note says now. An editor can change the text
462
+ * first, so it needs the permission to post a note that notifies
463
+ * subscribers, as sending the 'posted' notification again does; and it is
464
+ * refused while that update notification is being sent, whose send would
465
+ * otherwise run alongside a second one or overwrite the request.
466
+ */
467
+ describe.each(SERVICE_CASES)(
468
+ "$name: telling subscribers about an edit",
469
+ (serviceCase: ServiceCase) => {
470
+ const NOTIFY_ON_EDIT: JSONObject =
471
+ SubscriberUpdateNotification.getMiscDataProps();
472
+ const EDIT: JSONObject = { note: "The fix is rolling out, ETA 14:00 UTC." };
473
+
474
+ beforeEach(() => {
475
+ storedNotes = [storedNote(serviceCase)];
476
+
477
+ noteFindBy = getJestMockFunction();
478
+ noteFindBy.mockImplementation(() => {
479
+ return Promise.resolve(storedNotes);
480
+ });
481
+ jest
482
+ .spyOn(serviceCase.service, "findBy")
483
+ .mockImplementation(noteFindBy as never);
484
+ });
485
+
486
+ afterEach(() => {
487
+ jest.restoreAllMocks();
488
+ });
489
+
490
+ test("a member may, and the update notification is queued with the edit", async () => {
491
+ const result: OnUpdate<BaseModel> = await runBeforeUpdate(
492
+ serviceCase,
493
+ update({
494
+ props: makeProps([serviceCase.memberRole]),
495
+ data: { ...EDIT },
496
+ miscDataProps: NOTIFY_ON_EDIT,
497
+ }),
498
+ );
499
+
500
+ expect(result.updateBy.data).toEqual({
501
+ ...EDIT,
502
+ subscriberNotificationStatusOnNoteUpdated:
503
+ StatusPageSubscriberNotificationStatus.Pending,
504
+ subscriberNotificationStatusMessageOnNoteUpdated:
505
+ SubscriberUpdateNotification.queuedMessage,
506
+ });
507
+ });
508
+
509
+ test("reads the update notification's state with the caller's own permissions", async () => {
510
+ const props: DatabaseCommonInteractionProps = makeProps([
511
+ serviceCase.memberRole,
512
+ ]);
513
+
514
+ await runBeforeUpdate(
515
+ serviceCase,
516
+ update({ props, data: { ...EDIT }, miscDataProps: NOTIFY_ON_EDIT }),
517
+ );
518
+
519
+ expect(noteFindBy).toHaveBeenCalledTimes(1);
520
+ const findBy: {
521
+ query: JSONObject;
522
+ select: JSONObject;
523
+ props: DatabaseCommonInteractionProps;
524
+ } = noteFindBy.mock.calls[0]![0] as {
525
+ query: JSONObject;
526
+ select: JSONObject;
527
+ props: DatabaseCommonInteractionProps;
528
+ };
529
+ expect(findBy.query).toEqual({ _id: NOTE_ID });
530
+ expect(findBy.props).toBe(props);
531
+ expect(findBy.select).toEqual({
532
+ _id: true,
533
+ subscriberNotificationStatusOnNoteUpdated: true,
534
+ });
535
+ });
536
+
537
+ test("a role that may edit notes but could not post one may not, and learns nothing about the note", async () => {
538
+ await expect(
539
+ runBeforeUpdate(
540
+ serviceCase,
541
+ update({
542
+ props: makeProps([
543
+ serviceCase.editPermission,
544
+ serviceCase.readPermission,
545
+ ]),
546
+ data: { ...EDIT },
547
+ miscDataProps: NOTIFY_ON_EDIT,
548
+ }),
549
+ ),
550
+ ).rejects.toThrow(
551
+ new NotAuthorizedException(
552
+ SubscriberNotificationResend.noPermissionToNotifyAboutEditMessage,
553
+ ),
554
+ );
555
+ expect(noteFindBy).not.toHaveBeenCalled();
556
+ });
557
+
558
+ test("that role may still save the edit without notifying subscribers", async () => {
559
+ const result: OnUpdate<BaseModel> = await runBeforeUpdate(
560
+ serviceCase,
561
+ update({
562
+ props: makeProps([
563
+ serviceCase.editPermission,
564
+ serviceCase.readPermission,
565
+ ]),
566
+ data: { ...EDIT },
567
+ }),
568
+ );
569
+
570
+ expect(result.updateBy.data).toEqual(EDIT);
571
+ expect(noteFindBy).not.toHaveBeenCalled();
572
+ });
573
+
574
+ test("a custom role that may post and edit notes may", async () => {
575
+ await expect(
576
+ runBeforeUpdate(
577
+ serviceCase,
578
+ update({
579
+ props: makeProps([
580
+ serviceCase.createPermission,
581
+ serviceCase.editPermission,
582
+ serviceCase.readPermission,
583
+ ]),
584
+ data: { ...EDIT },
585
+ miscDataProps: NOTIFY_ON_EDIT,
586
+ }),
587
+ ),
588
+ ).resolves.toBeDefined();
589
+ });
590
+
591
+ test("is refused, with the reason, while the update notification is being sent", async () => {
592
+ storedNotes = [
593
+ storedNote(serviceCase, {
594
+ subscriberNotificationStatusOnNoteUpdated:
595
+ StatusPageSubscriberNotificationStatus.InProgress,
596
+ }),
597
+ ];
598
+
599
+ await expect(
600
+ runBeforeUpdate(
601
+ serviceCase,
602
+ update({
603
+ props: makeProps([serviceCase.memberRole]),
604
+ data: { ...EDIT },
605
+ miscDataProps: NOTIFY_ON_EDIT,
606
+ }),
607
+ ),
608
+ ).rejects.toThrow(
609
+ new BadDataException(
610
+ SubscriberNotificationResend.updateBeingSentMessage,
611
+ ),
612
+ );
613
+ });
614
+
615
+ test("a Retry written straight into the column is refused while it is being sent too", async () => {
616
+ storedNotes = [
617
+ storedNote(serviceCase, {
618
+ subscriberNotificationStatusOnNoteUpdated:
619
+ StatusPageSubscriberNotificationStatus.InProgress,
620
+ }),
621
+ ];
622
+
623
+ await expect(
624
+ runBeforeUpdate(
625
+ serviceCase,
626
+ update({
627
+ props: makeProps([serviceCase.memberRole]),
628
+ data: {
629
+ subscriberNotificationStatusOnNoteUpdated:
630
+ StatusPageSubscriberNotificationStatus.Pending,
631
+ },
632
+ }),
633
+ ),
634
+ ).rejects.toThrow(SubscriberNotificationResend.updateBeingSentMessage);
635
+ });
636
+
637
+ test.each([
638
+ StatusPageSubscriberNotificationStatus.Pending,
639
+ StatusPageSubscriberNotificationStatus.Success,
640
+ StatusPageSubscriberNotificationStatus.Failed,
641
+ StatusPageSubscriberNotificationStatus.Skipped,
642
+ ])(
643
+ "is let through when the update notification is %s",
644
+ async (status: StatusPageSubscriberNotificationStatus) => {
645
+ storedNotes = [
646
+ storedNote(serviceCase, {
647
+ subscriberNotificationStatusOnNoteUpdated: status,
648
+ }),
649
+ ];
650
+
651
+ await expect(
652
+ runBeforeUpdate(
653
+ serviceCase,
654
+ update({
655
+ props: makeProps([serviceCase.memberRole]),
656
+ data: { ...EDIT },
657
+ miscDataProps: NOTIFY_ON_EDIT,
658
+ }),
659
+ ),
660
+ ).resolves.toBeDefined();
661
+ },
662
+ );
663
+
664
+ test("the 'posted' notification being sent does not stop an edit that notifies: the update waits for it", async () => {
665
+ storedNotes = [
666
+ storedNote(serviceCase, {
667
+ subscriberNotificationStatusOnNoteCreated:
668
+ StatusPageSubscriberNotificationStatus.InProgress,
669
+ }),
670
+ ];
671
+
672
+ await expect(
673
+ runBeforeUpdate(
674
+ serviceCase,
675
+ update({
676
+ props: makeProps([serviceCase.memberRole]),
677
+ data: { ...EDIT },
678
+ miscDataProps: NOTIFY_ON_EDIT,
679
+ }),
680
+ ),
681
+ ).resolves.toBeDefined();
682
+ });
683
+
684
+ test("a master admin skips the permission checks, not the check on the send", async () => {
685
+ storedNotes = [
686
+ storedNote(serviceCase, {
687
+ subscriberNotificationStatusOnNoteUpdated:
688
+ StatusPageSubscriberNotificationStatus.InProgress,
689
+ }),
690
+ ];
691
+
692
+ await expect(
693
+ runBeforeUpdate(
694
+ serviceCase,
695
+ update({
696
+ props: { userId: USER_ID, isMasterAdmin: true },
697
+ data: { ...EDIT },
698
+ miscDataProps: NOTIFY_ON_EDIT,
699
+ }),
700
+ ),
701
+ ).rejects.toThrow(SubscriberNotificationResend.updateBeingSentMessage);
702
+ });
703
+
704
+ test("root - the workers - is never checked, and nothing is read", async () => {
705
+ storedNotes = [
706
+ storedNote(serviceCase, {
707
+ subscriberNotificationStatusOnNoteUpdated:
708
+ StatusPageSubscriberNotificationStatus.InProgress,
709
+ }),
710
+ ];
711
+
712
+ await runBeforeUpdate(
713
+ serviceCase,
714
+ update({
715
+ props: { isRoot: true },
716
+ data: { ...EDIT },
717
+ miscDataProps: NOTIFY_ON_EDIT,
718
+ }),
719
+ );
720
+
721
+ expect(noteFindBy).not.toHaveBeenCalled();
722
+ });
723
+ },
724
+ );
725
+
726
+ describe("SubscriberNotificationResendAccess.assertCallerMayUpdateColumns", () => {
727
+ const COLUMNS: JSONObject = {
728
+ subscriberNotificationStatusOnIncidentCreated:
729
+ StatusPageSubscriberNotificationStatus.Pending,
730
+ statusPagesNotifiedOnCreation: [],
731
+ };
732
+
733
+ test.each([
734
+ Permission.ProjectOwner,
735
+ Permission.ProjectAdmin,
736
+ Permission.ProjectMember,
737
+ Permission.IncidentAdmin,
738
+ Permission.IncidentMember,
739
+ Permission.EditProjectIncident,
740
+ ])("%s may write the incident's notification columns", (role: Permission) => {
741
+ expect(() => {
742
+ SubscriberNotificationResendAccess.assertCallerMayUpdateColumns({
743
+ modelType: Incident,
744
+ columns: COLUMNS,
745
+ props: makeProps([role]),
746
+ refusal: "refused",
747
+ });
748
+ }).not.toThrow();
749
+ });
750
+
751
+ test.each([
752
+ Permission.IncidentViewer,
753
+ Permission.Viewer,
754
+ Permission.ReadProjectIncident,
755
+ Permission.CreateProjectIncident,
756
+ Permission.StatusPageAdmin,
757
+ ])("%s may not, and is told what it was refused", (role: Permission) => {
758
+ expect(() => {
759
+ SubscriberNotificationResendAccess.assertCallerMayUpdateColumns({
760
+ modelType: Incident,
761
+ columns: COLUMNS,
762
+ props: makeProps([role]),
763
+ refusal: "You may not send it again.",
764
+ });
765
+ }).toThrow(new NotAuthorizedException("You may not send it again."));
766
+ });
767
+
768
+ test("root and master admins are not checked", () => {
769
+ for (const props of [
770
+ { isRoot: true },
771
+ { userId: USER_ID, isMasterAdmin: true },
772
+ ] as Array<DatabaseCommonInteractionProps>) {
773
+ expect(() => {
774
+ SubscriberNotificationResendAccess.assertCallerMayUpdateColumns({
775
+ modelType: Incident,
776
+ columns: COLUMNS,
777
+ props,
778
+ refusal: "refused",
779
+ });
780
+ }).not.toThrow();
781
+ }
782
+ });
783
+
784
+ test("a caller with no credentials keeps the sign-in error, not the refusal", () => {
785
+ let thrown: unknown = null;
786
+
787
+ try {
788
+ SubscriberNotificationResendAccess.assertCallerMayUpdateColumns({
789
+ modelType: Incident,
790
+ columns: COLUMNS,
791
+ props: {},
792
+ refusal: "refused",
793
+ });
794
+ } catch (err) {
795
+ thrown = err;
796
+ }
797
+
798
+ expect(thrown).not.toBeNull();
799
+ expect((thrown as Error).message).not.toBe("refused");
800
+ });
801
+ });
802
+
803
+ /*
804
+ * End to end through DatabaseService.updateOneById with the real hook and
805
+ * the real permission checks: only the database, the label query rewrite
806
+ * and the audit log are stubbed.
807
+ */
808
+ describe("IncidentPublicNoteService.updateOneById: sending a note's notification again", () => {
809
+ let saveMock: MockFunction;
810
+ let updateMock: MockFunction;
811
+
812
+ beforeEach(() => {
813
+ saveMock = getJestMockFunction();
814
+ saveMock.mockImplementation((item: unknown) => {
815
+ return Promise.resolve(item);
816
+ });
817
+ updateMock = getJestMockFunction();
818
+ updateMock.mockResolvedValue({ affected: 1 });
819
+
820
+ jest.spyOn(IncidentPublicNoteService, "findBy").mockImplementation((() => {
821
+ return Promise.resolve([storedNote(SERVICE_CASES[0]!)]);
822
+ }) as never);
823
+
824
+ jest
825
+ .spyOn(
826
+ IncidentPublicNoteService as unknown as {
827
+ _findBy: (...args: Array<unknown>) => Promise<unknown>;
828
+ },
829
+ "_findBy",
830
+ )
831
+ .mockImplementation((() => {
832
+ const row: IncidentPublicNote = new IncidentPublicNote();
833
+ row._id = NOTE_ID;
834
+ row.projectId = PROJECT_ID;
835
+ return Promise.resolve([row]);
836
+ }) as never);
837
+
838
+ jest
839
+ .spyOn(
840
+ IncidentPublicNoteService as unknown as {
841
+ onUpdateSuccess: (...args: Array<unknown>) => Promise<unknown>;
842
+ },
843
+ "onUpdateSuccess",
844
+ )
845
+ .mockImplementation(((onUpdate: unknown): Promise<unknown> => {
846
+ return Promise.resolve(onUpdate);
847
+ }) as never);
848
+
849
+ jest
850
+ .spyOn(
851
+ IncidentPublicNoteService as unknown as {
852
+ getRepository: () => unknown;
853
+ },
854
+ "getRepository",
855
+ )
856
+ .mockReturnValue({
857
+ update: updateMock,
858
+ save: saveMock,
859
+ } as never);
860
+
861
+ jest.spyOn(BasePermission, "checkPermissions").mockImplementation(((
862
+ _modelType: unknown,
863
+ query: unknown,
864
+ ): Promise<unknown> => {
865
+ return Promise.resolve({ query });
866
+ }) as never);
867
+
868
+ jest
869
+ .spyOn(IncidentPublicNoteService, "onTriggerWorkflow")
870
+ .mockResolvedValue(undefined as never);
871
+ jest
872
+ .spyOn(IncidentPublicNoteService, "onTriggerRealtime")
873
+ .mockResolvedValue(undefined as never);
874
+ jest
875
+ .spyOn(AuditLogService, "recordUpdate")
876
+ .mockResolvedValue(undefined as never);
877
+ });
878
+
879
+ afterEach(() => {
880
+ jest.restoreAllMocks();
881
+ });
882
+
883
+ function written(): JSONObject {
884
+ const calls: Array<Array<unknown>> = [
885
+ ...saveMock.mock.calls,
886
+ ...updateMock.mock.calls,
887
+ ];
888
+
889
+ expect(calls).toHaveLength(1);
890
+
891
+ const call: Array<unknown> = calls[0]!;
892
+
893
+ return (call.length > 1 ? call[1] : call[0]) as JSONObject;
894
+ }
895
+
896
+ test("an incident member's resend writes Pending", async () => {
897
+ await IncidentPublicNoteService.updateOneById({
898
+ id: new ObjectID(NOTE_ID),
899
+ data: {
900
+ subscriberNotificationStatusOnNoteCreated:
901
+ StatusPageSubscriberNotificationStatus.Pending,
902
+ },
903
+ props: makeProps([Permission.IncidentMember]),
904
+ });
905
+
906
+ expect(written()["subscriberNotificationStatusOnNoteCreated"]).toBe(
907
+ StatusPageSubscriberNotificationStatus.Pending,
908
+ );
909
+ });
910
+
911
+ test("an incident viewer cannot, and nothing is written", async () => {
912
+ // Refused by the update's own permission check, which comes first.
913
+ await expect(
914
+ IncidentPublicNoteService.updateOneById({
915
+ id: new ObjectID(NOTE_ID),
916
+ data: {
917
+ subscriberNotificationStatusOnNoteCreated:
918
+ StatusPageSubscriberNotificationStatus.Pending,
919
+ },
920
+ props: makeProps([Permission.IncidentViewer]),
921
+ }),
922
+ ).rejects.toThrow(NotAuthorizedException);
923
+
924
+ expect(saveMock).not.toHaveBeenCalled();
925
+ expect(updateMock).not.toHaveBeenCalled();
926
+ });
927
+
928
+ test("a role that may edit notes but could not post one cannot, and nothing is written", async () => {
929
+ await expect(
930
+ IncidentPublicNoteService.updateOneById({
931
+ id: new ObjectID(NOTE_ID),
932
+ data: {
933
+ subscriberNotificationStatusOnNoteCreated:
934
+ StatusPageSubscriberNotificationStatus.Pending,
935
+ },
936
+ props: makeProps([
937
+ Permission.EditIncidentPublicNote,
938
+ Permission.ReadIncidentPublicNote,
939
+ ]),
940
+ }),
941
+ ).rejects.toThrow(
942
+ SubscriberNotificationResend.noPermissionToResendNoteMessage,
943
+ );
944
+
945
+ expect(saveMock).not.toHaveBeenCalled();
946
+ expect(updateMock).not.toHaveBeenCalled();
947
+ });
948
+
949
+ test("a role that may edit notes but could not post one cannot tell subscribers about its edit, and nothing is written", async () => {
950
+ await expect(
951
+ IncidentPublicNoteService.updateOneById({
952
+ id: new ObjectID(NOTE_ID),
953
+ data: { note: "Everything is fine, ignore the last update." },
954
+ miscDataProps: SubscriberUpdateNotification.getMiscDataProps(),
955
+ props: makeProps([
956
+ Permission.EditIncidentPublicNote,
957
+ Permission.ReadIncidentPublicNote,
958
+ ]),
959
+ }),
960
+ ).rejects.toThrow(
961
+ SubscriberNotificationResend.noPermissionToNotifyAboutEditMessage,
962
+ );
963
+
964
+ expect(saveMock).not.toHaveBeenCalled();
965
+ expect(updateMock).not.toHaveBeenCalled();
966
+ });
967
+
968
+ test("an incident member's edit that notifies subscribers queues the update notification", async () => {
969
+ await IncidentPublicNoteService.updateOneById({
970
+ id: new ObjectID(NOTE_ID),
971
+ data: { note: "The fix is rolling out." },
972
+ miscDataProps: SubscriberUpdateNotification.getMiscDataProps(),
973
+ props: makeProps([Permission.IncidentMember]),
974
+ });
975
+
976
+ expect(written()["note"]).toBe("The fix is rolling out.");
977
+ expect(written()["subscriberNotificationStatusOnNoteUpdated"]).toBe(
978
+ StatusPageSubscriberNotificationStatus.Pending,
979
+ );
980
+ });
981
+
982
+ test("that role can still edit the note itself", async () => {
983
+ await IncidentPublicNoteService.updateOneById({
984
+ id: new ObjectID(NOTE_ID),
985
+ data: { note: "Corrected." },
986
+ props: makeProps([
987
+ Permission.EditIncidentPublicNote,
988
+ Permission.ReadIncidentPublicNote,
989
+ ]),
990
+ });
991
+
992
+ expect(written()["note"]).toBe("Corrected.");
993
+ });
994
+ });