@oneuptime/common 14.0.13 → 14.0.14

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 (1045) hide show
  1. package/Models/DatabaseModels/AlertGroupingRule.ts +10 -6
  2. package/Models/DatabaseModels/File.ts +41 -2
  3. package/Models/DatabaseModels/Form.ts +126 -0
  4. package/Models/DatabaseModels/IncidentGroupingRule.ts +10 -6
  5. package/Models/DatabaseModels/LogDropFilter.ts +1 -0
  6. package/Models/DatabaseModels/LogPipeline.ts +1 -0
  7. package/Models/DatabaseModels/LogScrubRule.ts +14 -8
  8. package/Models/DatabaseModels/MetricRecordingRule.ts +2 -1
  9. package/Models/DatabaseModels/OnCallDutyPolicyExecutionLogTimeline.ts +4 -2
  10. package/Models/DatabaseModels/OnCallDutyPolicyUserOverride.ts +22 -9
  11. package/Models/DatabaseModels/ProjectCallSMSConfig.ts +6 -1
  12. package/Models/DatabaseModels/StatusPage.ts +8 -4
  13. package/Models/DatabaseModels/TraceDropFilter.ts +1 -0
  14. package/Models/DatabaseModels/TracePipeline.ts +1 -0
  15. package/Models/DatabaseModels/TraceRecordingRule.ts +2 -1
  16. package/Models/DatabaseModels/TraceScrubRule.ts +14 -8
  17. package/Models/DatabaseModels/UserOnCallLog.ts +4 -2
  18. package/Server/API/CustomDomainRoutes.ts +251 -0
  19. package/Server/API/DashboardAPI.ts +33 -4
  20. package/Server/API/DashboardDomainAPI.ts +63 -93
  21. package/Server/API/StatusPageAPI.ts +92 -0
  22. package/Server/API/StatusPageDomainAPI.ts +59 -96
  23. package/Server/Infrastructure/GlobalCache.ts +84 -0
  24. package/Server/Infrastructure/Postgres/SchemaMigrations/1797700000000-AddFormBranding.ts +47 -0
  25. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +2 -0
  26. package/Server/Services/AlertGroupingEngineService.ts +48 -70
  27. package/Server/Services/AlertGroupingRuleService.ts +38 -0
  28. package/Server/Services/CephClusterService.ts +25 -0
  29. package/Server/Services/CloudResourceService.ts +42 -0
  30. package/Server/Services/DashboardDomainService.ts +640 -214
  31. package/Server/Services/DashboardService.ts +30 -8
  32. package/Server/Services/DatabaseService.ts +143 -12
  33. package/Server/Services/DockerHostService.ts +60 -0
  34. package/Server/Services/DockerSwarmClusterService.ts +25 -0
  35. package/Server/Services/FileService.ts +60 -0
  36. package/Server/Services/FormService.ts +212 -2
  37. package/Server/Services/GlobalOidcService.ts +10 -0
  38. package/Server/Services/GlobalSsoService.ts +10 -0
  39. package/Server/Services/HostService.ts +58 -0
  40. package/Server/Services/IncidentGroupingEngineService.ts +48 -70
  41. package/Server/Services/IncidentGroupingRuleService.ts +38 -0
  42. package/Server/Services/IoTFleetService.ts +26 -0
  43. package/Server/Services/KubernetesClusterService.ts +64 -0
  44. package/Server/Services/LabelService.ts +6 -17
  45. package/Server/Services/LogScrubRuleService.ts +50 -0
  46. package/Server/Services/OnCallDutyPolicyScheduleService.ts +264 -0
  47. package/Server/Services/OnCallDutyPolicyUserOverrideService.ts +9 -1
  48. package/Server/Services/OnCallNotificationAlertingService.ts +24 -29
  49. package/Server/Services/OnCallSetupReminderService.ts +41 -40
  50. package/Server/Services/PodmanHostService.ts +60 -0
  51. package/Server/Services/ProfileAggregationService.ts +38 -0
  52. package/Server/Services/ProjectCallSMSConfigService.ts +76 -3
  53. package/Server/Services/ProjectOidcService.ts +20 -0
  54. package/Server/Services/ProjectSsoService.ts +20 -0
  55. package/Server/Services/ProxmoxClusterService.ts +25 -0
  56. package/Server/Services/RumApplicationService.ts +45 -0
  57. package/Server/Services/ScheduledMaintenanceService.ts +9 -23
  58. package/Server/Services/ServerlessFunctionService.ts +47 -0
  59. package/Server/Services/ServiceService.ts +33 -10
  60. package/Server/Services/StatusPageDomainService.ts +622 -213
  61. package/Server/Services/StatusPageOidcService.ts +20 -0
  62. package/Server/Services/StatusPageResourceService.ts +89 -34
  63. package/Server/Services/StatusPageService.ts +270 -54
  64. package/Server/Services/StatusPageSsoService.ts +20 -0
  65. package/Server/Services/TraceScrubRuleService.ts +50 -0
  66. package/Server/Services/UserIncomingCallNumberService.ts +28 -14
  67. package/Server/Services/VMwareVCenterService.ts +25 -0
  68. package/Server/Utils/AI/PerfEvidence/SpanTreeAnalyzer.ts +3 -3
  69. package/Server/Utils/Greenlock/CertificateOrder.ts +482 -0
  70. package/Server/Utils/Greenlock/CertificateOrderBudget.ts +134 -0
  71. package/Server/Utils/Greenlock/CertificateOrderFailures.ts +288 -0
  72. package/Server/Utils/Greenlock/CertificateOrderLock.ts +163 -0
  73. package/Server/Utils/Greenlock/CertificateOrderOutcome.ts +29 -0
  74. package/Server/Utils/Greenlock/CertificateOwners.ts +72 -0
  75. package/Server/Utils/Greenlock/CertificateReissueOrder.ts +99 -0
  76. package/Server/Utils/Greenlock/CustomDomainOrders.ts +397 -0
  77. package/Server/Utils/Greenlock/Greenlock.ts +839 -203
  78. package/Server/Utils/Rules/GroupingRuleEpisodeOwners.ts +551 -0
  79. package/Server/Utils/Rules/OwnerRuleAssignment.ts +8 -1
  80. package/Server/Utils/ScrubRuleValidation.ts +248 -0
  81. package/Server/Utils/StatusPage/AffectedStatusPageResources.ts +138 -0
  82. package/Server/Utils/StatusPage/StatusPagesListingMonitorsBuilder.ts +294 -0
  83. package/Server/Utils/Telemetry/DiscoveredResourceCreate.ts +275 -0
  84. package/Server/Utils/Telemetry/DiscoveredResourceUpdate.ts +217 -0
  85. package/Server/Utils/Telemetry/MessageQueueDiscovery.ts +8 -2
  86. package/Tests/App/Accounts/FormPage.test.tsx +238 -0
  87. package/Tests/App/Accounts/FormUtils.test.ts +108 -0
  88. package/Tests/App/AdminDashboard/AdminAddToProjectMembersTeam.test.tsx +1087 -0
  89. package/Tests/App/AdminDashboard/AdminAuthenticationRequireSso.test.tsx +140 -138
  90. package/Tests/App/AdminDashboard/AdminAuthenticationSsoCardLocales.test.tsx +144 -145
  91. package/Tests/App/AdminDashboard/AdminSettingsFormsOnePage.test.tsx +907 -0
  92. package/Tests/App/AdminDashboard/AdminSettingsSwitches.test.tsx +906 -0
  93. package/Tests/App/AdminDashboard/AdminSmtpSettingsForm.test.tsx +823 -0
  94. package/Tests/App/AdminDashboard/BulkAddUsersToProjectModal.test.tsx +341 -368
  95. package/Tests/App/AdminDashboard/GlobalSsoOidcPages.test.tsx +199 -54
  96. package/Tests/App/AdminDashboard/ProjectScopedTeamsPicker.test.tsx +991 -0
  97. package/Tests/App/Dashboard/AIInvestigationSettingsLimits.test.tsx +147 -90
  98. package/Tests/App/Dashboard/AdminNotificationRulesPage.test.tsx +224 -104
  99. package/Tests/App/Dashboard/AdminUserNotificationMethodsPage.test.tsx +302 -0
  100. package/Tests/App/Dashboard/AdminUserOnCallPages.test.tsx +408 -152
  101. package/Tests/App/Dashboard/AffectedResourcesPicker.test.tsx +55 -0
  102. package/Tests/App/Dashboard/AffectedResourcesPickerEditForm.test.tsx +206 -115
  103. package/Tests/App/Dashboard/AffectedResourcesPickerFocusLeave.test.tsx +32 -0
  104. package/Tests/App/Dashboard/AffectedResourcesSummaryStep.test.tsx +206 -71
  105. package/Tests/App/Dashboard/AlertEpisodeViewFields.test.tsx +45 -9
  106. package/Tests/App/Dashboard/AnnouncementCreateForm.test.tsx +1208 -0
  107. package/Tests/App/Dashboard/AnnouncementEditAndTemplateForms.test.tsx +693 -0
  108. package/Tests/App/Dashboard/AnnouncementFormsStructure.test.ts +546 -0
  109. package/Tests/App/Dashboard/AnnouncementStatusPageSuggestions.test.tsx +425 -0
  110. package/Tests/App/Dashboard/AnnouncementsTableCreateLinks.test.tsx +267 -0
  111. package/Tests/App/Dashboard/ApiKeyCreateForm.test.ts +68 -51
  112. package/Tests/App/Dashboard/ApiKeyViewPermissions.test.tsx +11 -9
  113. package/Tests/App/Dashboard/ApiKeysCreateAccess.test.tsx +24 -16
  114. package/Tests/App/Dashboard/BulkChangeStateModalOnePage.test.tsx +333 -0
  115. package/Tests/App/Dashboard/CardDescriptionsSayWhatFor.test.tsx +307 -0
  116. package/Tests/App/Dashboard/ChangeAlertStateAIHeader.test.tsx +18 -4
  117. package/Tests/App/Dashboard/CloudDocumentationCard.test.tsx +7 -2
  118. package/Tests/App/Dashboard/ColorFieldsPrefilledPages.test.tsx +453 -0
  119. package/Tests/App/Dashboard/CreateFromRecordBreadcrumbs.test.ts +272 -0
  120. package/Tests/App/Dashboard/CreateFromRecordLinks.test.tsx +495 -0
  121. package/Tests/App/Dashboard/CreateFromRecordPages.test.tsx +1026 -0
  122. package/Tests/App/Dashboard/CreateWorkflowModal.test.tsx +65 -23
  123. package/Tests/App/Dashboard/CriteriaName.test.ts +5 -1
  124. package/Tests/App/Dashboard/CustomDomainCreateFlow.test.tsx +516 -0
  125. package/Tests/App/Dashboard/CustomDomainDnsSetupModal.test.tsx +547 -0
  126. package/Tests/App/Dashboard/CustomDomainsPages.test.tsx +1077 -0
  127. package/Tests/App/Dashboard/CustomFieldSimpleCreate.test.tsx +2 -2
  128. package/Tests/App/Dashboard/CustomFieldSimpleCreateRender.test.tsx +12 -6
  129. package/Tests/App/Dashboard/CustomFieldsSettingsIncidentOptions.test.tsx +1 -1
  130. package/Tests/App/Dashboard/CustomSmtpConfigForm.test.tsx +969 -0
  131. package/Tests/App/Dashboard/DashboardBlankCanvasAddWidget.test.tsx +425 -0
  132. package/Tests/App/Dashboard/DashboardCommandPalette.test.tsx +400 -0
  133. package/Tests/App/Dashboard/DashboardComponentsMRTranslation.test.tsx +4 -3
  134. package/Tests/App/Dashboard/DashboardCreateForm.test.ts +375 -0
  135. package/Tests/App/Dashboard/DashboardCreateFromTemplate.test.tsx +727 -0
  136. package/Tests/App/Dashboard/DashboardEmptyCanvasAddWidget.test.tsx +86 -0
  137. package/Tests/App/Dashboard/DashboardPhoneProductsMenu.test.tsx +99 -9
  138. package/Tests/App/Dashboard/DashboardSharingCard.test.tsx +1226 -0
  139. package/Tests/App/Dashboard/DashboardSharingEntryPoints.test.tsx +383 -0
  140. package/Tests/App/Dashboard/DashboardSharingPage.test.tsx +478 -0
  141. package/Tests/App/Dashboard/DeclareFormsAdvancedRender.test.tsx +90 -34
  142. package/Tests/App/Dashboard/DeclareIncidentMonitorsApart.test.tsx +1028 -0
  143. package/Tests/App/Dashboard/DefaultSsoTeams.test.tsx +215 -0
  144. package/Tests/App/Dashboard/DisabledWarningArchived.test.tsx +3 -3
  145. package/Tests/App/Dashboard/DisabledWarningRobustness.test.tsx +15 -12
  146. package/Tests/App/Dashboard/DiscoveredResourceCreateForms.test.tsx +936 -0
  147. package/Tests/App/Dashboard/EmbeddedMetricCardGroup.test.tsx +720 -0
  148. package/Tests/App/Dashboard/EpisodeMemberRoleAssignmentsFormField.test.tsx +331 -0
  149. package/Tests/App/Dashboard/EpisodePostmortemPage.test.tsx +327 -0
  150. package/Tests/App/Dashboard/EscalationRuleForm.test.ts +2 -2
  151. package/Tests/App/Dashboard/EscalationRuleFormGuard.test.ts +6 -2
  152. package/Tests/App/Dashboard/EscalationRuleOneStep.test.tsx +8 -4
  153. package/Tests/App/Dashboard/EventMeasurements.test.ts +1175 -0
  154. package/Tests/App/Dashboard/EventMeasurementsCard.test.tsx +993 -0
  155. package/Tests/App/Dashboard/EventNoteComposerDialog.test.tsx +939 -0
  156. package/Tests/App/Dashboard/EventNotes.test.tsx +34 -1
  157. package/Tests/App/Dashboard/EventOverviewPages.test.tsx +186 -1
  158. package/Tests/App/Dashboard/EventSettingsSwitchPages.test.tsx +625 -0
  159. package/Tests/App/Dashboard/ExceptionMonitorEnvironmentFilter.test.tsx +6 -2
  160. package/Tests/App/Dashboard/FeedNoteComposerDialog.test.tsx +1327 -0
  161. package/Tests/App/Dashboard/FeedNoteDialogHelpers.ts +109 -0
  162. package/Tests/App/Dashboard/FilterChipClearButton.test.tsx +356 -0
  163. package/Tests/App/Dashboard/FormBrandingSection.test.tsx +504 -0
  164. package/Tests/App/Dashboard/FormBrandingValues.test.ts +255 -0
  165. package/Tests/App/Dashboard/FormBuilderParts.test.tsx +38 -1
  166. package/Tests/App/Dashboard/FormBuilderUi.test.tsx +176 -2
  167. package/Tests/App/Dashboard/FormMappingCard.test.tsx +33 -13
  168. package/Tests/App/Dashboard/FormsMovedPagesAndSubmissions.test.tsx +1 -1
  169. package/Tests/App/Dashboard/GeneratedKeyForms.test.tsx +30 -7
  170. package/Tests/App/Dashboard/GlobalProbesOnNewMonitorsCard.test.tsx +258 -0
  171. package/Tests/App/Dashboard/GroupingRuleFormFlow.test.tsx +653 -37
  172. package/Tests/App/Dashboard/GroupingRulePages.test.tsx +218 -9
  173. package/Tests/App/Dashboard/IncidentCreateStatusPageScope.test.tsx +52 -4
  174. package/Tests/App/Dashboard/IncidentEpisodeFeedPublicNoteNotifyDefault.test.tsx +230 -338
  175. package/Tests/App/Dashboard/IncidentEpisodeViewFields.test.tsx +45 -9
  176. package/Tests/App/Dashboard/IncidentFeedPublicNoteNotifyDefault.test.tsx +227 -295
  177. package/Tests/App/Dashboard/IncidentLinkedAlertsSettingsPage.test.tsx +157 -181
  178. package/Tests/App/Dashboard/IncidentPostmortemPage.test.tsx +775 -0
  179. package/Tests/App/Dashboard/IncidentRoleFormField.test.tsx +379 -15
  180. package/Tests/App/Dashboard/IncidentRolesSettingsPage.test.tsx +148 -32
  181. package/Tests/App/Dashboard/IncidentTemplateInitialState.test.tsx +430 -0
  182. package/Tests/App/Dashboard/IncidentTemplateMonitorsApart.test.tsx +492 -0
  183. package/Tests/App/Dashboard/IncomingCallEscalationOneStep.test.tsx +830 -0
  184. package/Tests/App/Dashboard/IncomingCallEscalationRuleForm.test.ts +510 -0
  185. package/Tests/App/Dashboard/IncomingCallEscalationRuleFormGuard.test.ts +378 -0
  186. package/Tests/App/Dashboard/IngestionKeyForm.test.ts +664 -0
  187. package/Tests/App/Dashboard/IngestionKeySelectorCreate.test.tsx +523 -0
  188. package/Tests/App/Dashboard/InventoryTypeAndStatusFacets.test.tsx +130 -9
  189. package/Tests/App/Dashboard/IpAllowlistCopy.test.ts +122 -0
  190. package/Tests/App/Dashboard/KubernetesClusterAiPage.test.tsx +19 -3
  191. package/Tests/App/Dashboard/KubernetesMetricsSetupEmptyState.test.tsx +275 -0
  192. package/Tests/App/Dashboard/KubernetesMetricsSetupHints.test.tsx +720 -0
  193. package/Tests/App/Dashboard/LabelsFormField.test.tsx +18 -13
  194. package/Tests/App/Dashboard/LegacyDefaultAssigneeNote.test.tsx +359 -0
  195. package/Tests/App/Dashboard/LogMonitorAttributeOperatorPreview.test.tsx +10 -10
  196. package/Tests/App/Dashboard/MarkdownTemplateDialogsWide.test.tsx +42 -20
  197. package/Tests/App/Dashboard/MeasurementForms.test.tsx +191 -5
  198. package/Tests/App/Dashboard/MeasurementPagesI18n.test.ts +2 -0
  199. package/Tests/App/Dashboard/MeasurementSetup.test.ts +5 -2
  200. package/Tests/App/Dashboard/MenuSpecificFoldedSections.test.tsx +38 -12
  201. package/Tests/App/Dashboard/MessageQueueRulePages.test.tsx +34 -11
  202. package/Tests/App/Dashboard/MetricAndRecordingRuleFormsRender.test.tsx +1206 -0
  203. package/Tests/App/Dashboard/MetricResultsState.test.ts +248 -0
  204. package/Tests/App/Dashboard/MetricViewResultsState.test.tsx +404 -0
  205. package/Tests/App/Dashboard/MonitorCreateFromMonitorBackedDevice.test.tsx +1 -1
  206. package/Tests/App/Dashboard/MonitorCriteriaAdvancedOptions.test.tsx +77 -27
  207. package/Tests/App/Dashboard/MonitorCriteriaAttributeFilter.test.tsx +5 -8
  208. package/Tests/App/Dashboard/MonitorCriteriaHeaderControls.test.tsx +228 -0
  209. package/Tests/App/Dashboard/MonitorCriteriaIncidentRoles.test.tsx +411 -0
  210. package/Tests/App/Dashboard/MonitorFeedRefresh.test.tsx +5 -1
  211. package/Tests/App/Dashboard/MonitorFormIncidentRoles.test.tsx +460 -0
  212. package/Tests/App/Dashboard/MonitorIntervalRedirect.test.tsx +299 -0
  213. package/Tests/App/Dashboard/MonitorOverviewHero.test.tsx +243 -2
  214. package/Tests/App/Dashboard/MonitorProbesAndIntervalPage.test.tsx +638 -0
  215. package/Tests/App/Dashboard/MonitorStepsDefaultStatusAdvanced.test.tsx +2 -2
  216. package/Tests/App/Dashboard/MonitorSummaryTestMonitorButton.test.tsx +2 -2
  217. package/Tests/App/Dashboard/MonitoringIntervalCard.test.tsx +493 -0
  218. package/Tests/App/Dashboard/MonitoringSwitch.test.tsx +597 -0
  219. package/Tests/App/Dashboard/MovedRunnerSettingsRedirects.test.tsx +4 -5
  220. package/Tests/App/Dashboard/NotificationChannelOffSwitchLists.test.tsx +636 -0
  221. package/Tests/App/Dashboard/OnCallPolicyCreateFormGuard.test.ts +8 -4
  222. package/Tests/App/Dashboard/OnCallPolicyFirstResponders.test.tsx +1 -1
  223. package/Tests/App/Dashboard/OnCallReadinessSurfaces.test.tsx +54 -0
  224. package/Tests/App/Dashboard/OnCallRulesDeleteGuard.test.tsx +43 -24
  225. package/Tests/App/Dashboard/OnCallRulesRedirects.test.tsx +530 -0
  226. package/Tests/App/Dashboard/OnCallRulesTable.test.tsx +158 -74
  227. package/Tests/App/Dashboard/OnCallRulesTabs.test.tsx +511 -0
  228. package/Tests/App/Dashboard/OnCallScheduleAddLayerDefaults.test.tsx +236 -0
  229. package/Tests/App/Dashboard/OnCallScheduleCreateFormGuard.test.ts +633 -0
  230. package/Tests/App/Dashboard/OnCallScheduleOverrideClarity.test.tsx +13 -2
  231. package/Tests/App/Dashboard/OnCallScheduleWhoTakesTurns.test.tsx +771 -0
  232. package/Tests/App/Dashboard/OnCallSharedCalendarFeedSettingsForm.test.tsx +357 -0
  233. package/Tests/App/Dashboard/OnCallShiftRemindersAndUpcomingShifts.test.tsx +16 -6
  234. package/Tests/App/Dashboard/OnboardingSsoEmptyState.test.tsx +112 -23
  235. package/Tests/App/Dashboard/PageSearchIndexCoversMenus.test.tsx +594 -0
  236. package/Tests/App/Dashboard/PageSearchIndexCoversRoutes.test.tsx +271 -0
  237. package/Tests/App/Dashboard/PageSearchRanking.test.tsx +373 -0
  238. package/Tests/App/Dashboard/PostmortemPageParts.test.tsx +548 -0
  239. package/Tests/App/Dashboard/ProbeAgreementCard.test.tsx +483 -0
  240. package/Tests/App/Dashboard/ProductOrientation.test.tsx +9 -2
  241. package/Tests/App/Dashboard/ProjectAISettingsPages.test.tsx +282 -404
  242. package/Tests/App/Dashboard/ProjectAiSettingsSwitchPages.test.tsx +1414 -0
  243. package/Tests/App/Dashboard/ProjectNotificationChannels.test.tsx +1191 -0
  244. package/Tests/App/Dashboard/ProjectSettingsSwitchCards.test.tsx +603 -0
  245. package/Tests/App/Dashboard/RemindersCard.test.tsx +477 -0
  246. package/Tests/App/Dashboard/ResourceDetailsCard.test.tsx +458 -0
  247. package/Tests/App/Dashboard/ResourceRuleForm.test.ts +525 -0
  248. package/Tests/App/Dashboard/ResourceRuleFormsRender.test.tsx +803 -0
  249. package/Tests/App/Dashboard/RoleAccess.test.ts +753 -0
  250. package/Tests/App/Dashboard/RoleCardSelectOptions.test.ts +13 -6
  251. package/Tests/App/Dashboard/RouteUtilPageRoute.test.ts +116 -0
  252. package/Tests/App/Dashboard/RowActionPlacementOverrides.test.tsx +1 -1
  253. package/Tests/App/Dashboard/RumSessionReplayRetentionSettings.test.tsx +172 -219
  254. package/Tests/App/Dashboard/RunbookStepHeaderControls.test.tsx +269 -0
  255. package/Tests/App/Dashboard/RunnerKubernetesAgentForm.test.tsx +45 -85
  256. package/Tests/App/Dashboard/RunnerSupersededByAiAgentNote.test.tsx +16 -23
  257. package/Tests/App/Dashboard/ScheduledMaintenanceAffectedResourcesEditForm.test.tsx +485 -0
  258. package/Tests/App/Dashboard/ScheduledMaintenanceChangeStateNotifyDefault.test.tsx +27 -2
  259. package/Tests/App/Dashboard/ScheduledMaintenanceCreateForm.test.tsx +65 -28
  260. package/Tests/App/Dashboard/ScheduledMaintenanceFeedPublicNoteNotifyDefault.test.tsx +208 -309
  261. package/Tests/App/Dashboard/ScheduledMaintenanceFeedRefresh.test.tsx +36 -53
  262. package/Tests/App/Dashboard/ScheduledMaintenanceFormsStructure.test.ts +83 -14
  263. package/Tests/App/Dashboard/ScheduledMaintenanceMonitorsApart.test.tsx +1085 -0
  264. package/Tests/App/Dashboard/ScheduledMaintenanceOverviewPage.test.tsx +378 -25
  265. package/Tests/App/Dashboard/ScheduledMaintenanceStatusPageSuggestions.test.tsx +513 -0
  266. package/Tests/App/Dashboard/ScheduledMaintenanceTemplateForm.test.tsx +31 -9
  267. package/Tests/App/Dashboard/ScheduledMaintenanceTemplateMonitorsApart.test.tsx +760 -0
  268. package/Tests/App/Dashboard/SecurityEventConnectionFormModal.test.tsx +11 -13
  269. package/Tests/App/Dashboard/SecurityEventsMonitorStepForm.test.tsx +228 -69
  270. package/Tests/App/Dashboard/SelectedProjectSwitches.test.tsx +359 -0
  271. package/Tests/App/Dashboard/ServerlessSetupGuide.test.ts +1 -1
  272. package/Tests/App/Dashboard/SessionReplayAllowedCard.test.tsx +297 -0
  273. package/Tests/App/Dashboard/SetupGuideCard.test.tsx +7 -1
  274. package/Tests/App/Dashboard/ShortFormsOnePage.test.tsx +591 -0
  275. package/Tests/App/Dashboard/SloNoticeBanner.test.tsx +253 -6
  276. package/Tests/App/Dashboard/SloOverviewData.test.tsx +99 -0
  277. package/Tests/App/Dashboard/SloOverviewLayout.test.tsx +3 -1
  278. package/Tests/App/Dashboard/SloSettings.test.tsx +257 -4
  279. package/Tests/App/Dashboard/SsoPages.test.tsx +448 -52
  280. package/Tests/App/Dashboard/StateChangeDialogsFolded.test.tsx +695 -0
  281. package/Tests/App/Dashboard/StateChangeFormFields.test.ts +376 -0
  282. package/Tests/App/Dashboard/StatusPageAccessCard.test.tsx +1088 -0
  283. package/Tests/App/Dashboard/StatusPageAccessCopy.test.ts +450 -0
  284. package/Tests/App/Dashboard/StatusPageAccessMenu.test.tsx +137 -0
  285. package/Tests/App/Dashboard/StatusPageAccessPage.test.tsx +458 -0
  286. package/Tests/App/Dashboard/StatusPageAdvancedSettingsPage.test.tsx +30 -86
  287. package/Tests/App/Dashboard/StatusPageBrandingPage.test.tsx +28 -15
  288. package/Tests/App/Dashboard/StatusPageDisplaySettingsCard.test.tsx +949 -14
  289. package/Tests/App/Dashboard/StatusPagePrivateUsersNotice.test.tsx +233 -0
  290. package/Tests/App/Dashboard/StatusPageReportsCard.test.tsx +1001 -0
  291. package/Tests/App/Dashboard/StatusPageRequireSsoCard.test.tsx +247 -0
  292. package/Tests/App/Dashboard/StatusPageSubscriberSettingsPages.test.tsx +4 -17
  293. package/Tests/App/Dashboard/StatusPageSuggestions.test.tsx +930 -0
  294. package/Tests/App/Dashboard/StatusPageSwitchCardPages.test.tsx +335 -0
  295. package/Tests/App/Dashboard/TeamBlockPermissionsRedirect.test.tsx +332 -0
  296. package/Tests/App/Dashboard/TeamCreateForm.test.ts +206 -0
  297. package/Tests/App/Dashboard/TeamViewPermissions.test.tsx +713 -0
  298. package/Tests/App/Dashboard/TeamViewSideMenuBlockPermissions.test.tsx +113 -0
  299. package/Tests/App/Dashboard/TeamsCreateAccess.test.tsx +799 -0
  300. package/Tests/App/Dashboard/TelemetryDataRuleForms.test.ts +655 -0
  301. package/Tests/App/Dashboard/TelemetryDataRuleFormsRender.test.tsx +1014 -0
  302. package/Tests/App/Dashboard/TelemetryIngestionKeyEdit.test.tsx +6 -3
  303. package/Tests/App/Dashboard/TelemetryIngestionKeyForm.test.tsx +446 -446
  304. package/Tests/App/Dashboard/TelemetryMonitorFiltersFold.test.tsx +613 -0
  305. package/Tests/App/Dashboard/TelemetryRetentionShells.test.tsx +37 -2
  306. package/Tests/App/Dashboard/TemplateFormsNoInternalDataWarning.test.tsx +3 -3
  307. package/Tests/App/Dashboard/TraceExplorer.test.tsx +338 -0
  308. package/Tests/App/Dashboard/TwilioConfigFirstDefault.test.tsx +786 -0
  309. package/Tests/App/Dashboard/UpcomingShiftGetCover.test.tsx +306 -0
  310. package/Tests/App/Dashboard/UseBulkShortenDeviceNames.test.tsx +36 -11
  311. package/Tests/App/Dashboard/UserOverrideForm.test.ts +505 -0
  312. package/Tests/App/Dashboard/UserOverrideFormGuard.test.ts +334 -0
  313. package/Tests/App/Dashboard/UserOverrideWhoIsAway.test.tsx +999 -0
  314. package/Tests/App/Dashboard/UserSettingsSetupChecklistModel.test.ts +33 -11
  315. package/Tests/App/Dashboard/UserSettingsSetupChecklistPage.test.tsx +49 -4
  316. package/Tests/App/Dashboard/UsersPageEmptyState.test.tsx +245 -0
  317. package/Tests/App/Dashboard/WorkflowVariableOAuthSettingsEditSteps.test.tsx +13 -6
  318. package/Tests/App/Dashboard/WorkspaceMenusConnected.test.tsx +0 -2
  319. package/Tests/App/StatusPage/BulkAddStatusPageMonitorsModal.test.tsx +108 -86
  320. package/Tests/App/StatusPage/StatusPageResourceExplorer.test.tsx +397 -0
  321. package/Tests/App/StatusPage/StatusPageResourceForm.test.tsx +931 -0
  322. package/Tests/App/StatusPage/StatusPageSubscribeOnePage.test.tsx +686 -0
  323. package/Tests/DialogActionRules.ts +16 -5
  324. package/Tests/Helpers/CardHeaderScan.ts +258 -0
  325. package/Tests/Helpers/FormStepsScan.ts +576 -11
  326. package/Tests/Helpers/MonitorGroupStatusPageRows.ts +237 -0
  327. package/Tests/Helpers/NestedControls.ts +184 -0
  328. package/Tests/Helpers/NestedControlsScan.ts +414 -0
  329. package/Tests/Helpers/SteppedFooterScan.ts +722 -0
  330. package/Tests/Models/DatabaseModels/FormModel.test.ts +31 -7
  331. package/Tests/Models/DatabaseModels/UserOverrideColumnDescriptions.test.ts +123 -0
  332. package/Tests/Models/TelemetryDataRuleColumnDefaults.test.ts +239 -0
  333. package/Tests/ResponsiveFlexLayout.test.ts +111 -0
  334. package/Tests/ResponsiveFlexLayout.ts +165 -0
  335. package/Tests/Server/API/DashboardDomainOrderSslAPI.test.ts +372 -0
  336. package/Tests/Server/API/DashboardDomainVerifyCnameAPI.test.ts +921 -0
  337. package/Tests/Server/API/DashboardSharingPublicRoutes.test.ts +559 -0
  338. package/Tests/Server/API/FormBrandingPublicRoutes.test.ts +680 -0
  339. package/Tests/Server/API/ProjectAPICreateWithTenantHeader.test.ts +300 -0
  340. package/Tests/Server/API/ProjectCreateTestHelpers.ts +211 -0
  341. package/Tests/Server/API/ProjectCreateWithTenantHeaderPostgres.test.ts +387 -0
  342. package/Tests/Server/API/PublicRouteInvalidAccessToken.test.ts +29 -12
  343. package/Tests/Server/API/StatusPageDomainVerifyCnameAPI.test.ts +1090 -0
  344. package/Tests/Server/API/StatusPageMasterPageAccess.test.ts +254 -0
  345. package/Tests/Server/API/StatusPageReportScheduleWrites.test.ts +829 -0
  346. package/Tests/Server/API/StatusPageUptimeSettingsWrites.test.ts +417 -0
  347. package/Tests/Server/API/StatusPagesListingMonitorsAPI.test.ts +1029 -0
  348. package/Tests/Server/API/StatusPagesListingMonitorsRoute.test.ts +286 -0
  349. package/Tests/Server/Infrastructure/GlobalCache.test.ts +156 -0
  350. package/Tests/Server/Infrastructure/Postgres/MigrateIncidentFormsToFormsMigration.test.ts +52 -1
  351. package/Tests/Server/Services/CustomDomainCertificateLifecycle.test.ts +1559 -0
  352. package/Tests/Server/Services/CustomDomainCertificateReissue.test.ts +186 -16
  353. package/Tests/Server/Services/DashboardAccessChoice.test.ts +657 -0
  354. package/Tests/Server/Services/DashboardCreateFromTemplate.test.ts +200 -0
  355. package/Tests/Server/Services/DashboardDomainCertificateSweeps.test.ts +1142 -0
  356. package/Tests/Server/Services/DiscoveredResourceCreate.test.ts +864 -0
  357. package/Tests/Server/Services/DiscoveredResourceUpdate.test.ts +822 -0
  358. package/Tests/Server/Services/EpisodeRuleSeverityRepair.test.ts +3 -4
  359. package/Tests/Server/Services/FileServiceProject.test.ts +345 -0
  360. package/Tests/Server/Services/FormBranding.test.ts +982 -0
  361. package/Tests/Server/Services/FormPublicForm.test.ts +15 -0
  362. package/Tests/Server/Services/GroupingRuleEpisodeOwners.test.ts +857 -0
  363. package/Tests/Server/Services/IncidentFormPostgres.test.ts +297 -1
  364. package/Tests/Server/Services/MachinePickedColors.test.ts +202 -0
  365. package/Tests/Server/Services/OidcProviderCreateDefaults.test.ts +252 -0
  366. package/Tests/Server/Services/OnCallDutyPolicyScheduleFirstLayer.test.ts +799 -0
  367. package/Tests/Server/Services/OnCallDutyPolicyScheduleFirstLayerPlans.test.ts +172 -0
  368. package/Tests/Server/Services/OnCallDutyPolicyUserOverrideWhoIsAway.test.ts +571 -0
  369. package/Tests/Server/Services/OnCallSetupReminder.test.ts +35 -13
  370. package/Tests/Server/Services/OnCallUndeliverablePageRulesLink.test.ts +217 -0
  371. package/Tests/Server/Services/ProfileAggregationService.test.ts +213 -4
  372. package/Tests/Server/Services/ProjectCallSMSConfigFirstDefault.test.ts +699 -0
  373. package/Tests/Server/Services/ProjectCallSMSConfigFirstDefaultBilling.test.ts +247 -0
  374. package/Tests/Server/Services/SamlProviderCreateDefaults.test.ts +255 -0
  375. package/Tests/Server/Services/ScheduledMaintenanceSubscriberMonitorGroups.test.ts +333 -0
  376. package/Tests/Server/Services/ScrubRuleSaveValidation.test.ts +947 -0
  377. package/Tests/Server/Services/StaticRuleCriteriaRuntimeCoverage.test.ts +17 -4
  378. package/Tests/Server/Services/StatusPageAccessChoice.test.ts +548 -0
  379. package/Tests/Server/Services/StatusPageDomainCertificateSweeps.test.ts +1238 -0
  380. package/Tests/Server/Services/TeamCreateAccessGrant.test.ts +607 -0
  381. package/Tests/Server/Services/TwilioConfigSendersGuard.test.ts +249 -0
  382. package/Tests/Server/Services/VerificationCodesUseProjectTwilioConfig.test.ts +264 -0
  383. package/Tests/Server/Types/Database/Permissions/FormBrandingColumns.test.ts +270 -0
  384. package/Tests/Server/Utils/Greenlock/CertificateCleanup.test.ts +526 -0
  385. package/Tests/Server/Utils/Greenlock/CertificateOrder.test.ts +1062 -0
  386. package/Tests/Server/Utils/Greenlock/CertificateOrderBudget.test.ts +243 -0
  387. package/Tests/Server/Utils/Greenlock/CertificateOrderFailures.test.ts +293 -0
  388. package/Tests/Server/Utils/Greenlock/CertificateOrderLock.test.ts +152 -0
  389. package/Tests/Server/Utils/Greenlock/CertificateOwnersGuard.test.ts +119 -0
  390. package/Tests/Server/Utils/Greenlock/CertificateRenewalOwnership.test.ts +1170 -0
  391. package/Tests/Server/Utils/Greenlock/CertificateRenewalSafety.test.ts +368 -0
  392. package/Tests/Server/Utils/Greenlock/CertificateRenewalSchedule.test.ts +188 -11
  393. package/Tests/Server/Utils/Greenlock/CustomDomainOrders.test.ts +723 -0
  394. package/Tests/Server/Utils/Greenlock/InMemoryRedis.ts +189 -0
  395. package/Tests/Server/Utils/Greenlock/NginxAcmeCertificatesToDisk.test.ts +242 -0
  396. package/Tests/Server/Utils/Greenlock/NginxCustomCertificatesToDisk.test.ts +305 -0
  397. package/Tests/Server/Utils/Greenlock/OrderCertGuards.test.ts +583 -0
  398. package/Tests/Server/Utils/Greenlock/OrderCertStorage.test.ts +203 -0
  399. package/Tests/Server/Utils/StatusPage/AffectedStatusPageResources.test.ts +700 -0
  400. package/Tests/Server/Utils/StatusPage/StatusPagesListingMonitorsBuilder.test.ts +744 -0
  401. package/Tests/Types/CustomDomain/CustomDomainCertificates.test.ts +106 -0
  402. package/Tests/Types/CustomDomain/CustomDomainVerification.test.ts +94 -0
  403. package/Tests/Types/Dashboard/DashboardAccess.test.ts +460 -0
  404. package/Tests/Types/Filter/FilterConditionUtil.test.ts +59 -0
  405. package/Tests/Types/Form/FormBranding.test.ts +760 -0
  406. package/Tests/Types/IncomingCall/IncomingCallRingTime.test.ts +80 -0
  407. package/Tests/Types/NotificationRule/OnCallRuleKind.test.ts +271 -0
  408. package/Tests/Types/OnCallDutyPolicy/ScheduleFirstLayer.test.ts +300 -0
  409. package/Tests/Types/OnCallDutyPolicy/ScheduleLayerDefaults.test.ts +316 -0
  410. package/Tests/Types/OnCallDutyPolicy/UserOverrideCoverRequest.test.ts +257 -0
  411. package/Tests/Types/SSO/OidcProviderDefaults.test.ts +474 -0
  412. package/Tests/Types/SSO/SamlProviderDefaults.test.ts +150 -0
  413. package/Tests/Types/SSO/SsoProviderDefaults.test.ts +181 -0
  414. package/Tests/Types/StatusPage/StatusPageAccess.test.ts +393 -0
  415. package/Tests/Types/StatusPage/StatusPagesListingMonitors.test.ts +149 -0
  416. package/Tests/Types/Telemetry/ScrubRule.test.ts +375 -0
  417. package/Tests/UI/Components/AdvancedPageSection.test.tsx +201 -61
  418. package/Tests/UI/Components/BasicFormModalSteps.test.tsx +129 -153
  419. package/Tests/UI/Components/Card.test.tsx +354 -367
  420. package/Tests/UI/Components/CardHeaderActionsGuard.test.tsx +605 -0
  421. package/Tests/UI/Components/CardModelDetailHeaderLayout.test.tsx +23 -8
  422. package/Tests/UI/Components/CardModelDetailOnBeforeUpdate.test.tsx +224 -0
  423. package/Tests/UI/Components/CardModelDetailPageButtons.test.tsx +231 -0
  424. package/Tests/UI/Components/ChoiceRows.test.tsx +365 -0
  425. package/Tests/UI/Components/CommandPalette/CommandPalette.test.tsx +341 -5
  426. package/Tests/UI/Components/CommandPalette/PaletteFilter.test.ts +655 -64
  427. package/Tests/UI/Components/Detail/DetailCreatedOnIdLine.test.tsx +669 -0
  428. package/Tests/UI/Components/Detail/DetailIdLineDarkMode.test.ts +34 -7
  429. package/Tests/UI/Components/Detail/DetailIdLineInLocale.test.tsx +71 -0
  430. package/Tests/UI/Components/Detail/DetailRecordIdLine.test.tsx +26 -7
  431. package/Tests/UI/Components/Detail/DetailRecordLine.test.tsx +326 -0
  432. package/Tests/UI/Components/Detail/DetailRecordTime.test.ts +324 -0
  433. package/Tests/UI/Components/DetailCompactStyle.test.tsx +7 -3
  434. package/Tests/UI/Components/Dropdown/DropdownChange.test.ts +163 -0
  435. package/Tests/UI/Components/EntityDropdown.test.tsx +103 -1
  436. package/Tests/UI/Components/EntityDropdownClearButton.test.tsx +638 -0
  437. package/Tests/UI/Components/EntityDropdownEscape.test.tsx +6 -6
  438. package/Tests/UI/Components/EntityDropdownFocusLeave.test.tsx +21 -4
  439. package/Tests/UI/Components/EntityDropdownQuery.test.tsx +411 -0
  440. package/Tests/UI/Components/EntityDropdownQueryHelpers.test.ts +131 -0
  441. package/Tests/UI/Components/FilePicker.test.tsx +220 -1
  442. package/Tests/UI/Components/FoldedSection/FoldedSection.test.tsx +582 -0
  443. package/Tests/UI/Components/FoldedSection/FoldedSectionDarkMode.test.ts +197 -0
  444. package/Tests/UI/Components/FoldedSection/FoldedSectionItem.test.ts +247 -0
  445. package/Tests/UI/Components/FoldedSection/FoldedSectionQueries.ts +86 -0
  446. package/Tests/UI/Components/Forms/AdvancedStepsGuard.test.ts +358 -32
  447. package/Tests/UI/Components/Forms/AnchoredFieldPopupPlacement.test.tsx +195 -0
  448. package/Tests/UI/Components/Forms/BasicFormCollapsibleSections.test.tsx +187 -23
  449. package/Tests/UI/Components/Forms/BasicFormFillsInOnShow.test.tsx +246 -0
  450. package/Tests/UI/Components/Forms/BasicFormFooterSetValue.test.tsx +283 -0
  451. package/Tests/UI/Components/Forms/BasicFormPrimaryActionLastStep.test.tsx +780 -0
  452. package/Tests/UI/Components/Forms/BasicFormSingleStep.test.tsx +232 -0
  453. package/Tests/UI/Components/Forms/BasicFormStepValidation.test.tsx +7 -4
  454. package/Tests/UI/Components/Forms/BasicFormSubmitAllSteps.test.tsx +4 -3
  455. package/Tests/UI/Components/Forms/CreateFormDefaultsGuard.test.ts +252 -7
  456. package/Tests/UI/Components/Forms/DeclareAndCreateFormsGuard.test.ts +247 -7
  457. package/Tests/UI/Components/Forms/DiscoveredResourceCreateFormsGuard.test.ts +492 -0
  458. package/Tests/UI/Components/Forms/FilterConditionOnlyWithTwoFiltersGuard.test.ts +333 -0
  459. package/Tests/UI/Components/Forms/FoldedEditorWidthTiming.test.tsx +127 -0
  460. package/Tests/UI/Components/Forms/FoldedSectionsGuard.test.ts +523 -0
  461. package/Tests/UI/Components/Forms/FollowPickName.test.ts +88 -0
  462. package/Tests/UI/Components/Forms/FormFieldDropdownChange.test.tsx +294 -0
  463. package/Tests/UI/Components/Forms/FormModalWidth.test.tsx +237 -2
  464. package/Tests/UI/Components/Forms/FormSummary.test.tsx +181 -0
  465. package/Tests/UI/Components/Forms/IncidentEpisodePublicNoteNotifyPayload.test.tsx +110 -86
  466. package/Tests/UI/Components/Forms/LabelsFormFieldGuard.test.ts +101 -96
  467. package/Tests/UI/Components/Forms/LongFormStepsGuard.test.ts +559 -10
  468. package/Tests/UI/Components/Forms/MarkdownFormsAreWide.test.ts +56 -4
  469. package/Tests/UI/Components/Forms/MetricAndRecordingRuleFormsGuard.test.ts +400 -0
  470. package/Tests/UI/Components/Forms/ModelFormColorDefaults.test.tsx +659 -0
  471. package/Tests/UI/Components/Forms/ModelFormCreateDefaults.test.tsx +108 -4
  472. package/Tests/UI/Components/Forms/ModelFormDropdownQuery.test.tsx +403 -0
  473. package/Tests/UI/Components/Forms/ModelFormPreservesUserInput.test.tsx +126 -0
  474. package/Tests/UI/Components/Forms/ModelFormSinglePickPeoplePicker.test.tsx +453 -0
  475. package/Tests/UI/Components/Forms/OidcProviderFormsGuard.test.ts +321 -0
  476. package/Tests/UI/Components/Forms/OneIncidentRolePickerGuard.test.ts +823 -0
  477. package/Tests/UI/Components/Forms/OneSwitchCardsGuard.test.ts +739 -0
  478. package/Tests/UI/Components/Forms/OpenFormSections.test.tsx +169 -0
  479. package/Tests/UI/Components/Forms/OverloadedFormStepsGuard.test.ts +21 -9
  480. package/Tests/UI/Components/Forms/PrimaryActionLastStepGuard.test.ts +478 -0
  481. package/Tests/UI/Components/Forms/ProjectTeamFormsGuard.test.ts +240 -0
  482. package/Tests/UI/Components/Forms/ResourceDetailsOnePlaceGuard.test.ts +936 -0
  483. package/Tests/UI/Components/Forms/ResourceRuleFormsGuard.test.ts +527 -0
  484. package/Tests/UI/Components/Forms/SamlProviderFormsGuard.test.ts +387 -0
  485. package/Tests/UI/Components/Forms/SmallWizardsOnePageGuard.test.ts +331 -0
  486. package/Tests/UI/Components/Forms/SmtpConfigFormsGuard.test.ts +470 -0
  487. package/Tests/UI/Components/Forms/StateChangeDialogsFoldTheNote.test.ts +322 -0
  488. package/Tests/UI/Components/Forms/StatusPageResourceFormsGuard.test.ts +381 -0
  489. package/Tests/UI/Components/Forms/SteppedFormFooter.test.ts +225 -0
  490. package/Tests/UI/Components/Forms/SteppedFormFooterTiming.test.tsx +82 -40
  491. package/Tests/UI/Components/Forms/SwitchCardsGuard.test.ts +339 -0
  492. package/Tests/UI/Components/Forms/TelemetryDataRuleFormsGuard.test.ts +383 -0
  493. package/Tests/UI/Components/Forms/UniqueName.test.ts +163 -0
  494. package/Tests/UI/Components/Forms/Utils/AdvancedFormSection.test.ts +131 -11
  495. package/Tests/UI/Components/Forms/Utils/CreateFormColorDefaults.test.ts +373 -0
  496. package/Tests/UI/Components/Forms/Utils/FoldedFormFields.test.ts +482 -0
  497. package/Tests/UI/Components/LabelRuleImportExport.test.tsx +32 -0
  498. package/Tests/UI/Components/MemberRoleAssignment.test.tsx +51 -7
  499. package/Tests/UI/Components/ModalSecondaryButton.test.tsx +60 -3
  500. package/Tests/UI/Components/ModelFormDraftValues.test.tsx +255 -0
  501. package/Tests/UI/Components/ModelPageHeaderChanged.test.tsx +293 -0
  502. package/Tests/UI/Components/ModelSwitchCard.test.tsx +813 -0
  503. package/Tests/UI/Components/ModelSwitchEvents.test.ts +188 -0
  504. package/Tests/UI/Components/ModelSwitchRow.test.tsx +861 -0
  505. package/Tests/UI/Components/ModelSwitchUtil.test.ts +239 -0
  506. package/Tests/UI/Components/ModelSwitchesCard.test.tsx +699 -0
  507. package/Tests/UI/Components/NavBar.test.tsx +41 -2
  508. package/Tests/UI/Components/NavBarCategoryToggleDarkMode.test.ts +12 -0
  509. package/Tests/UI/Components/NavBarMenuCatalog.test.tsx +197 -30
  510. package/Tests/UI/Components/NavBarMenuFolding.test.tsx +221 -39
  511. package/Tests/UI/Components/NavBarMenuSearch.test.tsx +15 -10
  512. package/Tests/UI/Components/NestedControlsGuard.test.ts +300 -0
  513. package/Tests/UI/Components/PageBreadcrumbOverride.test.tsx +202 -0
  514. package/Tests/UI/Components/PeoplePicker/OwnersFieldGuard.test.ts +269 -5
  515. package/Tests/UI/Components/PeoplePicker/PeoplePickerChange.test.tsx +414 -0
  516. package/Tests/UI/Components/PeoplePicker/PeoplePickerExcludePicks.test.tsx +582 -0
  517. package/Tests/UI/Components/PeoplePicker/PeoplePickerSinglePick.test.tsx +519 -0
  518. package/Tests/UI/Components/PeoplePicker/PeoplePickerSinglePickTypes.test.ts +174 -0
  519. package/Tests/UI/Components/RuleCriteria/RuleCriteriaModelTable.test.tsx +8 -3
  520. package/Tests/UI/Components/RuleCriteria/RuleCriteriaNamesGuard.test.ts +14 -6
  521. package/Tests/UI/Components/RuleRun/RuleViewDescription.test.tsx +107 -0
  522. package/Tests/UI/Components/SaveStatus.test.tsx +81 -0
  523. package/Tests/UI/Components/SmtpConfig/SmtpConfigFormFields.test.ts +1078 -0
  524. package/Tests/UI/Components/Sso/OidcProviderForm.test.tsx +608 -0
  525. package/Tests/UI/Components/Sso/OidcProviderFormFields.test.ts +698 -0
  526. package/Tests/UI/Components/Sso/SamlProviderForm.test.tsx +583 -0
  527. package/Tests/UI/Components/Sso/SamlProviderFormFields.test.ts +619 -0
  528. package/Tests/UI/Components/SteppedEditFormSave.test.tsx +199 -141
  529. package/Tests/UI/Components/Table/TableEmptyStateComponent.test.tsx +143 -0
  530. package/Tests/UI/Components/UseSaveModelSwitch.test.tsx +409 -0
  531. package/Tests/UI/Components/Workflow/ArgumentsForm.test.tsx +3 -3
  532. package/Tests/UI/Components/Workflow/ArgumentsFormAdvancedSettings.test.tsx +83 -27
  533. package/Tests/UI/Components/Workflow/WorkflowLogModal.test.tsx +7 -2
  534. package/Tests/UI/Components/useSaveOnChange.test.tsx +341 -0
  535. package/Tests/UI/Monitor/MonitorStepRetrySettings.test.tsx +1 -1
  536. package/Tests/UI/Rum/ReplayTimeline.test.tsx +75 -5
  537. package/Tests/UI/Rum/SessionReplaySettingsPolicyLoading.test.tsx +97 -0
  538. package/Tests/UI/Utils/Breadcrumb/fixtures/RealBreadcrumbTrails.ts +12 -39
  539. package/Tests/UI/Utils/Breadcrumb/fixtures/RealRoutePatterns.ts +1 -6
  540. package/Tests/{App/Dashboard → UI/Utils}/DefaultInviteTeam.test.ts +126 -4
  541. package/Tests/UI/Utils/GrantablePermission.test.ts +172 -0
  542. package/Tests/UI/Utils/PermissionGateColumnUpdate.test.ts +211 -0
  543. package/Tests/UI/Utils/TranslateTemplate.test.ts +31 -0
  544. package/Tests/Utils/DistinctColor.test.ts +446 -0
  545. package/Tests/Utils/Monitor/MonitorOverviewPresentationUtil.test.ts +14 -2
  546. package/Tests/Utils/Monitor/MonitoringIntervalUtil.test.ts +1 -1
  547. package/Tests/Utils/NetworkDiscovery/ShortHostnameUtil.test.ts +2 -2
  548. package/Tests/Utils/Slo/SloHealth.test.ts +28 -6
  549. package/Tests/Utils/StatusPage/ReportSchedule.test.ts +867 -0
  550. package/Tests/Utils/Telemetry/DiscoveredResourceName.test.ts +189 -0
  551. package/Tests/Utils/WordMatch.test.ts +104 -0
  552. package/Types/BrandColors.ts +10 -0
  553. package/Types/CustomDomain/CustomDomainCertificates.ts +128 -0
  554. package/Types/CustomDomain/CustomDomainVerification.ts +78 -0
  555. package/Types/Dashboard/DashboardAccess.ts +250 -0
  556. package/Types/Dashboard/DashboardTemplates.ts +33 -0
  557. package/Types/Filter/FilterConditionUtil.ts +29 -0
  558. package/Types/Form/FormBranding.ts +475 -0
  559. package/Types/Form/FormPublic.ts +26 -0
  560. package/Types/IncomingCall/IncomingCallRingTime.ts +48 -0
  561. package/Types/Log/LogScrubField.ts +14 -0
  562. package/Types/Monitor/SeriesContext/SeriesLabelDisplay.ts +1 -1
  563. package/Types/NotificationRule/OnCallRuleKind.ts +194 -0
  564. package/Types/OnCallDutyPolicy/FirstResponders.ts +3 -32
  565. package/Types/OnCallDutyPolicy/MiscDataId.ts +38 -0
  566. package/Types/OnCallDutyPolicy/ScheduleFirstLayer.ts +165 -0
  567. package/Types/OnCallDutyPolicy/ScheduleLayerDefaults.ts +160 -0
  568. package/Types/OnCallDutyPolicy/UserOverrideCoverRequest.ts +148 -0
  569. package/Types/SSO/OidcProviderDefaults.ts +282 -0
  570. package/Types/SSO/SamlProviderDefaults.ts +77 -0
  571. package/Types/SSO/SsoProviderDefaults.ts +101 -0
  572. package/Types/StatusPage/StatusPageAccess.ts +215 -0
  573. package/Types/StatusPage/StatusPagesListingMonitors.ts +150 -0
  574. package/Types/Telemetry/ScrubRule.ts +240 -0
  575. package/UI/Components/AdvancedPageSection/AdvancedPageSection.tsx +59 -29
  576. package/UI/Components/Card/Card.tsx +139 -80
  577. package/UI/Components/ChoiceRows/ChoiceRows.tsx +249 -0
  578. package/UI/Components/CommandPalette/CommandPalette.tsx +28 -12
  579. package/UI/Components/CommandPalette/PaletteFilter.ts +962 -75
  580. package/UI/Components/CommandPalette/PaletteRow.tsx +35 -3
  581. package/UI/Components/CommandPalette/Types.ts +37 -1
  582. package/UI/Components/CustomFields/CustomFieldsDetail.tsx +6 -0
  583. package/UI/Components/Detail/Detail.tsx +52 -15
  584. package/UI/Components/Detail/DetailRecordLine.tsx +96 -0
  585. package/UI/Components/Detail/DetailRecordTime.ts +196 -0
  586. package/UI/Components/Dropdown/DropdownChange.ts +198 -0
  587. package/UI/Components/EntityDropdown/EntityDropdown.tsx +270 -106
  588. package/UI/Components/EntityDropdown/EntityDropdownQuery.ts +119 -0
  589. package/UI/Components/FilePicker/FilePicker.tsx +90 -6
  590. package/UI/Components/FoldedSection/FoldedSection.tsx +370 -0
  591. package/UI/Components/FoldedSection/FoldedSectionItem.ts +141 -0
  592. package/UI/Components/FoldedSection/FoldedSectionTitles.ts +39 -0
  593. package/UI/Components/FormModal/BasicFormModal.tsx +40 -67
  594. package/UI/Components/Forms/BasicForm.tsx +218 -119
  595. package/UI/Components/Forms/BasicModelForm.tsx +0 -2
  596. package/UI/Components/Forms/CollapsibleFormSection.tsx +108 -18
  597. package/UI/Components/Forms/Fields/FormField.tsx +62 -15
  598. package/UI/Components/Forms/Fields/GeneratedKeyField.tsx +1 -5
  599. package/UI/Components/Forms/FormSummary.tsx +5 -3
  600. package/UI/Components/Forms/ModelForm.tsx +123 -16
  601. package/UI/Components/Forms/Types/Field.ts +98 -16
  602. package/UI/Components/Forms/Utils/AdvancedFormSection.ts +102 -31
  603. package/UI/Components/Forms/Utils/CreateFormDefaults.ts +158 -17
  604. package/UI/Components/Forms/Utils/FoldedFormFields.ts +393 -0
  605. package/UI/Components/Forms/Utils/FollowPickName.ts +52 -0
  606. package/UI/Components/Forms/Utils/FormModalWidth.ts +35 -4
  607. package/UI/Components/Forms/Utils/OpenFormSections.ts +104 -0
  608. package/UI/Components/Forms/Utils/SteppedFormFooter.ts +151 -0
  609. package/UI/Components/Forms/Utils/UniqueName.ts +81 -0
  610. package/UI/Components/LabelRule/ImportLabelRulesModal.tsx +22 -3
  611. package/UI/Components/MemberRoleAssignment/MemberRoleAssignment.tsx +6 -5
  612. package/UI/Components/Modal/ModalFooter.tsx +10 -1
  613. package/UI/Components/ModelDetail/CardModelDetail.tsx +33 -8
  614. package/UI/Components/ModelFormModal/ModelFormModal.tsx +44 -58
  615. package/UI/Components/ModelSwitch/ModelSwitchCard.tsx +305 -0
  616. package/UI/Components/ModelSwitch/ModelSwitchEvents.ts +109 -0
  617. package/UI/Components/ModelSwitch/ModelSwitchRow.tsx +438 -0
  618. package/UI/Components/ModelSwitch/ModelSwitchUtil.ts +120 -0
  619. package/UI/Components/ModelSwitch/ModelSwitchesCard.tsx +265 -0
  620. package/UI/Components/ModelSwitch/useSaveModelSwitch.ts +144 -0
  621. package/UI/Components/ModelTable/BaseModelTable.tsx +6 -0
  622. package/UI/Components/ModelTable/ModelTable.tsx +5 -0
  623. package/UI/Components/Navbar/NavBar.tsx +10 -9
  624. package/UI/Components/Navbar/NavBarMenuCatalog.ts +57 -22
  625. package/UI/Components/Navbar/NavBarMenuModal.tsx +17 -13
  626. package/UI/Components/Navbar/NavBarMobileMenu.tsx +40 -23
  627. package/UI/Components/Page/ModelHeaderEvents.ts +75 -0
  628. package/UI/Components/Page/ModelPage.tsx +21 -1
  629. package/UI/Components/Page/Page.tsx +68 -13
  630. package/UI/Components/PeoplePicker/PeoplePicker.tsx +150 -45
  631. package/UI/Components/PeoplePicker/PeoplePickerTypes.ts +193 -4
  632. package/UI/Components/PeoplePicker/PeopleSearchPopup.tsx +37 -10
  633. package/UI/Components/PublicForm/PublicFormFavicon.ts +99 -0
  634. package/UI/Components/PublicForm/PublicFormLogo.tsx +81 -0
  635. package/UI/Components/RuleCriteria/RuleCriteriaBuilder.tsx +5 -3
  636. package/UI/Components/RuleRun/RuleView.tsx +2 -5
  637. package/UI/Components/SaveStatus/SaveStatus.tsx +76 -0
  638. package/UI/Components/SaveStatus/useSaveOnChange.ts +178 -0
  639. package/UI/Components/SmtpConfig/SmtpConfigFormFields.ts +815 -0
  640. package/UI/Components/Sso/OidcProviderFormFields.ts +481 -0
  641. package/UI/Components/Sso/SamlProviderFormFields.ts +322 -0
  642. package/UI/Components/Sso/SsoProviderFormFields.ts +331 -0
  643. package/UI/Components/Table/TableEmptyState.tsx +39 -5
  644. package/UI/Components/Workflow/ArgumentsForm.tsx +21 -78
  645. package/UI/Components/Workflow/ComponentPicker/ComponentSearch.ts +18 -145
  646. package/UI/Components/Workflow/WorkflowLogModal.tsx +1 -3
  647. package/UI/Types/UseAnchoredFieldPopup.ts +46 -4
  648. package/UI/Utils/DefaultInviteTeam.ts +306 -0
  649. package/UI/Utils/GrantablePermission.ts +46 -0
  650. package/UI/Utils/PermissionGate.ts +80 -7
  651. package/UI/Utils/TranslateTemplate.ts +22 -0
  652. package/Utils/DistinctColor.ts +223 -0
  653. package/Utils/Monitor/MonitorOverviewPresentationUtil.ts +47 -2
  654. package/Utils/Slo/SloHealth.ts +9 -3
  655. package/Utils/StatusPage/ReportSchedule.ts +462 -0
  656. package/Utils/Telemetry/DiscoveredResourceName.ts +95 -0
  657. package/Utils/WordMatch.ts +185 -0
  658. package/build/dist/Models/DatabaseModels/AlertGroupingRule.js +6 -6
  659. package/build/dist/Models/DatabaseModels/AlertGroupingRule.js.map +1 -1
  660. package/build/dist/Models/DatabaseModels/File.js +47 -1
  661. package/build/dist/Models/DatabaseModels/File.js.map +1 -1
  662. package/build/dist/Models/DatabaseModels/Form.js +125 -0
  663. package/build/dist/Models/DatabaseModels/Form.js.map +1 -1
  664. package/build/dist/Models/DatabaseModels/IncidentGroupingRule.js +6 -6
  665. package/build/dist/Models/DatabaseModels/IncidentGroupingRule.js.map +1 -1
  666. package/build/dist/Models/DatabaseModels/LogDropFilter.js +1 -0
  667. package/build/dist/Models/DatabaseModels/LogDropFilter.js.map +1 -1
  668. package/build/dist/Models/DatabaseModels/LogPipeline.js +1 -0
  669. package/build/dist/Models/DatabaseModels/LogPipeline.js.map +1 -1
  670. package/build/dist/Models/DatabaseModels/LogScrubRule.js +14 -8
  671. package/build/dist/Models/DatabaseModels/LogScrubRule.js.map +1 -1
  672. package/build/dist/Models/DatabaseModels/MetricRecordingRule.js +1 -1
  673. package/build/dist/Models/DatabaseModels/MetricRecordingRule.js.map +1 -1
  674. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyExecutionLogTimeline.js +2 -2
  675. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyExecutionLogTimeline.js.map +1 -1
  676. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyUserOverride.js +20 -9
  677. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyUserOverride.js.map +1 -1
  678. package/build/dist/Models/DatabaseModels/ProjectCallSMSConfig.js +6 -1
  679. package/build/dist/Models/DatabaseModels/ProjectCallSMSConfig.js.map +1 -1
  680. package/build/dist/Models/DatabaseModels/StatusPage.js +4 -4
  681. package/build/dist/Models/DatabaseModels/StatusPage.js.map +1 -1
  682. package/build/dist/Models/DatabaseModels/TraceDropFilter.js +1 -0
  683. package/build/dist/Models/DatabaseModels/TraceDropFilter.js.map +1 -1
  684. package/build/dist/Models/DatabaseModels/TracePipeline.js +1 -0
  685. package/build/dist/Models/DatabaseModels/TracePipeline.js.map +1 -1
  686. package/build/dist/Models/DatabaseModels/TraceRecordingRule.js +1 -1
  687. package/build/dist/Models/DatabaseModels/TraceRecordingRule.js.map +1 -1
  688. package/build/dist/Models/DatabaseModels/TraceScrubRule.js +14 -8
  689. package/build/dist/Models/DatabaseModels/TraceScrubRule.js.map +1 -1
  690. package/build/dist/Models/DatabaseModels/UserOnCallLog.js +2 -2
  691. package/build/dist/Models/DatabaseModels/UserOnCallLog.js.map +1 -1
  692. package/build/dist/Server/API/CustomDomainRoutes.js +124 -0
  693. package/build/dist/Server/API/CustomDomainRoutes.js.map +1 -0
  694. package/build/dist/Server/API/DashboardAPI.js +22 -4
  695. package/build/dist/Server/API/DashboardAPI.js.map +1 -1
  696. package/build/dist/Server/API/DashboardDomainAPI.js +56 -49
  697. package/build/dist/Server/API/DashboardDomainAPI.js.map +1 -1
  698. package/build/dist/Server/API/StatusPageAPI.js +101 -39
  699. package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
  700. package/build/dist/Server/API/StatusPageDomainAPI.js +52 -51
  701. package/build/dist/Server/API/StatusPageDomainAPI.js.map +1 -1
  702. package/build/dist/Server/Infrastructure/GlobalCache.js +63 -0
  703. package/build/dist/Server/Infrastructure/GlobalCache.js.map +1 -1
  704. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1797700000000-AddFormBranding.js +36 -0
  705. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1797700000000-AddFormBranding.js.map +1 -0
  706. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +2 -0
  707. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  708. package/build/dist/Server/Services/AlertGroupingEngineService.js +44 -61
  709. package/build/dist/Server/Services/AlertGroupingEngineService.js.map +1 -1
  710. package/build/dist/Server/Services/AlertGroupingRuleService.js +45 -0
  711. package/build/dist/Server/Services/AlertGroupingRuleService.js.map +1 -1
  712. package/build/dist/Server/Services/CephClusterService.js +23 -0
  713. package/build/dist/Server/Services/CephClusterService.js.map +1 -1
  714. package/build/dist/Server/Services/CloudResourceService.js +39 -0
  715. package/build/dist/Server/Services/CloudResourceService.js.map +1 -1
  716. package/build/dist/Server/Services/DashboardDomainService.js +510 -172
  717. package/build/dist/Server/Services/DashboardDomainService.js.map +1 -1
  718. package/build/dist/Server/Services/DashboardService.js +22 -9
  719. package/build/dist/Server/Services/DashboardService.js.map +1 -1
  720. package/build/dist/Server/Services/DatabaseService.js +114 -10
  721. package/build/dist/Server/Services/DatabaseService.js.map +1 -1
  722. package/build/dist/Server/Services/DockerHostService.js +51 -0
  723. package/build/dist/Server/Services/DockerHostService.js.map +1 -1
  724. package/build/dist/Server/Services/DockerSwarmClusterService.js +23 -0
  725. package/build/dist/Server/Services/DockerSwarmClusterService.js.map +1 -1
  726. package/build/dist/Server/Services/FileService.js +45 -0
  727. package/build/dist/Server/Services/FileService.js.map +1 -1
  728. package/build/dist/Server/Services/FormService.js +158 -14
  729. package/build/dist/Server/Services/FormService.js.map +1 -1
  730. package/build/dist/Server/Services/GlobalOidcService.js +9 -0
  731. package/build/dist/Server/Services/GlobalOidcService.js.map +1 -1
  732. package/build/dist/Server/Services/GlobalSsoService.js +9 -0
  733. package/build/dist/Server/Services/GlobalSsoService.js.map +1 -1
  734. package/build/dist/Server/Services/HostService.js +50 -0
  735. package/build/dist/Server/Services/HostService.js.map +1 -1
  736. package/build/dist/Server/Services/IncidentGroupingEngineService.js +45 -62
  737. package/build/dist/Server/Services/IncidentGroupingEngineService.js.map +1 -1
  738. package/build/dist/Server/Services/IncidentGroupingRuleService.js +45 -0
  739. package/build/dist/Server/Services/IncidentGroupingRuleService.js.map +1 -1
  740. package/build/dist/Server/Services/IoTFleetService.js +23 -0
  741. package/build/dist/Server/Services/IoTFleetService.js.map +1 -1
  742. package/build/dist/Server/Services/KubernetesClusterService.js +54 -0
  743. package/build/dist/Server/Services/KubernetesClusterService.js.map +1 -1
  744. package/build/dist/Server/Services/LabelService.js +6 -16
  745. package/build/dist/Server/Services/LabelService.js.map +1 -1
  746. package/build/dist/Server/Services/LogScrubRuleService.js +31 -0
  747. package/build/dist/Server/Services/LogScrubRuleService.js.map +1 -1
  748. package/build/dist/Server/Services/OnCallDutyPolicyScheduleService.js +202 -0
  749. package/build/dist/Server/Services/OnCallDutyPolicyScheduleService.js.map +1 -1
  750. package/build/dist/Server/Services/OnCallDutyPolicyUserOverrideService.js +7 -1
  751. package/build/dist/Server/Services/OnCallDutyPolicyUserOverrideService.js.map +1 -1
  752. package/build/dist/Server/Services/OnCallNotificationAlertingService.js +17 -24
  753. package/build/dist/Server/Services/OnCallNotificationAlertingService.js.map +1 -1
  754. package/build/dist/Server/Services/OnCallSetupReminderService.js +32 -38
  755. package/build/dist/Server/Services/OnCallSetupReminderService.js.map +1 -1
  756. package/build/dist/Server/Services/PodmanHostService.js +51 -0
  757. package/build/dist/Server/Services/PodmanHostService.js.map +1 -1
  758. package/build/dist/Server/Services/ProfileAggregationService.js +34 -0
  759. package/build/dist/Server/Services/ProfileAggregationService.js.map +1 -1
  760. package/build/dist/Server/Services/ProjectCallSMSConfigService.js +53 -3
  761. package/build/dist/Server/Services/ProjectCallSMSConfigService.js.map +1 -1
  762. package/build/dist/Server/Services/ProjectOidcService.js +28 -0
  763. package/build/dist/Server/Services/ProjectOidcService.js.map +1 -1
  764. package/build/dist/Server/Services/ProjectSsoService.js +28 -0
  765. package/build/dist/Server/Services/ProjectSsoService.js.map +1 -1
  766. package/build/dist/Server/Services/ProxmoxClusterService.js +23 -0
  767. package/build/dist/Server/Services/ProxmoxClusterService.js.map +1 -1
  768. package/build/dist/Server/Services/RumApplicationService.js +42 -0
  769. package/build/dist/Server/Services/RumApplicationService.js.map +1 -1
  770. package/build/dist/Server/Services/ScheduledMaintenanceService.js +18 -26
  771. package/build/dist/Server/Services/ScheduledMaintenanceService.js.map +1 -1
  772. package/build/dist/Server/Services/ServerlessFunctionService.js +44 -0
  773. package/build/dist/Server/Services/ServerlessFunctionService.js.map +1 -1
  774. package/build/dist/Server/Services/ServiceService.js +30 -4
  775. package/build/dist/Server/Services/ServiceService.js.map +1 -1
  776. package/build/dist/Server/Services/StatusPageDomainService.js +495 -174
  777. package/build/dist/Server/Services/StatusPageDomainService.js.map +1 -1
  778. package/build/dist/Server/Services/StatusPageOidcService.js +28 -0
  779. package/build/dist/Server/Services/StatusPageOidcService.js.map +1 -1
  780. package/build/dist/Server/Services/StatusPageResourceService.js +82 -46
  781. package/build/dist/Server/Services/StatusPageResourceService.js.map +1 -1
  782. package/build/dist/Server/Services/StatusPageService.js +174 -45
  783. package/build/dist/Server/Services/StatusPageService.js.map +1 -1
  784. package/build/dist/Server/Services/StatusPageSsoService.js +28 -0
  785. package/build/dist/Server/Services/StatusPageSsoService.js.map +1 -1
  786. package/build/dist/Server/Services/TraceScrubRuleService.js +31 -0
  787. package/build/dist/Server/Services/TraceScrubRuleService.js.map +1 -1
  788. package/build/dist/Server/Services/UserIncomingCallNumberService.js +20 -10
  789. package/build/dist/Server/Services/UserIncomingCallNumberService.js.map +1 -1
  790. package/build/dist/Server/Services/VMwareVCenterService.js +23 -0
  791. package/build/dist/Server/Services/VMwareVCenterService.js.map +1 -1
  792. package/build/dist/Server/Utils/AI/PerfEvidence/SpanTreeAnalyzer.js +3 -3
  793. package/build/dist/Server/Utils/AI/PerfEvidence/SpanTreeAnalyzer.js.map +1 -1
  794. package/build/dist/Server/Utils/Greenlock/CertificateOrder.js +353 -0
  795. package/build/dist/Server/Utils/Greenlock/CertificateOrder.js.map +1 -0
  796. package/build/dist/Server/Utils/Greenlock/CertificateOrderBudget.js +119 -0
  797. package/build/dist/Server/Utils/Greenlock/CertificateOrderBudget.js.map +1 -0
  798. package/build/dist/Server/Utils/Greenlock/CertificateOrderFailures.js +208 -0
  799. package/build/dist/Server/Utils/Greenlock/CertificateOrderFailures.js.map +1 -0
  800. package/build/dist/Server/Utils/Greenlock/CertificateOrderLock.js +111 -0
  801. package/build/dist/Server/Utils/Greenlock/CertificateOrderLock.js.map +1 -0
  802. package/build/dist/Server/Utils/Greenlock/CertificateOrderOutcome.js +30 -0
  803. package/build/dist/Server/Utils/Greenlock/CertificateOrderOutcome.js.map +1 -0
  804. package/build/dist/Server/Utils/Greenlock/CertificateOwners.js +52 -0
  805. package/build/dist/Server/Utils/Greenlock/CertificateOwners.js.map +1 -0
  806. package/build/dist/Server/Utils/Greenlock/CertificateReissueOrder.js +80 -0
  807. package/build/dist/Server/Utils/Greenlock/CertificateReissueOrder.js.map +1 -0
  808. package/build/dist/Server/Utils/Greenlock/CustomDomainOrders.js +296 -0
  809. package/build/dist/Server/Utils/Greenlock/CustomDomainOrders.js.map +1 -0
  810. package/build/dist/Server/Utils/Greenlock/Greenlock.js +595 -146
  811. package/build/dist/Server/Utils/Greenlock/Greenlock.js.map +1 -1
  812. package/build/dist/Server/Utils/Rules/GroupingRuleEpisodeOwners.js +366 -0
  813. package/build/dist/Server/Utils/Rules/GroupingRuleEpisodeOwners.js.map +1 -0
  814. package/build/dist/Server/Utils/Rules/OwnerRuleAssignment.js +7 -0
  815. package/build/dist/Server/Utils/Rules/OwnerRuleAssignment.js.map +1 -1
  816. package/build/dist/Server/Utils/ScrubRuleValidation.js +127 -0
  817. package/build/dist/Server/Utils/ScrubRuleValidation.js.map +1 -0
  818. package/build/dist/Server/Utils/StatusPage/AffectedStatusPageResources.js +97 -0
  819. package/build/dist/Server/Utils/StatusPage/AffectedStatusPageResources.js.map +1 -0
  820. package/build/dist/Server/Utils/StatusPage/StatusPagesListingMonitorsBuilder.js +195 -0
  821. package/build/dist/Server/Utils/StatusPage/StatusPagesListingMonitorsBuilder.js.map +1 -0
  822. package/build/dist/Server/Utils/Telemetry/DiscoveredResourceCreate.js +177 -0
  823. package/build/dist/Server/Utils/Telemetry/DiscoveredResourceCreate.js.map +1 -0
  824. package/build/dist/Server/Utils/Telemetry/DiscoveredResourceUpdate.js +129 -0
  825. package/build/dist/Server/Utils/Telemetry/DiscoveredResourceUpdate.js.map +1 -0
  826. package/build/dist/Server/Utils/Telemetry/MessageQueueDiscovery.js +8 -2
  827. package/build/dist/Server/Utils/Telemetry/MessageQueueDiscovery.js.map +1 -1
  828. package/build/dist/Types/BrandColors.js +9 -0
  829. package/build/dist/Types/BrandColors.js.map +1 -1
  830. package/build/dist/Types/CustomDomain/CustomDomainCertificates.js +71 -0
  831. package/build/dist/Types/CustomDomain/CustomDomainCertificates.js.map +1 -0
  832. package/build/dist/Types/CustomDomain/CustomDomainVerification.js +58 -0
  833. package/build/dist/Types/CustomDomain/CustomDomainVerification.js.map +1 -0
  834. package/build/dist/Types/Dashboard/DashboardAccess.js +157 -0
  835. package/build/dist/Types/Dashboard/DashboardAccess.js.map +1 -0
  836. package/build/dist/Types/Dashboard/DashboardTemplates.js +26 -0
  837. package/build/dist/Types/Dashboard/DashboardTemplates.js.map +1 -1
  838. package/build/dist/Types/Filter/FilterConditionUtil.js +24 -0
  839. package/build/dist/Types/Filter/FilterConditionUtil.js.map +1 -0
  840. package/build/dist/Types/Form/FormBranding.js +266 -0
  841. package/build/dist/Types/Form/FormBranding.js.map +1 -0
  842. package/build/dist/Types/Form/FormPublic.js +6 -0
  843. package/build/dist/Types/Form/FormPublic.js.map +1 -1
  844. package/build/dist/Types/IncomingCall/IncomingCallRingTime.js +37 -0
  845. package/build/dist/Types/IncomingCall/IncomingCallRingTime.js.map +1 -0
  846. package/build/dist/Types/Log/LogScrubField.js +15 -0
  847. package/build/dist/Types/Log/LogScrubField.js.map +1 -0
  848. package/build/dist/Types/Monitor/SeriesContext/SeriesLabelDisplay.js +1 -1
  849. package/build/dist/Types/Monitor/SeriesContext/SeriesLabelDisplay.js.map +1 -1
  850. package/build/dist/Types/NotificationRule/OnCallRuleKind.js +134 -0
  851. package/build/dist/Types/NotificationRule/OnCallRuleKind.js.map +1 -0
  852. package/build/dist/Types/OnCallDutyPolicy/FirstResponders.js +2 -23
  853. package/build/dist/Types/OnCallDutyPolicy/FirstResponders.js.map +1 -1
  854. package/build/dist/Types/OnCallDutyPolicy/MiscDataId.js +30 -0
  855. package/build/dist/Types/OnCallDutyPolicy/MiscDataId.js.map +1 -0
  856. package/build/dist/Types/OnCallDutyPolicy/ScheduleFirstLayer.js +113 -0
  857. package/build/dist/Types/OnCallDutyPolicy/ScheduleFirstLayer.js.map +1 -0
  858. package/build/dist/Types/OnCallDutyPolicy/ScheduleLayerDefaults.js +95 -0
  859. package/build/dist/Types/OnCallDutyPolicy/ScheduleLayerDefaults.js.map +1 -0
  860. package/build/dist/Types/OnCallDutyPolicy/UserOverrideCoverRequest.js +99 -0
  861. package/build/dist/Types/OnCallDutyPolicy/UserOverrideCoverRequest.js.map +1 -0
  862. package/build/dist/Types/SSO/OidcProviderDefaults.js +189 -0
  863. package/build/dist/Types/SSO/OidcProviderDefaults.js.map +1 -0
  864. package/build/dist/Types/SSO/SamlProviderDefaults.js +55 -0
  865. package/build/dist/Types/SSO/SamlProviderDefaults.js.map +1 -0
  866. package/build/dist/Types/SSO/SsoProviderDefaults.js +67 -0
  867. package/build/dist/Types/SSO/SsoProviderDefaults.js.map +1 -0
  868. package/build/dist/Types/StatusPage/StatusPageAccess.js +132 -0
  869. package/build/dist/Types/StatusPage/StatusPageAccess.js.map +1 -0
  870. package/build/dist/Types/StatusPage/StatusPagesListingMonitors.js +84 -0
  871. package/build/dist/Types/StatusPage/StatusPagesListingMonitors.js.map +1 -0
  872. package/build/dist/Types/Telemetry/ScrubRule.js +180 -0
  873. package/build/dist/Types/Telemetry/ScrubRule.js.map +1 -0
  874. package/build/dist/UI/Components/AdvancedPageSection/AdvancedPageSection.js +9 -7
  875. package/build/dist/UI/Components/AdvancedPageSection/AdvancedPageSection.js.map +1 -1
  876. package/build/dist/UI/Components/Card/Card.js +81 -35
  877. package/build/dist/UI/Components/Card/Card.js.map +1 -1
  878. package/build/dist/UI/Components/ChoiceRows/ChoiceRows.js +54 -0
  879. package/build/dist/UI/Components/ChoiceRows/ChoiceRows.js.map +1 -0
  880. package/build/dist/UI/Components/CommandPalette/CommandPalette.js +25 -12
  881. package/build/dist/UI/Components/CommandPalette/CommandPalette.js.map +1 -1
  882. package/build/dist/UI/Components/CommandPalette/PaletteFilter.js +632 -64
  883. package/build/dist/UI/Components/CommandPalette/PaletteFilter.js.map +1 -1
  884. package/build/dist/UI/Components/CommandPalette/PaletteRow.js +7 -1
  885. package/build/dist/UI/Components/CommandPalette/PaletteRow.js.map +1 -1
  886. package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js +7 -1
  887. package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js.map +1 -1
  888. package/build/dist/UI/Components/Detail/Detail.js +26 -13
  889. package/build/dist/UI/Components/Detail/Detail.js.map +1 -1
  890. package/build/dist/UI/Components/Detail/DetailRecordLine.js +32 -0
  891. package/build/dist/UI/Components/Detail/DetailRecordLine.js.map +1 -0
  892. package/build/dist/UI/Components/Detail/DetailRecordTime.js +134 -0
  893. package/build/dist/UI/Components/Detail/DetailRecordTime.js.map +1 -0
  894. package/build/dist/UI/Components/Dropdown/DropdownChange.js +102 -0
  895. package/build/dist/UI/Components/Dropdown/DropdownChange.js.map +1 -0
  896. package/build/dist/UI/Components/EntityDropdown/EntityDropdown.js +134 -57
  897. package/build/dist/UI/Components/EntityDropdown/EntityDropdown.js.map +1 -1
  898. package/build/dist/UI/Components/EntityDropdown/EntityDropdownQuery.js +78 -0
  899. package/build/dist/UI/Components/EntityDropdown/EntityDropdownQuery.js.map +1 -0
  900. package/build/dist/UI/Components/FilePicker/FilePicker.js +55 -6
  901. package/build/dist/UI/Components/FilePicker/FilePicker.js.map +1 -1
  902. package/build/dist/UI/Components/FoldedSection/FoldedSection.js +139 -0
  903. package/build/dist/UI/Components/FoldedSection/FoldedSection.js.map +1 -0
  904. package/build/dist/UI/Components/FoldedSection/FoldedSectionItem.js +58 -0
  905. package/build/dist/UI/Components/FoldedSection/FoldedSectionItem.js.map +1 -0
  906. package/build/dist/UI/Components/FoldedSection/FoldedSectionTitles.js +34 -0
  907. package/build/dist/UI/Components/FoldedSection/FoldedSectionTitles.js.map +1 -0
  908. package/build/dist/UI/Components/FormModal/BasicFormModal.js +29 -45
  909. package/build/dist/UI/Components/FormModal/BasicFormModal.js.map +1 -1
  910. package/build/dist/UI/Components/Forms/BasicForm.js +148 -89
  911. package/build/dist/UI/Components/Forms/BasicForm.js.map +1 -1
  912. package/build/dist/UI/Components/Forms/BasicModelForm.js +1 -1
  913. package/build/dist/UI/Components/Forms/BasicModelForm.js.map +1 -1
  914. package/build/dist/UI/Components/Forms/CollapsibleFormSection.js +49 -10
  915. package/build/dist/UI/Components/Forms/CollapsibleFormSection.js.map +1 -1
  916. package/build/dist/UI/Components/Forms/Fields/FormField.js +25 -11
  917. package/build/dist/UI/Components/Forms/Fields/FormField.js.map +1 -1
  918. package/build/dist/UI/Components/Forms/Fields/GeneratedKeyField.js +2 -5
  919. package/build/dist/UI/Components/Forms/Fields/GeneratedKeyField.js.map +1 -1
  920. package/build/dist/UI/Components/Forms/FormSummary.js +5 -3
  921. package/build/dist/UI/Components/Forms/FormSummary.js.map +1 -1
  922. package/build/dist/UI/Components/Forms/ModelForm.js +72 -14
  923. package/build/dist/UI/Components/Forms/ModelForm.js.map +1 -1
  924. package/build/dist/UI/Components/Forms/Types/Field.js.map +1 -1
  925. package/build/dist/UI/Components/Forms/Utils/AdvancedFormSection.js +80 -23
  926. package/build/dist/UI/Components/Forms/Utils/AdvancedFormSection.js.map +1 -1
  927. package/build/dist/UI/Components/Forms/Utils/CreateFormDefaults.js +91 -9
  928. package/build/dist/UI/Components/Forms/Utils/CreateFormDefaults.js.map +1 -1
  929. package/build/dist/UI/Components/Forms/Utils/FoldedFormFields.js +243 -0
  930. package/build/dist/UI/Components/Forms/Utils/FoldedFormFields.js.map +1 -0
  931. package/build/dist/UI/Components/Forms/Utils/FollowPickName.js +32 -0
  932. package/build/dist/UI/Components/Forms/Utils/FollowPickName.js.map +1 -0
  933. package/build/dist/UI/Components/Forms/Utils/FormModalWidth.js +25 -5
  934. package/build/dist/UI/Components/Forms/Utils/FormModalWidth.js.map +1 -1
  935. package/build/dist/UI/Components/Forms/Utils/OpenFormSections.js +32 -0
  936. package/build/dist/UI/Components/Forms/Utils/OpenFormSections.js.map +1 -0
  937. package/build/dist/UI/Components/Forms/Utils/SteppedFormFooter.js +71 -0
  938. package/build/dist/UI/Components/Forms/Utils/SteppedFormFooter.js.map +1 -0
  939. package/build/dist/UI/Components/Forms/Utils/UniqueName.js +56 -0
  940. package/build/dist/UI/Components/Forms/Utils/UniqueName.js.map +1 -0
  941. package/build/dist/UI/Components/LabelRule/ImportLabelRulesModal.js +17 -1
  942. package/build/dist/UI/Components/LabelRule/ImportLabelRulesModal.js.map +1 -1
  943. package/build/dist/UI/Components/MemberRoleAssignment/MemberRoleAssignment.js +1 -2
  944. package/build/dist/UI/Components/MemberRoleAssignment/MemberRoleAssignment.js.map +1 -1
  945. package/build/dist/UI/Components/Modal/ModalFooter.js +8 -1
  946. package/build/dist/UI/Components/Modal/ModalFooter.js.map +1 -1
  947. package/build/dist/UI/Components/ModelDetail/CardModelDetail.js +19 -6
  948. package/build/dist/UI/Components/ModelDetail/CardModelDetail.js.map +1 -1
  949. package/build/dist/UI/Components/ModelFormModal/ModelFormModal.js +33 -43
  950. package/build/dist/UI/Components/ModelFormModal/ModelFormModal.js.map +1 -1
  951. package/build/dist/UI/Components/ModelSwitch/ModelSwitchCard.js +151 -0
  952. package/build/dist/UI/Components/ModelSwitch/ModelSwitchCard.js.map +1 -0
  953. package/build/dist/UI/Components/ModelSwitch/ModelSwitchEvents.js +54 -0
  954. package/build/dist/UI/Components/ModelSwitch/ModelSwitchEvents.js.map +1 -0
  955. package/build/dist/UI/Components/ModelSwitch/ModelSwitchRow.js +201 -0
  956. package/build/dist/UI/Components/ModelSwitch/ModelSwitchRow.js.map +1 -0
  957. package/build/dist/UI/Components/ModelSwitch/ModelSwitchUtil.js +60 -0
  958. package/build/dist/UI/Components/ModelSwitch/ModelSwitchUtil.js.map +1 -0
  959. package/build/dist/UI/Components/ModelSwitch/ModelSwitchesCard.js +116 -0
  960. package/build/dist/UI/Components/ModelSwitch/ModelSwitchesCard.js.map +1 -0
  961. package/build/dist/UI/Components/ModelSwitch/useSaveModelSwitch.js +68 -0
  962. package/build/dist/UI/Components/ModelSwitch/useSaveModelSwitch.js.map +1 -0
  963. package/build/dist/UI/Components/ModelTable/BaseModelTable.js +1 -0
  964. package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
  965. package/build/dist/UI/Components/ModelTable/ModelTable.js +3 -1
  966. package/build/dist/UI/Components/ModelTable/ModelTable.js.map +1 -1
  967. package/build/dist/UI/Components/Navbar/NavBar.js +2 -2
  968. package/build/dist/UI/Components/Navbar/NavBar.js.map +1 -1
  969. package/build/dist/UI/Components/Navbar/NavBarMenuCatalog.js +21 -10
  970. package/build/dist/UI/Components/Navbar/NavBarMenuCatalog.js.map +1 -1
  971. package/build/dist/UI/Components/Navbar/NavBarMenuModal.js +10 -7
  972. package/build/dist/UI/Components/Navbar/NavBarMenuModal.js.map +1 -1
  973. package/build/dist/UI/Components/Navbar/NavBarMobileMenu.js +17 -9
  974. package/build/dist/UI/Components/Navbar/NavBarMobileMenu.js.map +1 -1
  975. package/build/dist/UI/Components/Page/ModelHeaderEvents.js +43 -0
  976. package/build/dist/UI/Components/Page/ModelHeaderEvents.js.map +1 -0
  977. package/build/dist/UI/Components/Page/ModelPage.js +19 -1
  978. package/build/dist/UI/Components/Page/ModelPage.js.map +1 -1
  979. package/build/dist/UI/Components/Page/Page.js +31 -13
  980. package/build/dist/UI/Components/Page/Page.js.map +1 -1
  981. package/build/dist/UI/Components/PeoplePicker/PeoplePicker.js +62 -20
  982. package/build/dist/UI/Components/PeoplePicker/PeoplePicker.js.map +1 -1
  983. package/build/dist/UI/Components/PeoplePicker/PeoplePickerTypes.js +98 -1
  984. package/build/dist/UI/Components/PeoplePicker/PeoplePickerTypes.js.map +1 -1
  985. package/build/dist/UI/Components/PeoplePicker/PeopleSearchPopup.js +35 -14
  986. package/build/dist/UI/Components/PeoplePicker/PeopleSearchPopup.js.map +1 -1
  987. package/build/dist/UI/Components/PublicForm/PublicFormFavicon.js +66 -0
  988. package/build/dist/UI/Components/PublicForm/PublicFormFavicon.js.map +1 -0
  989. package/build/dist/UI/Components/PublicForm/PublicFormLogo.js +38 -0
  990. package/build/dist/UI/Components/PublicForm/PublicFormLogo.js.map +1 -0
  991. package/build/dist/UI/Components/RuleCriteria/RuleCriteriaBuilder.js +5 -3
  992. package/build/dist/UI/Components/RuleCriteria/RuleCriteriaBuilder.js.map +1 -1
  993. package/build/dist/UI/Components/RuleRun/RuleView.js +2 -2
  994. package/build/dist/UI/Components/RuleRun/RuleView.js.map +1 -1
  995. package/build/dist/UI/Components/SaveStatus/SaveStatus.js +46 -0
  996. package/build/dist/UI/Components/SaveStatus/SaveStatus.js.map +1 -0
  997. package/build/dist/UI/Components/SaveStatus/useSaveOnChange.js +98 -0
  998. package/build/dist/UI/Components/SaveStatus/useSaveOnChange.js.map +1 -0
  999. package/build/dist/UI/Components/SmtpConfig/SmtpConfigFormFields.js +503 -0
  1000. package/build/dist/UI/Components/SmtpConfig/SmtpConfigFormFields.js.map +1 -0
  1001. package/build/dist/UI/Components/Sso/OidcProviderFormFields.js +283 -0
  1002. package/build/dist/UI/Components/Sso/OidcProviderFormFields.js.map +1 -0
  1003. package/build/dist/UI/Components/Sso/SamlProviderFormFields.js +182 -0
  1004. package/build/dist/UI/Components/Sso/SamlProviderFormFields.js.map +1 -0
  1005. package/build/dist/UI/Components/Sso/SsoProviderFormFields.js +208 -0
  1006. package/build/dist/UI/Components/Sso/SsoProviderFormFields.js.map +1 -0
  1007. package/build/dist/UI/Components/Table/TableEmptyState.js +17 -4
  1008. package/build/dist/UI/Components/Table/TableEmptyState.js.map +1 -1
  1009. package/build/dist/UI/Components/Workflow/ArgumentsForm.js +17 -52
  1010. package/build/dist/UI/Components/Workflow/ArgumentsForm.js.map +1 -1
  1011. package/build/dist/UI/Components/Workflow/ComponentPicker/ComponentSearch.js +7 -89
  1012. package/build/dist/UI/Components/Workflow/ComponentPicker/ComponentSearch.js.map +1 -1
  1013. package/build/dist/UI/Components/Workflow/WorkflowLogModal.js +1 -1
  1014. package/build/dist/UI/Components/Workflow/WorkflowLogModal.js.map +1 -1
  1015. package/build/dist/UI/Types/UseAnchoredFieldPopup.js +22 -3
  1016. package/build/dist/UI/Types/UseAnchoredFieldPopup.js.map +1 -1
  1017. package/build/dist/UI/Utils/DefaultInviteTeam.js +207 -0
  1018. package/build/dist/UI/Utils/DefaultInviteTeam.js.map +1 -0
  1019. package/build/dist/UI/Utils/GrantablePermission.js +31 -0
  1020. package/build/dist/UI/Utils/GrantablePermission.js.map +1 -1
  1021. package/build/dist/UI/Utils/PermissionGate.js +54 -3
  1022. package/build/dist/UI/Utils/PermissionGate.js.map +1 -1
  1023. package/build/dist/UI/Utils/TranslateTemplate.js +16 -0
  1024. package/build/dist/UI/Utils/TranslateTemplate.js.map +1 -1
  1025. package/build/dist/Utils/DistinctColor.js +151 -0
  1026. package/build/dist/Utils/DistinctColor.js.map +1 -0
  1027. package/build/dist/Utils/Monitor/MonitorOverviewPresentationUtil.js +12 -1
  1028. package/build/dist/Utils/Monitor/MonitorOverviewPresentationUtil.js.map +1 -1
  1029. package/build/dist/Utils/Slo/SloHealth.js +9 -3
  1030. package/build/dist/Utils/Slo/SloHealth.js.map +1 -1
  1031. package/build/dist/Utils/StatusPage/ReportSchedule.js +304 -0
  1032. package/build/dist/Utils/StatusPage/ReportSchedule.js.map +1 -0
  1033. package/build/dist/Utils/Telemetry/DiscoveredResourceName.js +65 -0
  1034. package/build/dist/Utils/Telemetry/DiscoveredResourceName.js.map +1 -0
  1035. package/build/dist/Utils/WordMatch.js +115 -0
  1036. package/build/dist/Utils/WordMatch.js.map +1 -0
  1037. package/package.json +1 -1
  1038. package/Tests/App/Dashboard/BulkChangeStateModalSteps.test.ts +0 -109
  1039. package/Tests/App/StatusPage/StatusPageSubscribeFromFirstStep.test.tsx +0 -382
  1040. package/Tests/UI/Components/Forms/BasicFormFinishFromAnyStep.test.tsx +0 -729
  1041. package/Tests/UI/Components/Forms/FinishFromAnyStep.test.ts +0 -571
  1042. package/Tests/UI/Components/Forms/FinishFromAnyStepGuard.test.ts +0 -566
  1043. package/UI/Components/Forms/Utils/FinishFromAnyStep.ts +0 -284
  1044. package/build/dist/UI/Components/Forms/Utils/FinishFromAnyStep.js +0 -132
  1045. package/build/dist/UI/Components/Forms/Utils/FinishFromAnyStep.js.map +0 -1
@@ -0,0 +1,1559 @@
1
+ /**
2
+ * A custom domain's certificate, end to end - a status page's and a
3
+ * dashboard's alike: Check now, the four sweeps of its worker job
4
+ * (StatusPageCerts, DashboardCerts), and the order itself, run together the
5
+ * way the worker and the API run them. Every scenario runs for both kinds:
6
+ * dashboard custom domains got the status page flow (Check now orders the
7
+ * certificate, the CNAME sweep orders what it verifies) and its guarantees,
8
+ * and this is where the two are held to be the same.
9
+ *
10
+ * Check now (the verify-cname route) now orders the free certificate the
11
+ * moment it finds the domain's record, while the 15-minute sweeps keep
12
+ * ordering for every verified domain without one. #4309's reviews found two
13
+ * ways this kind of change goes wrong, and these are the regression tests
14
+ * for both:
15
+ *
16
+ * - duplicate orders: two orders for one name - Check now and the order
17
+ * sweep at the same tick, a sweep that read the domain before Check now
18
+ * ordered it, or a domain whose isSslOrdered a CNAME blip reset while
19
+ * its certificate was fine;
20
+ * - a CNAME blip deleting a working certificate: an order checks the
21
+ * CNAME once more and GreenlockUtil.orderCert removes the name's
22
+ * certificate when that check fails, so any order placed for a domain
23
+ * that has a certificate, during a moment of DNS trouble, took the
24
+ * domain off HTTPS.
25
+ *
26
+ * The hardening that followed (custom-domain-ssl-hardening) adds the rest
27
+ * of what its review found, end to end:
28
+ *
29
+ * - Reissue SSL takes the same lock as every other order;
30
+ * - Check now and the CNAME sweep check the record once, not again inside
31
+ * the order, and a sweep's DNS blip during a Check now order cannot
32
+ * leave the domain ordered but "waiting for DNS";
33
+ * - Check now says Issued only for a certificate that exists and has not
34
+ * expired, and renews an expired one;
35
+ * - a domain whose order failed waits longer before the sweeps order it
36
+ * again, and its failure is what the Status column reads;
37
+ * - Check now, the sweeps and the renewal run together stay within the
38
+ * installation's Let's Encrypt budget.
39
+ *
40
+ * Real: StatusPageDomainService and DashboardDomainService (the sweeps,
41
+ * isCnameValid, orderCert, orderCertIfMissing, orderCertOnceCnameIsVerified,
42
+ * reissueCert, the renewal run), CustomDomainOrders, CertificateOrder,
43
+ * CertificateOrderBudget, CertificateOrderFailures and GreenlockUtil's
44
+ * order. Replaced: the two tables (in memory), the Let's Encrypt client, the
45
+ * DNS and HTTP checks of the customer's domain, and Redis (InMemoryRedis: a
46
+ * held lock refuses at once, as Redis does).
47
+ */
48
+
49
+ import StatusPageDomainService, {
50
+ Service as StatusPageDomainServiceClass,
51
+ } from "../../../Server/Services/StatusPageDomainService";
52
+ import DashboardDomainService, {
53
+ Service as DashboardDomainServiceClass,
54
+ } from "../../../Server/Services/DashboardDomainService";
55
+ import GreenlockUtil from "../../../Server/Utils/Greenlock/Greenlock";
56
+ import AcmeCertificateService from "../../../Server/Services/AcmeCertificateService";
57
+ import QueryHelper from "../../../Server/Types/Database/QueryHelper";
58
+ import Domain from "../../../Server/Types/Domain";
59
+ import GlobalCache from "../../../Server/Infrastructure/GlobalCache";
60
+ import CertificateOrderBudget from "../../../Server/Utils/Greenlock/CertificateOrderBudget";
61
+ import CertificateOrderFailures from "../../../Server/Utils/Greenlock/CertificateOrderFailures";
62
+ import ObjectID from "../../../Types/ObjectID";
63
+ import OneUptimeDate from "../../../Types/Date";
64
+ import TooManyRequestsException from "../../../Types/Exception/TooManyRequestsException";
65
+ import {
66
+ CustomDomainCertificateStatus,
67
+ CustomDomainVerificationResult,
68
+ } from "../../../Types/CustomDomain/CustomDomainVerification";
69
+ import { CustomDomainCertificate } from "../../../Types/CustomDomain/CustomDomainCertificates";
70
+ import {
71
+ InMemoryRedis,
72
+ useInMemoryRedis,
73
+ } from "../Utils/Greenlock/InMemoryRedis";
74
+ import {
75
+ afterEach,
76
+ beforeEach,
77
+ describe,
78
+ expect,
79
+ jest,
80
+ test,
81
+ } from "@jest/globals";
82
+ import type { SpyInstance } from "jest-mock";
83
+
84
+ /*
85
+ * The two kinds of custom domain. Their services have the same methods,
86
+ * each over its own table; the tests call them through the status page
87
+ * service's type, since every row they pass is a plain object.
88
+ */
89
+ interface Kind {
90
+ name: string;
91
+ service: StatusPageDomainServiceClass;
92
+ orderMaxPerRun: number;
93
+ // How many certificates one renewal run renews at most.
94
+ renewalsPerRun: number;
95
+ // What a domain's CNAME record points to, as the installation is set up.
96
+ cnameRecord: string;
97
+ }
98
+
99
+ const KINDS: Array<Kind> = [
100
+ {
101
+ name: "a status page",
102
+ service: StatusPageDomainService,
103
+ orderMaxPerRun: StatusPageDomainServiceClass.ORDER_MAX_PER_RUN,
104
+ renewalsPerRun: GreenlockUtil.RENEW_MAX_PER_RUN,
105
+ cnameRecord: "statuspage.example.com",
106
+ },
107
+ {
108
+ name: "a dashboard",
109
+ service: DashboardDomainService as unknown as StatusPageDomainServiceClass,
110
+ orderMaxPerRun: DashboardDomainServiceClass.ORDER_MAX_PER_RUN,
111
+ // The dashboard renewal run renews ORDER_MAX_PER_RUN at most.
112
+ renewalsPerRun: DashboardDomainServiceClass.ORDER_MAX_PER_RUN,
113
+ cnameRecord: "dashboards.example.com",
114
+ },
115
+ ];
116
+
117
+ // The kind the running test is about, and its service.
118
+ let kind: Kind = KINDS[0]!;
119
+ let service: StatusPageDomainServiceClass = kind.service;
120
+
121
+ // What the CA does with the next order; the default issues at once.
122
+ let mockAuto: () => Promise<string> = async (): Promise<string> => {
123
+ return "-----BEGIN CERTIFICATE-----\nissued\n-----END CERTIFICATE-----";
124
+ };
125
+
126
+ // Names the CA was asked for, in order.
127
+ const mockCaOrders: Array<string> = [];
128
+
129
+ jest.mock("acme-client", () => {
130
+ return {
131
+ __esModule: true,
132
+ default: {
133
+ Client: class {
134
+ public async auto(): Promise<string> {
135
+ return mockAuto();
136
+ }
137
+ },
138
+ directory: {
139
+ letsencrypt: {
140
+ production: "https://acme.example.com/directory",
141
+ },
142
+ },
143
+ crypto: {
144
+ createCsr: async (data: {
145
+ commonName: string;
146
+ }): Promise<Array<string>> => {
147
+ mockCaOrders.push(data.commonName);
148
+ return ["-----BEGIN PRIVATE KEY-----", "csr"];
149
+ },
150
+ readCertificateInfo: (): { notBefore: Date; notAfter: Date } => {
151
+ return {
152
+ notBefore: new Date(),
153
+ notAfter: new Date(Date.now() + 90 * 24 * 3600 * 1000),
154
+ };
155
+ },
156
+ },
157
+ },
158
+ };
159
+ });
160
+
161
+ jest.mock("../../../Server/EnvironmentConfig", () => {
162
+ return {
163
+ ...(jest.requireActual("../../../Server/EnvironmentConfig") as Record<
164
+ string,
165
+ unknown
166
+ >),
167
+ __esModule: true,
168
+ LetsEncryptAccountKey: Buffer.from("account key").toString("base64"),
169
+ LetsEncryptNotificationEmail: "certificates@example.com",
170
+ // The same as KINDS' cnameRecord: the factory runs before that is set.
171
+ StatusPageCNameRecord: "statuspage.example.com",
172
+ DashboardCNameRecord: "dashboards.example.com",
173
+ };
174
+ });
175
+
176
+ type DomainRow = {
177
+ _id: string;
178
+ id: ObjectID;
179
+ fullDomain: string;
180
+ subdomain: string;
181
+ cnameVerificationToken: string;
182
+ isCnameVerified: boolean;
183
+ isSslOrdered: boolean;
184
+ isSslProvisioned: boolean;
185
+ isCustomCertificate: boolean;
186
+ certificateReissueRequestedAt?: Date | undefined;
187
+ };
188
+
189
+ type CertificateRow = {
190
+ _id: string;
191
+ domain: string;
192
+ certificate: string;
193
+ expiresAt: Date;
194
+ };
195
+
196
+ type World = {
197
+ domains: Array<DomainRow>;
198
+ certificates: Map<string, CertificateRow>;
199
+ // Names whose record points here: the HTTP and DNS checks pass.
200
+ dnsLive: Set<string>;
201
+ // Names nginx serves a certificate for: the HTTPS probe passes.
202
+ served: Set<string>;
203
+ deletedCertificates: Array<string>;
204
+ heldLocks: Set<string>;
205
+ // Redis keys with their values: Check now's window, the budgets, failures.
206
+ cache: Map<string, string>;
207
+ redis: InMemoryRedis;
208
+ };
209
+
210
+ let world: World;
211
+
212
+ function makeDomain(
213
+ fullDomain: string,
214
+ state: Partial<
215
+ Pick<DomainRow, "isCnameVerified" | "isSslOrdered" | "isSslProvisioned">
216
+ > = {},
217
+ ): DomainRow {
218
+ const id: ObjectID = ObjectID.generate();
219
+
220
+ return {
221
+ _id: id.toString(),
222
+ id: id,
223
+ fullDomain: fullDomain,
224
+ subdomain: fullDomain.split(".")[0] || "",
225
+ cnameVerificationToken: `token-${fullDomain}`,
226
+ isCnameVerified: false,
227
+ isSslOrdered: false,
228
+ isSslProvisioned: false,
229
+ isCustomCertificate: false,
230
+ ...state,
231
+ };
232
+ }
233
+
234
+ function matches(row: DomainRow, query: Record<string, unknown>): boolean {
235
+ return Object.entries(query).every(([key, value]: [string, unknown]) => {
236
+ // "IN (...)": QueryHelper.any, as the spy below builds it.
237
+ const inList: Array<string> | undefined = (
238
+ value as { inList?: Array<string> } | null
239
+ )?.inList;
240
+
241
+ if (inList) {
242
+ return inList.includes(
243
+ String((row as unknown as Record<string, unknown>)[key]),
244
+ );
245
+ }
246
+
247
+ if (value instanceof Date) {
248
+ const current: unknown = (row as unknown as Record<string, unknown>)[key];
249
+
250
+ return current instanceof Date && current.getTime() === value.getTime();
251
+ }
252
+
253
+ /*
254
+ * Another query operator (the reissue cooldown's "older than or null")
255
+ * is not modelled: the domains here are reissued at most once.
256
+ */
257
+ if (value !== null && typeof value === "object") {
258
+ return true;
259
+ }
260
+
261
+ return (
262
+ String((row as unknown as Record<string, unknown>)[key]) === String(value)
263
+ );
264
+ });
265
+ }
266
+
267
+ function copyOf(row: DomainRow): DomainRow {
268
+ return { ...row };
269
+ }
270
+
271
+ function rowById(id: ObjectID | string): DomainRow | undefined {
272
+ return world.domains.find((row: DomainRow) => {
273
+ return row._id === id.toString();
274
+ });
275
+ }
276
+
277
+ function hostOf(url: string): string {
278
+ return url.split("://")[1]!.split("/")[0]!;
279
+ }
280
+
281
+ function addCertificate(domain: string, expiresInDays: number): void {
282
+ world.certificates.set(domain, {
283
+ _id: ObjectID.generate().toString(),
284
+ domain: domain,
285
+ certificate: `existing certificate of ${domain}`,
286
+ expiresAt: new Date(Date.now() + expiresInDays * 24 * 3600 * 1000),
287
+ });
288
+ }
289
+
290
+ /*
291
+ * The sweeps' read of the order sweep's domains can be held open, to stand
292
+ * for a sweep that read its domains before Check now ordered one of them.
293
+ */
294
+ let holdOrderSweepRead: Promise<void> | null = null;
295
+
296
+ function setUpWorld(forKind: Kind): void {
297
+ kind = forKind;
298
+ service = forKind.service;
299
+
300
+ const redis: InMemoryRedis = useInMemoryRedis();
301
+
302
+ world = {
303
+ domains: [],
304
+ certificates: new Map(),
305
+ dnsLive: new Set(),
306
+ served: new Set(),
307
+ deletedCertificates: [],
308
+ heldLocks: redis.heldLocks,
309
+ cache: redis.cache,
310
+ redis: redis,
311
+ };
312
+ mockCaOrders.length = 0;
313
+ holdOrderSweepRead = null;
314
+
315
+ jest.spyOn(QueryHelper, "any").mockImplementation(((
316
+ values: Array<string>,
317
+ ) => {
318
+ return { inList: values.map(String) };
319
+ }) as never);
320
+
321
+ // The kind's domain table.
322
+ jest.spyOn(service, "findBy").mockImplementation((async (call: {
323
+ query: Record<string, unknown>;
324
+ skip?: number;
325
+ limit: number;
326
+ }) => {
327
+ const snapshot: Array<DomainRow> = world.domains
328
+ .filter((row: DomainRow) => {
329
+ return matches(row, call.query);
330
+ })
331
+ .map(copyOf)
332
+ .slice(call.skip || 0, (call.skip || 0) + call.limit);
333
+
334
+ const isOrderSweepRead: boolean =
335
+ call.query["isCnameVerified"] === true &&
336
+ call.query["isSslOrdered"] === false;
337
+
338
+ if (isOrderSweepRead && holdOrderSweepRead) {
339
+ await holdOrderSweepRead;
340
+ }
341
+
342
+ return snapshot;
343
+ }) as never);
344
+
345
+ jest.spyOn(service, "findOneBy").mockImplementation((async (call: {
346
+ query: Record<string, unknown>;
347
+ }) => {
348
+ const row: DomainRow | undefined = world.domains.find((row: DomainRow) => {
349
+ return matches(row, call.query);
350
+ });
351
+
352
+ return row ? copyOf(row) : null;
353
+ }) as never);
354
+
355
+ jest.spyOn(service, "updateOneById").mockImplementation((async (update: {
356
+ id: ObjectID;
357
+ data: Partial<DomainRow>;
358
+ }) => {
359
+ const row: DomainRow | undefined = rowById(update.id);
360
+
361
+ if (row) {
362
+ Object.assign(row, update.data);
363
+ }
364
+ }) as never);
365
+
366
+ jest.spyOn(service, "updateOneBy").mockImplementation((async (update: {
367
+ query: Record<string, unknown>;
368
+ data: Partial<DomainRow>;
369
+ }) => {
370
+ const row: DomainRow | undefined = world.domains.find(
371
+ (candidate: DomainRow) => {
372
+ return matches(candidate, update.query);
373
+ },
374
+ );
375
+
376
+ if (!row) {
377
+ return 0;
378
+ }
379
+
380
+ Object.assign(row, update.data);
381
+ return 1;
382
+ }) as never);
383
+
384
+ // Every row the query matches: the one write that records many domains.
385
+ jest.spyOn(service, "updateBy").mockImplementation((async (update: {
386
+ query: Record<string, unknown>;
387
+ data: Partial<DomainRow>;
388
+ }) => {
389
+ const rows: Array<DomainRow> = world.domains.filter(
390
+ (candidate: DomainRow) => {
391
+ return matches(candidate, update.query);
392
+ },
393
+ );
394
+
395
+ for (const row of rows) {
396
+ Object.assign(row, update.data);
397
+ }
398
+
399
+ return rows.length;
400
+ }) as never);
401
+
402
+ /*
403
+ * The certificate table: a lookup by name, or - for the renewal run - the
404
+ * rows due, by expiry.
405
+ */
406
+ jest
407
+ .spyOn(AcmeCertificateService, "findBy")
408
+ .mockImplementation((async (call: {
409
+ query: { domain?: { inList: Array<string> } };
410
+ skip?: number;
411
+ }) => {
412
+ if (!call.query.domain) {
413
+ if (call.skip) {
414
+ return [];
415
+ }
416
+
417
+ return [...world.certificates.values()]
418
+ .sort((a: CertificateRow, b: CertificateRow) => {
419
+ return a.expiresAt.getTime() - b.expiresAt.getTime();
420
+ })
421
+ .map((row: CertificateRow) => {
422
+ return { ...row };
423
+ });
424
+ }
425
+
426
+ return call.query.domain.inList
427
+ .filter((domain: string) => {
428
+ return world.certificates.has(domain);
429
+ })
430
+ .map((domain: string) => {
431
+ return { ...world.certificates.get(domain)! };
432
+ });
433
+ }) as never);
434
+
435
+ jest
436
+ .spyOn(AcmeCertificateService, "findOneBy")
437
+ .mockImplementation((async (call: { query: { domain: string } }) => {
438
+ const row: CertificateRow | undefined = world.certificates.get(
439
+ call.query.domain,
440
+ );
441
+ return row ? { ...row } : null;
442
+ }) as never);
443
+
444
+ jest
445
+ .spyOn(AcmeCertificateService, "updateBy")
446
+ .mockImplementation((async (call: {
447
+ query: { domain: string };
448
+ data: Partial<CertificateRow>;
449
+ }) => {
450
+ const row: CertificateRow | undefined = world.certificates.get(
451
+ call.query.domain,
452
+ );
453
+
454
+ if (!row) {
455
+ return 0;
456
+ }
457
+
458
+ Object.assign(row, call.data);
459
+ return 1;
460
+ }) as never);
461
+
462
+ jest
463
+ .spyOn(AcmeCertificateService, "create")
464
+ .mockImplementation((async (call: { data: Partial<CertificateRow> }) => {
465
+ world.certificates.set(call.data.domain as string, {
466
+ _id: ObjectID.generate().toString(),
467
+ domain: call.data.domain as string,
468
+ certificate: call.data.certificate as string,
469
+ expiresAt: call.data.expiresAt as Date,
470
+ });
471
+ return call.data;
472
+ }) as never);
473
+
474
+ jest
475
+ .spyOn(AcmeCertificateService, "deleteBy")
476
+ .mockImplementation((async (call: { query: { domain: string } }) => {
477
+ if (world.certificates.delete(call.query.domain)) {
478
+ world.deletedCertificates.push(call.query.domain);
479
+ return 1;
480
+ }
481
+
482
+ return 0;
483
+ }) as never);
484
+
485
+ // The customer's domain: its HTTP token check, its HTTPS probe, its DNS.
486
+ jest
487
+ .spyOn(Domain, "getForDomainVerification")
488
+ .mockImplementation((async (request: { url: string }) => {
489
+ const host: string = hostOf(request.url);
490
+ const isHttps: boolean = request.url.startsWith("https://");
491
+ const answers: boolean =
492
+ world.dnsLive.has(host) && (!isHttps || world.served.has(host));
493
+
494
+ return {
495
+ isSuccess: (): boolean => {
496
+ return answers;
497
+ },
498
+ isFailure: (): boolean => {
499
+ return !answers;
500
+ },
501
+ };
502
+ }) as never);
503
+
504
+ jest.spyOn(Domain, "getCnameRecords").mockImplementation((async (data: {
505
+ domain: string;
506
+ }) => {
507
+ return world.dnsLive.has(data.domain) ? [kind.cnameRecord] : [];
508
+ }) as never);
509
+
510
+ /*
511
+ * The lock (a held key refuses at once, like Redis with one attempt) and
512
+ * the cache - Check now's window, the order budgets and the failed-order
513
+ * records - are InMemoryRedis, set up above.
514
+ */
515
+ }
516
+
517
+ // A new on-demand order window: what 15 minutes later looks like.
518
+ function nextOnDemandWindow(): void {
519
+ world.cache.clear();
520
+ }
521
+
522
+ // The clock, moved on by this many minutes from now.
523
+ function minutesLater(minutes: number): void {
524
+ const later: Date = OneUptimeDate.addRemoveMinutes(new Date(), minutes);
525
+
526
+ jest.spyOn(OneUptimeDate, "getCurrentDate").mockReturnValue(later);
527
+ }
528
+
529
+ /*
530
+ * What the verify-cname route does: read the domain, check its record, and
531
+ * once it is found order its certificate.
532
+ */
533
+ async function clickCheckNow(
534
+ domain: DomainRow,
535
+ options?: {
536
+ waitInMs?: number;
537
+ // DNS stops answering the moment after the route found the record.
538
+ dnsGoesAwayAfterTheCheck?: boolean;
539
+ },
540
+ ): Promise<CustomDomainVerificationResult | null> {
541
+ const asRead: DomainRow = copyOf(rowById(domain.id)!);
542
+
543
+ const isValid: boolean = await service.isCnameValid(asRead.fullDomain);
544
+
545
+ if (!isValid) {
546
+ return null;
547
+ }
548
+
549
+ if (options?.dnsGoesAwayAfterTheCheck) {
550
+ world.dnsLive.delete(asRead.fullDomain);
551
+ }
552
+
553
+ return await service.orderCertOnceCnameIsVerified(asRead as never, options);
554
+ }
555
+
556
+ // Every sweep of the StatusPageCerts worker but renewal, as one tick.
557
+ async function runSweeps(): Promise<void> {
558
+ await Promise.all([
559
+ service.verifyCnameWhoseCnameisNotVerified(),
560
+ service.orderSSLForDomainsWhichAreNotOrderedYet(),
561
+ service.checkOrderStatus(),
562
+ service.updateSslProvisioningStatusForAllDomains(),
563
+ ]);
564
+ }
565
+
566
+ // A CA that holds the next order until released.
567
+ function holdTheNextOrder(): {
568
+ release: () => void;
569
+ started: Promise<void>;
570
+ } {
571
+ let release: () => void = (): void => {};
572
+ let markStarted: () => void = (): void => {};
573
+
574
+ const started: Promise<void> = new Promise<void>((resolve: () => void) => {
575
+ markStarted = resolve;
576
+ });
577
+
578
+ const original: () => Promise<string> = mockAuto;
579
+
580
+ mockAuto = (): Promise<string> => {
581
+ mockAuto = original;
582
+ markStarted();
583
+
584
+ return new Promise<string>((resolve: (cert: string) => void) => {
585
+ release = (): void => {
586
+ resolve("-----BEGIN CERTIFICATE-----\nheld\n-----END CERTIFICATE-----");
587
+ };
588
+ });
589
+ };
590
+
591
+ return {
592
+ release: (): void => {
593
+ release();
594
+ },
595
+ started: started,
596
+ };
597
+ }
598
+
599
+ async function settle(): Promise<void> {
600
+ for (let i: number = 0; i < 5; i++) {
601
+ await new Promise<void>((resolve: () => void) => {
602
+ setTimeout(resolve, 0);
603
+ });
604
+ }
605
+ }
606
+
607
+ const DEFAULT_AUTO: () => Promise<string> = mockAuto;
608
+
609
+ describe.each(KINDS)(
610
+ "$name custom domain's certificate, Check now and the sweeps together",
611
+ (forKind: Kind) => {
612
+ beforeEach(() => {
613
+ mockAuto = DEFAULT_AUTO;
614
+ setUpWorld(forKind);
615
+ });
616
+
617
+ afterEach(() => {
618
+ jest.restoreAllMocks();
619
+ });
620
+
621
+ test("Check now orders the certificate the moment the record is found, and the sweeps order nothing more", async () => {
622
+ const domain: DomainRow = makeDomain("status.acme.com");
623
+ world.domains.push(domain);
624
+ world.dnsLive.add("status.acme.com");
625
+
626
+ const result: CustomDomainVerificationResult | null =
627
+ await clickCheckNow(domain);
628
+
629
+ expect(result?.certificateStatus).toBe(
630
+ CustomDomainCertificateStatus.Issuing,
631
+ );
632
+ expect(mockCaOrders).toEqual(["status.acme.com"]);
633
+ expect(world.certificates.has("status.acme.com")).toBe(true);
634
+ expect(domain.isCnameVerified).toBe(true);
635
+ expect(domain.isSslOrdered).toBe(true);
636
+
637
+ // nginx writes it out; the sweeps see it served and order nothing.
638
+ world.served.add("status.acme.com");
639
+ await runSweeps();
640
+ await runSweeps();
641
+
642
+ expect(mockCaOrders).toEqual(["status.acme.com"]);
643
+ expect(domain.isSslProvisioned).toBe(true);
644
+ });
645
+
646
+ /*
647
+ * Regression: duplicate orders. Check now's order is still with the CA
648
+ * when the 15-minute tick starts; the order sweep finds the domain
649
+ * verified and unordered, and places nothing.
650
+ */
651
+ test("Check now and the order sweep at the same moment: one order", async () => {
652
+ const domain: DomainRow = makeDomain("status.acme.com");
653
+ world.domains.push(domain);
654
+ world.dnsLive.add("status.acme.com");
655
+
656
+ const ca: { release: () => void; started: Promise<void> } =
657
+ holdTheNextOrder();
658
+
659
+ const checkNow: Promise<CustomDomainVerificationResult | null> =
660
+ clickCheckNow(domain);
661
+
662
+ await ca.started;
663
+
664
+ // The tick, while Check now's order is with the CA.
665
+ await runSweeps();
666
+
667
+ expect(domain.isCnameVerified).toBe(true);
668
+ expect(domain.isSslOrdered).toBe(false);
669
+
670
+ ca.release();
671
+ await checkNow;
672
+
673
+ expect(mockCaOrders).toEqual(["status.acme.com"]);
674
+ expect(domain.isSslOrdered).toBe(true);
675
+
676
+ await runSweeps();
677
+
678
+ expect(mockCaOrders).toEqual(["status.acme.com"]);
679
+ });
680
+
681
+ /*
682
+ * Regression: duplicate orders. The sweep read its domains a moment
683
+ * before Check now ordered this one, and gets to it after that order has
684
+ * finished and let go of the lock.
685
+ */
686
+ test("a sweep that read the domain before Check now ordered it does not order it again", async () => {
687
+ const domain: DomainRow = makeDomain("status.acme.com", {
688
+ isCnameVerified: true,
689
+ });
690
+ world.domains.push(domain);
691
+ world.dnsLive.add("status.acme.com");
692
+
693
+ let letTheSweepGoOn: () => void = (): void => {};
694
+ holdOrderSweepRead = new Promise<void>((resolve: () => void) => {
695
+ letTheSweepGoOn = resolve;
696
+ });
697
+
698
+ const sweep: Promise<void> =
699
+ service.orderSSLForDomainsWhichAreNotOrderedYet();
700
+
701
+ await settle();
702
+
703
+ // Check now, start to finish, while the sweep holds its stale list.
704
+ holdOrderSweepRead = null;
705
+ await clickCheckNow(domain);
706
+
707
+ expect(mockCaOrders).toEqual(["status.acme.com"]);
708
+
709
+ letTheSweepGoOn();
710
+ await sweep;
711
+
712
+ expect(mockCaOrders).toEqual(["status.acme.com"]);
713
+ expect(world.certificates.size).toBe(1);
714
+ });
715
+
716
+ test("two clicks on Check now at once: one order", async () => {
717
+ const domain: DomainRow = makeDomain("status.acme.com");
718
+ world.domains.push(domain);
719
+ world.dnsLive.add("status.acme.com");
720
+
721
+ const ca: { release: () => void; started: Promise<void> } =
722
+ holdTheNextOrder();
723
+
724
+ const first: Promise<CustomDomainVerificationResult | null> =
725
+ clickCheckNow(domain);
726
+ await ca.started;
727
+ const second: CustomDomainVerificationResult | null =
728
+ await clickCheckNow(domain);
729
+
730
+ ca.release();
731
+ const firstResult: CustomDomainVerificationResult | null = await first;
732
+
733
+ expect(mockCaOrders).toEqual(["status.acme.com"]);
734
+ expect(firstResult?.certificateStatus).toBe(
735
+ CustomDomainCertificateStatus.Issuing,
736
+ );
737
+ expect(second?.certificateStatus).toBe(
738
+ CustomDomainCertificateStatus.Issuing,
739
+ );
740
+ });
741
+
742
+ /*
743
+ * Regression: a CNAME blip deleting a valid certificate, and the
744
+ * duplicate order after the blip reset isSslOrdered. The record fails
745
+ * for a few runs and comes back; the certificate that was fine throughout
746
+ * is neither deleted nor ordered again.
747
+ */
748
+ test("a CNAME blip: the working certificate is never deleted, and never ordered again", async () => {
749
+ const domain: DomainRow = makeDomain("status.acme.com", {
750
+ isCnameVerified: true,
751
+ isSslOrdered: true,
752
+ isSslProvisioned: true,
753
+ });
754
+ world.domains.push(domain);
755
+ world.dnsLive.add("status.acme.com");
756
+ world.served.add("status.acme.com");
757
+ addCertificate("status.acme.com", 60);
758
+
759
+ const certificateBefore: string =
760
+ world.certificates.get("status.acme.com")!.certificate;
761
+
762
+ // The blip.
763
+ world.dnsLive.delete("status.acme.com");
764
+ world.served.delete("status.acme.com");
765
+
766
+ await runSweeps();
767
+
768
+ // The failed checks reset the domain, as they always did...
769
+ expect(domain.isCnameVerified).toBe(false);
770
+ expect(domain.isSslOrdered).toBe(false);
771
+
772
+ await runSweeps();
773
+ await runSweeps();
774
+
775
+ // ...but nothing deleted its certificate or ordered another.
776
+ expect(world.deletedCertificates).toEqual([]);
777
+ expect(world.certificates.get("status.acme.com")?.certificate).toBe(
778
+ certificateBefore,
779
+ );
780
+ expect(mockCaOrders).toEqual([]);
781
+
782
+ // The record is back.
783
+ world.dnsLive.add("status.acme.com");
784
+ world.served.add("status.acme.com");
785
+
786
+ await runSweeps();
787
+ await runSweeps();
788
+
789
+ expect(domain.isCnameVerified).toBe(true);
790
+ expect(domain.isSslOrdered).toBe(true);
791
+ expect(domain.isSslProvisioned).toBe(true);
792
+ expect(mockCaOrders).toEqual([]);
793
+ expect(world.deletedCertificates).toEqual([]);
794
+ expect(world.certificates.get("status.acme.com")?.certificate).toBe(
795
+ certificateBefore,
796
+ );
797
+ });
798
+
799
+ /*
800
+ * Regression: Reissue SSL during a CNAME blip. The order checks the CNAME
801
+ * once more, and GreenlockUtil.orderCert used to remove the name's
802
+ * certificate when that check failed - the one being reissued, still
803
+ * serving. The reissue is refused; the certificate stays.
804
+ */
805
+ test("Reissue SSL during a CNAME blip is refused, and the working certificate stays", async () => {
806
+ const domain: DomainRow = makeDomain("status.acme.com", {
807
+ isCnameVerified: true,
808
+ isSslOrdered: true,
809
+ isSslProvisioned: true,
810
+ });
811
+ world.domains.push(domain);
812
+ addCertificate("status.acme.com", 60);
813
+
814
+ const certificateBefore: string =
815
+ world.certificates.get("status.acme.com")!.certificate;
816
+
817
+ // The blip, as the reissue's own CNAME check runs.
818
+ world.dnsLive.delete("status.acme.com");
819
+
820
+ await expect(service.reissueCert(domain.id)).rejects.toThrow(
821
+ "Cname is not valid",
822
+ );
823
+
824
+ expect(world.deletedCertificates).toEqual([]);
825
+ expect(world.certificates.get("status.acme.com")?.certificate).toBe(
826
+ certificateBefore,
827
+ );
828
+ expect(mockCaOrders).toEqual([]);
829
+ });
830
+
831
+ test("Check now on a domain verified again after a blip records its certificate and orders nothing", async () => {
832
+ const domain: DomainRow = makeDomain("status.acme.com");
833
+ world.domains.push(domain);
834
+ world.dnsLive.add("status.acme.com");
835
+ addCertificate("status.acme.com", 45);
836
+
837
+ const result: CustomDomainVerificationResult | null =
838
+ await clickCheckNow(domain);
839
+
840
+ expect(result?.certificateStatus).toBe(
841
+ CustomDomainCertificateStatus.Issued,
842
+ );
843
+ expect(mockCaOrders).toEqual([]);
844
+ expect(domain.isSslOrdered).toBe(true);
845
+ });
846
+
847
+ /*
848
+ * The provisioning sweep used to order again whenever its HTTPS probe
849
+ * failed - which, right after an order, is until nginx next writes the
850
+ * new certificate.
851
+ */
852
+ test("a certificate nginx has not written out yet is not ordered a second time", async () => {
853
+ const domain: DomainRow = makeDomain("status.acme.com");
854
+ world.domains.push(domain);
855
+ world.dnsLive.add("status.acme.com");
856
+
857
+ await clickCheckNow(domain);
858
+
859
+ // Not served yet, for a few ticks.
860
+ await runSweeps();
861
+ await runSweeps();
862
+
863
+ expect(mockCaOrders).toEqual(["status.acme.com"]);
864
+ expect(domain.isSslProvisioned).toBe(false);
865
+
866
+ world.served.add("status.acme.com");
867
+ await runSweeps();
868
+
869
+ expect(domain.isSslProvisioned).toBe(true);
870
+ expect(mockCaOrders).toEqual(["status.acme.com"]);
871
+ });
872
+
873
+ test("without anyone clicking Check now, the sweep that verifies a domain orders its certificate in the same run", async () => {
874
+ const domain: DomainRow = makeDomain("status.acme.com");
875
+ world.domains.push(domain);
876
+
877
+ await runSweeps();
878
+
879
+ expect(mockCaOrders).toEqual([]);
880
+ expect(domain.isCnameVerified).toBe(false);
881
+
882
+ world.dnsLive.add("status.acme.com");
883
+ await service.verifyCnameWhoseCnameisNotVerified();
884
+
885
+ expect(domain.isCnameVerified).toBe(true);
886
+ expect(domain.isSslOrdered).toBe(true);
887
+ expect(mockCaOrders).toEqual(["status.acme.com"]);
888
+
889
+ await runSweeps();
890
+
891
+ expect(mockCaOrders).toEqual(["status.acme.com"]);
892
+ });
893
+
894
+ /*
895
+ * The sweeps order a failed domain again - once its retry delay is up,
896
+ * not on the very next tick: a domain whose order keeps failing used to
897
+ * be ordered every run, each order against the account the whole
898
+ * installation shares.
899
+ */
900
+ test("an order that fails is reported to Check now, and the sweeps order again once its delay is up", async () => {
901
+ const domain: DomainRow = makeDomain("status.acme.com");
902
+ world.domains.push(domain);
903
+ world.dnsLive.add("status.acme.com");
904
+
905
+ mockAuto = async (): Promise<string> => {
906
+ mockAuto = DEFAULT_AUTO;
907
+ throw new Error("urn:ietf:params:acme:error:caa");
908
+ };
909
+
910
+ const result: CustomDomainVerificationResult | null =
911
+ await clickCheckNow(domain);
912
+
913
+ expect(result?.certificateStatus).toBe(
914
+ CustomDomainCertificateStatus.Failed,
915
+ );
916
+ expect(result?.certificateError).toContain(
917
+ "Unable to order certificate for status.acme.com",
918
+ );
919
+ expect(domain.isCnameVerified).toBe(true);
920
+ expect(domain.isSslOrdered).toBe(false);
921
+
922
+ // The next tick, a moment later: the domain waits.
923
+ await runSweeps();
924
+
925
+ expect(mockCaOrders).toEqual(["status.acme.com"]);
926
+
927
+ // Its first retry delay, 15 minutes, is up.
928
+ minutesLater(15);
929
+ await runSweeps();
930
+
931
+ expect(mockCaOrders).toEqual(["status.acme.com", "status.acme.com"]);
932
+ expect(domain.isSslOrdered).toBe(true);
933
+ expect(world.certificates.has("status.acme.com")).toBe(true);
934
+ });
935
+
936
+ /*
937
+ * Regression (review): an order that fails leaves the domain unordered,
938
+ * so every click on Check now - or a script calling verify-cname - used
939
+ * to place another order against the shared Let's Encrypt account.
940
+ */
941
+ test("after a failed order, more clicks in the same window order nothing and keep showing why", async () => {
942
+ const domain: DomainRow = makeDomain("status.acme.com");
943
+ world.domains.push(domain);
944
+ world.dnsLive.add("status.acme.com");
945
+
946
+ mockAuto = async (): Promise<string> => {
947
+ throw new Error("urn:ietf:params:acme:error:caa");
948
+ };
949
+
950
+ const first: CustomDomainVerificationResult | null =
951
+ await clickCheckNow(domain);
952
+
953
+ expect(first?.certificateStatus).toBe(
954
+ CustomDomainCertificateStatus.Failed,
955
+ );
956
+ expect(mockCaOrders).toEqual(["status.acme.com"]);
957
+
958
+ for (let click: number = 0; click < 5; click++) {
959
+ const again: CustomDomainVerificationResult | null =
960
+ await clickCheckNow(domain);
961
+
962
+ expect(again?.certificateStatus).toBe(
963
+ CustomDomainCertificateStatus.Failed,
964
+ );
965
+ expect(again?.certificateError).toBe(first?.certificateError);
966
+ }
967
+
968
+ expect(mockCaOrders).toEqual(["status.acme.com"]);
969
+
970
+ // The next window: Check now may order again, and this time it works.
971
+ mockAuto = DEFAULT_AUTO;
972
+ nextOnDemandWindow();
973
+
974
+ const later: CustomDomainVerificationResult | null =
975
+ await clickCheckNow(domain);
976
+
977
+ expect(later?.certificateStatus).toBe(
978
+ CustomDomainCertificateStatus.Issuing,
979
+ );
980
+ expect(mockCaOrders).toEqual(["status.acme.com", "status.acme.com"]);
981
+ expect(domain.isSslOrdered).toBe(true);
982
+ });
983
+
984
+ test("without the cache, Check now orders nothing and leaves the domain to the sweeps", async () => {
985
+ const domain: DomainRow = makeDomain("status.acme.com");
986
+ world.domains.push(domain);
987
+ world.dnsLive.add("status.acme.com");
988
+
989
+ jest
990
+ .spyOn(GlobalCache, "setStringIfNotExists")
991
+ .mockRejectedValue(new Error("Cache is not connected") as never);
992
+
993
+ const result: CustomDomainVerificationResult | null =
994
+ await clickCheckNow(domain);
995
+
996
+ expect(result?.certificateStatus).toBe(
997
+ CustomDomainCertificateStatus.Issuing,
998
+ );
999
+ expect(mockCaOrders).toEqual([]);
1000
+
1001
+ await runSweeps();
1002
+
1003
+ expect(mockCaOrders).toEqual(["status.acme.com"]);
1004
+ });
1005
+
1006
+ /*
1007
+ * The three sweeps that order - the CNAME sweep for what it has just
1008
+ * verified, the order sweep, the re-order of a missing certificate - run
1009
+ * on the same tick, each capping its own batch. Between them they order
1010
+ * no more than ORDER_MAX_PER_RUN in a window: one budget.
1011
+ */
1012
+ test("the ordering sweeps together order at most ORDER_MAX_PER_RUN in a window", async () => {
1013
+ const max: number = kind.orderMaxPerRun;
1014
+
1015
+ // Verified and waiting for the order sweep.
1016
+ for (let i: number = 0; i < max; i++) {
1017
+ world.domains.push(
1018
+ makeDomain(`waiting${i}.acme.com`, { isCnameVerified: true }),
1019
+ );
1020
+ world.dnsLive.add(`waiting${i}.acme.com`);
1021
+ }
1022
+
1023
+ // Live but not verified yet: the CNAME sweep verifies and orders them.
1024
+ for (let i: number = 0; i < max; i++) {
1025
+ world.domains.push(makeDomain(`new${i}.acme.com`));
1026
+ world.dnsLive.add(`new${i}.acme.com`);
1027
+ }
1028
+
1029
+ // Marked ordered, certificate gone: the re-order sweep's.
1030
+ for (let i: number = 0; i < max; i++) {
1031
+ world.domains.push(
1032
+ makeDomain(`lost${i}.acme.com`, {
1033
+ isCnameVerified: true,
1034
+ isSslOrdered: true,
1035
+ }),
1036
+ );
1037
+ world.dnsLive.add(`lost${i}.acme.com`);
1038
+ }
1039
+
1040
+ await runSweeps();
1041
+
1042
+ expect(mockCaOrders).toHaveLength(max);
1043
+
1044
+ // The same window: nothing more.
1045
+ await runSweeps();
1046
+
1047
+ expect(mockCaOrders).toHaveLength(max);
1048
+
1049
+ // The next window orders the next batch.
1050
+ nextOnDemandWindow();
1051
+ await runSweeps();
1052
+
1053
+ expect(mockCaOrders).toHaveLength(max * 2);
1054
+ expect(new Set(mockCaOrders).size).toBe(max * 2);
1055
+ });
1056
+
1057
+ test("an order that outlives Check now's wait is answered as issuing, and finishes on its own", async () => {
1058
+ const domain: DomainRow = makeDomain("status.acme.com");
1059
+ world.domains.push(domain);
1060
+ world.dnsLive.add("status.acme.com");
1061
+
1062
+ const ca: { release: () => void; started: Promise<void> } =
1063
+ holdTheNextOrder();
1064
+
1065
+ const result: CustomDomainVerificationResult | null = await clickCheckNow(
1066
+ domain,
1067
+ { waitInMs: 10 },
1068
+ );
1069
+
1070
+ expect(result?.certificateStatus).toBe(
1071
+ CustomDomainCertificateStatus.Issuing,
1072
+ );
1073
+ expect(domain.isSslOrdered).toBe(false);
1074
+
1075
+ ca.release();
1076
+ await settle();
1077
+
1078
+ expect(domain.isSslOrdered).toBe(true);
1079
+ expect(world.certificates.has("status.acme.com")).toBe(true);
1080
+ expect(world.heldLocks.size).toBe(0);
1081
+ });
1082
+ },
1083
+ );
1084
+
1085
+ describe.each(KINDS)(
1086
+ "$name custom domain's certificate, hardened: the review's findings end to end",
1087
+ (forKind: Kind) => {
1088
+ beforeEach(() => {
1089
+ mockAuto = DEFAULT_AUTO;
1090
+ setUpWorld(forKind);
1091
+ });
1092
+
1093
+ afterEach(() => {
1094
+ jest.restoreAllMocks();
1095
+ });
1096
+
1097
+ /*
1098
+ * Review finding 2 (lock part): Reissue SSL ordered without the name's
1099
+ * lock, beside a Check now or a re-order of the same name - two orders,
1100
+ * and two http-01 challenges that remove each other's challenge rows.
1101
+ */
1102
+ test("Reissue SSL while another order of the name runs is refused, orders nothing, and keeps the day's reissue", async () => {
1103
+ const domain: DomainRow = makeDomain("status.acme.com", {
1104
+ isCnameVerified: true,
1105
+ isSslOrdered: true,
1106
+ });
1107
+ world.domains.push(domain);
1108
+ world.dnsLive.add("status.acme.com");
1109
+
1110
+ // The certificate went missing; the re-order sweep is ordering it.
1111
+ const ca: { release: () => void; started: Promise<void> } =
1112
+ holdTheNextOrder();
1113
+ const reorder: Promise<void> = service.checkOrderStatus();
1114
+ await ca.started;
1115
+
1116
+ await expect(service.reissueCert(domain.id)).rejects.toThrow(
1117
+ TooManyRequestsException,
1118
+ );
1119
+
1120
+ expect(mockCaOrders).toEqual(["status.acme.com"]);
1121
+ expect(domain.certificateReissueRequestedAt).toBeUndefined();
1122
+
1123
+ ca.release();
1124
+ await reorder;
1125
+
1126
+ // Nothing runs now: the reissue goes ahead, once.
1127
+ await service.reissueCert(domain.id);
1128
+
1129
+ expect(mockCaOrders).toEqual(["status.acme.com", "status.acme.com"]);
1130
+ expect(domain.certificateReissueRequestedAt).toBeInstanceOf(Date);
1131
+ expect(world.heldLocks.size).toBe(0);
1132
+ });
1133
+
1134
+ test("Check now while a reissue of the name runs orders nothing more", async () => {
1135
+ const domain: DomainRow = makeDomain("status.acme.com", {
1136
+ isCnameVerified: true,
1137
+ isSslOrdered: true,
1138
+ });
1139
+ world.domains.push(domain);
1140
+ world.dnsLive.add("status.acme.com");
1141
+ addCertificate("status.acme.com", -1);
1142
+
1143
+ const ca: { release: () => void; started: Promise<void> } =
1144
+ holdTheNextOrder();
1145
+ const reissue: Promise<void> = service.reissueCert(domain.id);
1146
+ await ca.started;
1147
+
1148
+ const result: CustomDomainVerificationResult | null =
1149
+ await clickCheckNow(domain);
1150
+
1151
+ expect(result?.certificateStatus).toBe(
1152
+ CustomDomainCertificateStatus.Issuing,
1153
+ );
1154
+ expect(mockCaOrders).toEqual(["status.acme.com"]);
1155
+
1156
+ ca.release();
1157
+ await reissue;
1158
+
1159
+ expect(mockCaOrders).toEqual(["status.acme.com"]);
1160
+ });
1161
+
1162
+ /*
1163
+ * Review finding 3: the route found the record, then the order checked it
1164
+ * once more. A DNS blip between the two marked the domain unverified and
1165
+ * failed the order, while the dialog said "Your CNAME record is verified."
1166
+ */
1167
+ test("Check now does not check the record twice: a DNS blip right after the route found it does not fail the order", async () => {
1168
+ const domain: DomainRow = makeDomain("status.acme.com");
1169
+ world.domains.push(domain);
1170
+ world.dnsLive.add("status.acme.com");
1171
+
1172
+ const result: CustomDomainVerificationResult | null = await clickCheckNow(
1173
+ domain,
1174
+ { dnsGoesAwayAfterTheCheck: true },
1175
+ );
1176
+
1177
+ expect(result?.certificateStatus).toBe(
1178
+ CustomDomainCertificateStatus.Issuing,
1179
+ );
1180
+ expect(mockCaOrders).toEqual(["status.acme.com"]);
1181
+ expect(domain.isCnameVerified).toBe(true);
1182
+ expect(domain.isSslOrdered).toBe(true);
1183
+ });
1184
+
1185
+ test("the CNAME sweep does not check the record twice either", async () => {
1186
+ const domain: DomainRow = makeDomain("status.acme.com");
1187
+ world.domains.push(domain);
1188
+ world.dnsLive.add("status.acme.com");
1189
+
1190
+ const isCnameValid: SpyInstance<typeof service.isCnameValid> = jest.spyOn(
1191
+ service,
1192
+ "isCnameValid",
1193
+ );
1194
+
1195
+ await service.verifyCnameWhoseCnameisNotVerified();
1196
+
1197
+ expect(mockCaOrders).toEqual(["status.acme.com"]);
1198
+ expect(isCnameValid).toHaveBeenCalledTimes(1);
1199
+ });
1200
+
1201
+ /*
1202
+ * Review finding 8: the CNAME sweep read the domain as unverified before
1203
+ * Check now verified it, then hit a DNS blip on it while Check now's order
1204
+ * ran - marking it unverified and unordered - and the order then marked
1205
+ * it ordered: "ordered", but "waiting for DNS".
1206
+ */
1207
+ test("a sweep's DNS blip while Check now orders never leaves the domain ordered but unverified", async () => {
1208
+ const domain: DomainRow = makeDomain("status.acme.com");
1209
+ world.domains.push(domain);
1210
+ world.dnsLive.add("status.acme.com");
1211
+
1212
+ const ca: { release: () => void; started: Promise<void> } =
1213
+ holdTheNextOrder();
1214
+ const checkNow: Promise<CustomDomainVerificationResult | null> =
1215
+ clickCheckNow(domain);
1216
+ await ca.started;
1217
+
1218
+ // The sweep's own check of the domain, during a blip.
1219
+ world.dnsLive.delete("status.acme.com");
1220
+ await service.isCnameValid("status.acme.com");
1221
+
1222
+ expect(domain.isCnameVerified).toBe(false);
1223
+
1224
+ world.dnsLive.add("status.acme.com");
1225
+ ca.release();
1226
+ await checkNow;
1227
+
1228
+ // Let's Encrypt fetched its challenge from the domain: it reaches us.
1229
+ expect(domain.isSslOrdered).toBe(true);
1230
+ expect(domain.isCnameVerified).toBe(true);
1231
+ });
1232
+
1233
+ test("a domain that has a certificate is recorded as ordered only while it is verified", async () => {
1234
+ const domain: DomainRow = makeDomain("status.acme.com", {
1235
+ isCnameVerified: true,
1236
+ });
1237
+ world.domains.push(domain);
1238
+ addCertificate("status.acme.com", 40);
1239
+
1240
+ // Read as verified by the order sweep, then a blip marks it unverified.
1241
+ let letTheSweepGoOn: () => void = (): void => {};
1242
+ holdOrderSweepRead = new Promise<void>((resolve: () => void) => {
1243
+ letTheSweepGoOn = resolve;
1244
+ });
1245
+
1246
+ const sweep: Promise<void> =
1247
+ service.orderSSLForDomainsWhichAreNotOrderedYet();
1248
+ await settle();
1249
+
1250
+ holdOrderSweepRead = null;
1251
+ await service.isCnameValid("status.acme.com");
1252
+
1253
+ letTheSweepGoOn();
1254
+ await sweep;
1255
+
1256
+ expect(domain.isCnameVerified).toBe(false);
1257
+ expect(domain.isSslOrdered).toBe(false);
1258
+ expect(mockCaOrders).toEqual([]);
1259
+ });
1260
+
1261
+ /*
1262
+ * Review finding 6: Check now said Issued - "already has its free SSL
1263
+ * certificate, and we renew it automatically" - for an expired
1264
+ * certificate, and for a domain marked ordered whose certificate was gone.
1265
+ */
1266
+ test("Check now renews an expired certificate, and does not call it issued", async () => {
1267
+ const domain: DomainRow = makeDomain("status.acme.com", {
1268
+ isCnameVerified: true,
1269
+ isSslOrdered: true,
1270
+ });
1271
+ world.domains.push(domain);
1272
+ world.dnsLive.add("status.acme.com");
1273
+ addCertificate("status.acme.com", -2);
1274
+
1275
+ const result: CustomDomainVerificationResult | null =
1276
+ await clickCheckNow(domain);
1277
+
1278
+ expect(result?.certificateStatus).toBe(
1279
+ CustomDomainCertificateStatus.Issuing,
1280
+ );
1281
+ expect(mockCaOrders).toEqual(["status.acme.com"]);
1282
+ expect(
1283
+ world.certificates.get("status.acme.com")!.expiresAt.getTime(),
1284
+ ).toBeGreaterThan(Date.now());
1285
+ expect(world.deletedCertificates).toEqual([]);
1286
+ });
1287
+
1288
+ test("Check now on a domain marked ordered whose certificate is gone orders it, and does not call it issued", async () => {
1289
+ const domain: DomainRow = makeDomain("status.acme.com", {
1290
+ isCnameVerified: true,
1291
+ isSslOrdered: true,
1292
+ });
1293
+ world.domains.push(domain);
1294
+ world.dnsLive.add("status.acme.com");
1295
+
1296
+ const result: CustomDomainVerificationResult | null =
1297
+ await clickCheckNow(domain);
1298
+
1299
+ expect(result?.certificateStatus).toBe(
1300
+ CustomDomainCertificateStatus.Issuing,
1301
+ );
1302
+ expect(mockCaOrders).toEqual(["status.acme.com"]);
1303
+ });
1304
+
1305
+ test("Check now calls a certificate issued only when it is there and valid", async () => {
1306
+ const domain: DomainRow = makeDomain("status.acme.com", {
1307
+ isCnameVerified: true,
1308
+ isSslOrdered: true,
1309
+ });
1310
+ world.domains.push(domain);
1311
+ world.dnsLive.add("status.acme.com");
1312
+ addCertificate("status.acme.com", 30);
1313
+
1314
+ const result: CustomDomainVerificationResult | null =
1315
+ await clickCheckNow(domain);
1316
+
1317
+ expect(result?.certificateStatus).toBe(
1318
+ CustomDomainCertificateStatus.Issued,
1319
+ );
1320
+ expect(mockCaOrders).toEqual([]);
1321
+ });
1322
+
1323
+ /*
1324
+ * Review finding 4 / honest status: a domain whose order kept failing
1325
+ * said "Issuing a free certificate" for days, with the reason only in the
1326
+ * worker's log. The Status column reads this.
1327
+ */
1328
+ test("a failed order is what the Status column reads, until an order succeeds", async () => {
1329
+ const domain: DomainRow = makeDomain("status.acme.com", {
1330
+ isCnameVerified: true,
1331
+ });
1332
+ world.domains.push(domain);
1333
+ world.dnsLive.add("status.acme.com");
1334
+
1335
+ mockAuto = async (): Promise<string> => {
1336
+ throw new Error("urn:ietf:params:acme:error:caa");
1337
+ };
1338
+
1339
+ await runSweeps();
1340
+
1341
+ const failed: Array<CustomDomainCertificate> =
1342
+ await service.getCertificates([domain as never]);
1343
+
1344
+ expect(failed).toEqual([
1345
+ {
1346
+ domainId: domain._id,
1347
+ expiresAt: undefined,
1348
+ lastOrderError: expect.stringContaining(
1349
+ "Unable to order certificate for status.acme.com",
1350
+ ),
1351
+ lastOrderFailedAt: expect.any(Date),
1352
+ },
1353
+ ]);
1354
+
1355
+ mockAuto = DEFAULT_AUTO;
1356
+ minutesLater(15);
1357
+ await runSweeps();
1358
+
1359
+ const issued: Array<CustomDomainCertificate> =
1360
+ await service.getCertificates([domain as never]);
1361
+
1362
+ expect(issued[0]!.lastOrderError).toBeUndefined();
1363
+ expect(issued[0]!.expiresAt).toBeInstanceOf(Date);
1364
+ });
1365
+
1366
+ test("a domain whose orders keep failing is ordered less and less often by the sweeps", async () => {
1367
+ const domain: DomainRow = makeDomain("status.acme.com", {
1368
+ isCnameVerified: true,
1369
+ });
1370
+ world.domains.push(domain);
1371
+ world.dnsLive.add("status.acme.com");
1372
+
1373
+ mockAuto = async (): Promise<string> => {
1374
+ throw new Error("urn:ietf:params:acme:error:caa");
1375
+ };
1376
+
1377
+ // Two hours of ticks, every 15 minutes.
1378
+ for (let tick: number = 0; tick < 8; tick++) {
1379
+ minutesLater(tick * 15);
1380
+ await runSweeps();
1381
+ }
1382
+
1383
+ /*
1384
+ * At 0, 15 and 45 minutes, then at 1 hour 45: four orders in eight
1385
+ * ticks, where every tick used to order one.
1386
+ */
1387
+ expect(mockCaOrders).toHaveLength(4);
1388
+
1389
+ const failure: Map<string, unknown> = await CertificateOrderFailures.get([
1390
+ "status.acme.com",
1391
+ ]);
1392
+
1393
+ expect(failure.size).toBe(1);
1394
+ });
1395
+
1396
+ /*
1397
+ * Review finding 5: each sweep and run capped only itself, and the caps
1398
+ * added up past the 300 new orders per three hours of the account the
1399
+ * whole installation shares. Check now, the sweeps and the renewal run
1400
+ * now draw from one budget per window.
1401
+ */
1402
+ test("Check now, the sweeps and the renewal run together stay within the installation's budget, renewals first", async () => {
1403
+ // Due for renewal: a full renewal run.
1404
+ for (let i: number = 0; i < 10; i++) {
1405
+ const renewing: DomainRow = makeDomain(`renew${i}.acme.com`, {
1406
+ isCnameVerified: true,
1407
+ isSslOrdered: true,
1408
+ isSslProvisioned: true,
1409
+ });
1410
+ world.domains.push(renewing);
1411
+ world.dnsLive.add(renewing.fullDomain);
1412
+ world.served.add(renewing.fullDomain);
1413
+ addCertificate(renewing.fullDomain, 2);
1414
+ }
1415
+
1416
+ // Verified, waiting for the order sweep.
1417
+ for (let i: number = 0; i < 10; i++) {
1418
+ world.domains.push(
1419
+ makeDomain(`waiting${i}.acme.com`, { isCnameVerified: true }),
1420
+ );
1421
+ world.dnsLive.add(`waiting${i}.acme.com`);
1422
+ }
1423
+
1424
+ // Clicks on Check now, on domains whose record just went live.
1425
+ const clicked: Array<DomainRow> = [];
1426
+
1427
+ for (let i: number = 0; i < 10; i++) {
1428
+ const domain: DomainRow = makeDomain(`clicked${i}.acme.com`);
1429
+ world.domains.push(domain);
1430
+ world.dnsLive.add(domain.fullDomain);
1431
+ clicked.push(domain);
1432
+ }
1433
+
1434
+ // The new certificates come first in this window...
1435
+ for (const domain of clicked) {
1436
+ await clickCheckNow(domain);
1437
+ }
1438
+
1439
+ await service.orderSSLForDomainsWhichAreNotOrderedYet();
1440
+
1441
+ const newCertificates: number = mockCaOrders.length;
1442
+
1443
+ expect(newCertificates).toBeLessThanOrEqual(
1444
+ CertificateOrderBudget.NEW_CERTIFICATE_ORDERS_PER_WINDOW,
1445
+ );
1446
+
1447
+ // ...and the renewals still have their share.
1448
+ await service.renewCertsWhichAreExpiringSoon();
1449
+
1450
+ expect(mockCaOrders.length).toBeLessThanOrEqual(
1451
+ CertificateOrderBudget.ORDERS_PER_WINDOW,
1452
+ );
1453
+ // As many as the run renews, up to what the new certificates left.
1454
+ expect(mockCaOrders.length - newCertificates).toBeGreaterThanOrEqual(
1455
+ Math.min(
1456
+ kind.renewalsPerRun,
1457
+ CertificateOrderBudget.ORDERS_PER_WINDOW -
1458
+ CertificateOrderBudget.NEW_CERTIFICATE_ORDERS_PER_WINDOW,
1459
+ ),
1460
+ );
1461
+ expect(world.deletedCertificates).toEqual([]);
1462
+ });
1463
+
1464
+ /*
1465
+ * Finding (h): renewal still deleted a certificate whose CNAME check
1466
+ * failed, weeks before it expired. And (review of this change) its CNAME
1467
+ * check still marked the domain unverified, unordered and unprovisioned -
1468
+ * "Waiting for DNS", Reissue SSL refused - while the certificate kept
1469
+ * serving.
1470
+ */
1471
+ test("a DNS blip during a renewal keeps the certificate and the domain as they are, says why, and a later run renews it", async () => {
1472
+ const domain: DomainRow = makeDomain("status.acme.com", {
1473
+ isCnameVerified: true,
1474
+ isSslOrdered: true,
1475
+ isSslProvisioned: true,
1476
+ });
1477
+ world.domains.push(domain);
1478
+ world.served.add("status.acme.com");
1479
+ addCertificate("status.acme.com", 10);
1480
+
1481
+ const before: string =
1482
+ world.certificates.get("status.acme.com")!.certificate;
1483
+
1484
+ await service.renewCertsWhichAreExpiringSoon();
1485
+
1486
+ expect(world.deletedCertificates).toEqual([]);
1487
+ expect(world.certificates.get("status.acme.com")?.certificate).toBe(
1488
+ before,
1489
+ );
1490
+ expect(mockCaOrders).toEqual([]);
1491
+
1492
+ // The row is untouched...
1493
+ expect(domain.isCnameVerified).toBe(true);
1494
+ expect(domain.isSslOrdered).toBe(true);
1495
+ expect(domain.isSslProvisioned).toBe(true);
1496
+
1497
+ // ...and the Status column learns why the renewal did not happen.
1498
+ const certificates: Array<CustomDomainCertificate> =
1499
+ await service.getCertificates([domain as never]);
1500
+
1501
+ expect(certificates[0]!.lastOrderError).toContain(
1502
+ "CNAME record could not be verified",
1503
+ );
1504
+
1505
+ world.dnsLive.add("status.acme.com");
1506
+
1507
+ // The name waits a little before the next try...
1508
+ await service.renewCertsWhichAreExpiringSoon();
1509
+
1510
+ expect(mockCaOrders).toEqual([]);
1511
+
1512
+ // ...and is renewed once its delay is up.
1513
+ minutesLater(15);
1514
+ await service.renewCertsWhichAreExpiringSoon();
1515
+
1516
+ expect(mockCaOrders).toEqual(["status.acme.com"]);
1517
+ expect(world.deletedCertificates).toEqual([]);
1518
+ expect(
1519
+ (await service.getCertificates([domain as never]))[0]!.lastOrderError,
1520
+ ).toBeUndefined();
1521
+ });
1522
+
1523
+ /*
1524
+ * Review of this change: a Check now refused for the window's orders being
1525
+ * used up kept the domain's window, so the next click within 15 minutes
1526
+ * only said "Issuing" again although nothing had been ordered.
1527
+ */
1528
+ test("Check now that ordered nothing - the orders used up - leaves the window to the next click", async () => {
1529
+ const domain: DomainRow = makeDomain("status.acme.com");
1530
+ world.domains.push(domain);
1531
+ world.dnsLive.add("status.acme.com");
1532
+
1533
+ // This window's new certificates are all spent.
1534
+ world.cache.set(
1535
+ `${CertificateOrderBudget.NAMESPACE}-window-${CertificateOrderBudget.getWindowIndex(new Date())}`,
1536
+ String(CertificateOrderBudget.NEW_CERTIFICATE_ORDERS_PER_WINDOW),
1537
+ );
1538
+
1539
+ const refused: CustomDomainVerificationResult | null =
1540
+ await clickCheckNow(domain);
1541
+
1542
+ expect(refused?.certificateStatus).toBe(
1543
+ CustomDomainCertificateStatus.Issuing,
1544
+ );
1545
+ expect(mockCaOrders).toEqual([]);
1546
+
1547
+ // The next window: the next click orders straight away.
1548
+ minutesLater(15);
1549
+
1550
+ const ordered: CustomDomainVerificationResult | null =
1551
+ await clickCheckNow(domain);
1552
+
1553
+ expect(ordered?.certificateStatus).toBe(
1554
+ CustomDomainCertificateStatus.Issuing,
1555
+ );
1556
+ expect(mockCaOrders).toEqual(["status.acme.com"]);
1557
+ });
1558
+ },
1559
+ );