@oneuptime/common 12.0.28 → 12.0.30

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 (367) hide show
  1. package/Models/DatabaseModels/DashboardDomain.ts +43 -0
  2. package/Models/DatabaseModels/Index.ts +11 -0
  3. package/Models/DatabaseModels/MonitorTemplate.ts +15 -3
  4. package/Models/DatabaseModels/OnCallDutyPolicySchedule.ts +43 -0
  5. package/Models/DatabaseModels/OnCallDutyPolicyScheduleCalendarFeed.ts +662 -0
  6. package/Models/DatabaseModels/ProjectOnCallCalendarFeed.ts +584 -0
  7. package/Models/DatabaseModels/StatusPageDomain.ts +43 -0
  8. package/Models/DatabaseModels/UserOnCallCalendarFeed.ts +546 -0
  9. package/Models/DatabaseModels/UserOnCallShiftReminder.ts +231 -0
  10. package/Models/DatabaseModels/UserOnCallShiftReminderLog.ts +331 -0
  11. package/Server/API/DashboardDomainAPI.ts +74 -0
  12. package/Server/API/MicrosoftTeamsAPI.ts +81 -180
  13. package/Server/API/OnCallCalendarAPI.ts +2194 -0
  14. package/Server/API/StatusPageDomainAPI.ts +74 -0
  15. package/Server/EnvironmentConfig.ts +138 -0
  16. package/Server/Infrastructure/OnCallCalendarFeedCache.ts +1050 -0
  17. package/Server/Infrastructure/Postgres/SchemaMigrations/1790200000000-AllowNullMonitorNameOnMonitorTemplate.ts +43 -0
  18. package/Server/Infrastructure/Postgres/SchemaMigrations/1790300000000-AddCertificateReissueRequestedAtToDomains.ts +40 -0
  19. package/Server/Infrastructure/Postgres/SchemaMigrations/1790400000000-AddOnCallCalendarFeeds.ts +253 -0
  20. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +6 -0
  21. package/Server/Middleware/OnCallCalendarFeedRateLimit.ts +487 -0
  22. package/Server/Middleware/ProjectAuthorization.ts +6 -1
  23. package/Server/Services/DashboardDomainService.ts +131 -0
  24. package/Server/Services/Index.ts +12 -0
  25. package/Server/Services/LogPipelineProcessorService.ts +88 -0
  26. package/Server/Services/MonitorTemplateService.ts +70 -11
  27. package/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.ts +32 -0
  28. package/Server/Services/OnCallDutyPolicyScheduleCalendarFeedService.ts +260 -0
  29. package/Server/Services/OnCallDutyPolicyScheduleLayerService.ts +70 -3
  30. package/Server/Services/OnCallDutyPolicyScheduleLayerUserService.ts +138 -3
  31. package/Server/Services/OnCallDutyPolicyScheduleService.ts +1465 -134
  32. package/Server/Services/OnCallDutyPolicyUserOverrideService.ts +167 -31
  33. package/Server/Services/ProjectOnCallCalendarFeedService.ts +192 -0
  34. package/Server/Services/StatusPageDomainService.ts +163 -3
  35. package/Server/Services/StatusPagePrivateUserSessionService.ts +197 -56
  36. package/Server/Services/TeamMemberService.ts +489 -1
  37. package/Server/Services/TeamService.ts +50 -0
  38. package/Server/Services/UserNotificationSettingService.ts +44 -1
  39. package/Server/Services/UserOnCallCalendarFeedService.ts +159 -0
  40. package/Server/Services/UserOnCallShiftReminderLogService.ts +151 -0
  41. package/Server/Services/UserOnCallShiftReminderService.ts +112 -0
  42. package/Server/Types/Domain.ts +17 -0
  43. package/Server/Utils/APIKey/AccessPermission.ts +24 -0
  44. package/Server/Utils/Greenlock/Greenlock.ts +156 -58
  45. package/Server/Utils/LogPipelineProcessorValidation.ts +112 -0
  46. package/Server/Utils/OnCall/CalendarFeedToken.ts +241 -0
  47. package/Server/Utils/OnCall/OnCallCalendarFeedRenderer.ts +1488 -0
  48. package/Server/Utils/OnCall/OnCallCalendarFeedUrls.ts +230 -0
  49. package/Server/Utils/OnCall/OnCallShiftChangeListeners.ts +227 -0
  50. package/Server/Utils/OnCall/OnCallShiftMaterializer.ts +1127 -0
  51. package/Server/Utils/OnCall/OnCallShiftReminderListener.ts +174 -0
  52. package/Server/Utils/OnCall/OnCallShiftReminderRunner.ts +2584 -0
  53. package/Server/Utils/Response.ts +134 -0
  54. package/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsClient.ts +1094 -45
  55. package/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsPoller.ts +153 -17
  56. package/Server/Utils/StartServer.ts +10 -0
  57. package/Server/Utils/Telemetry/AppMetrics.ts +51 -0
  58. package/Server/Utils/Workspace/MicrosoftTeams/Actions/Alert.ts +23 -11
  59. package/Server/Utils/Workspace/MicrosoftTeams/Actions/Auth.ts +0 -170
  60. package/Server/Utils/Workspace/MicrosoftTeams/Actions/Authorization.ts +158 -0
  61. package/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.ts +23 -11
  62. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +35 -13
  63. package/Tests/App/Dashboard/MonitorTemplateColumnAndFacet.test.tsx +505 -0
  64. package/Tests/App/Dashboard/OnCallCalendarFeedLinks.test.tsx +229 -0
  65. package/Tests/App/Dashboard/OnCallCalendarFeedPlanGate.test.tsx +489 -0
  66. package/Tests/App/Dashboard/OnCallCalendarFeedSideMenus.test.tsx +149 -0
  67. package/Tests/App/Dashboard/OnCallCalendarFeedUtil.test.ts +855 -0
  68. package/Tests/App/Dashboard/OnCallPersonalCalendarFeed.test.tsx +1209 -0
  69. package/Tests/App/Dashboard/OnCallSharedCalendarFeedCard.test.tsx +1019 -0
  70. package/Tests/App/Dashboard/OnCallShiftRemindersAndUpcomingShifts.test.tsx +655 -0
  71. package/Tests/App/Dashboard/UserSettingsSetupChecklistModel.test.ts +73 -0
  72. package/Tests/App/StatusPage/StatusPageLoginCodeBootstrap.test.ts +106 -0
  73. package/Tests/App/StatusPage/StatusPageLoginCodeUtil.test.ts +84 -0
  74. package/Tests/App/StatusPage/StatusPageRedirectSafety.test.ts +119 -0
  75. package/Tests/Models/MonitorTemplateMonitorNameColumn.test.ts +258 -0
  76. package/Tests/Models/OnCallCalendarFeedModels.test.ts +642 -0
  77. package/Tests/Models/OnCallDutyPolicyScheduleCalendarFeed.test.ts +288 -0
  78. package/Tests/Models/OnCallDutyPolicyScheduleShiftConfigVersion.test.ts +88 -0
  79. package/Tests/Models/UserOnCallShiftReminderModels.test.ts +368 -0
  80. package/Tests/Server/API/CustomDomainReissueSslAPI.test.ts +469 -0
  81. package/Tests/Server/API/Helpers.ts +8 -0
  82. package/Tests/Server/API/MicrosoftTeamsBotMessagesEndpoint.test.ts +729 -0
  83. package/Tests/Server/API/MicrosoftTeamsBotTestEndpoint.test.ts +26 -0
  84. package/Tests/Server/API/MicrosoftTeamsLegacyWebhookRemoval.test.ts +402 -0
  85. package/Tests/Server/API/OnCallCalendarAPI.test.ts +4461 -0
  86. package/Tests/Server/EnvironmentConfigOnCallCalendarFeed.test.ts +357 -0
  87. package/Tests/Server/Infrastructure/OnCallCalendarFeedCache.test.ts +1589 -0
  88. package/Tests/Server/Infrastructure/Postgres/AddOnCallCalendarFeedsMigration.test.ts +369 -0
  89. package/Tests/Server/Middleware/OnCallCalendarFeedRateLimit.test.ts +1365 -0
  90. package/Tests/Server/Middleware/ProjectAuthorizationApiKeyMiddleware.test.ts +4 -0
  91. package/Tests/Server/Services/AddCertificateReissueRequestedAtToDomainsMigration.test.ts +287 -0
  92. package/Tests/Server/Services/CustomDomainCertificateReissue.test.ts +477 -0
  93. package/Tests/Server/Services/FeedRetentionConsistency.test.ts +70 -0
  94. package/Tests/Server/Services/LogPipelineProcessorSaveValidation.test.ts +249 -0
  95. package/Tests/Server/Services/MonitorTemplateServiceBulkSync.test.ts +549 -0
  96. package/Tests/Server/Services/MonitorTemplateServiceNetworkDeviceSync.test.ts +16 -3
  97. package/Tests/Server/Services/NetworkDeviceAutoImportRuleEngineService.test.ts +57 -0
  98. package/Tests/Server/Services/OnCallCalendarFeedServices.test.ts +1103 -0
  99. package/Tests/Server/Services/OnCallDutyPolicyScheduleResolver.test.ts +1037 -0
  100. package/Tests/Server/Services/OnCallShiftConfigPropagation.test.ts +1270 -0
  101. package/Tests/Server/Services/SecurityEventLastErrorColumnWidth.test.ts +151 -15
  102. package/Tests/Server/Services/StatusPagePrivateUserSessionService.test.ts +587 -0
  103. package/Tests/Server/Services/TeamDeleteMemberCleanup.test.ts +156 -0
  104. package/Tests/Server/Services/TeamMemberOnCallCleanup.test.ts +755 -0
  105. package/Tests/Server/Services/UserNotificationSettingShiftReminderDefaults.test.ts +267 -0
  106. package/Tests/Server/Services/UserOnCallShiftReminderServices.test.ts +583 -0
  107. package/Tests/Server/Types/Database/Permissions/ApiKeyFileAccess.test.ts +286 -0
  108. package/Tests/Server/Types/Database/Permissions/MonitorTemplateColumnAccess.test.ts +328 -0
  109. package/Tests/Server/Types/Database/Permissions/OnCallCalendarFeedPermissions.test.ts +254 -0
  110. package/Tests/Server/Types/Database/QueryHelperFindWithSameTextAndIsNull.test.ts +83 -0
  111. package/Tests/Server/Utils/AI/SRE/ConfidenceSignal.test.ts +377 -0
  112. package/Tests/Server/Utils/APIKey/AccessPermission.test.ts +16 -1
  113. package/Tests/Server/Utils/Greenlock/CertificateRenewalSchedule.test.ts +250 -0
  114. package/Tests/Server/Utils/LogPipelineProcessorValidation.test.ts +198 -0
  115. package/Tests/Server/Utils/OnCall/CalendarFeedToken.test.ts +467 -0
  116. package/Tests/Server/Utils/OnCall/OnCallCalendarFeedRenderer.test.ts +2142 -0
  117. package/Tests/Server/Utils/OnCall/OnCallCalendarFeedUrls.test.ts +278 -0
  118. package/Tests/Server/Utils/OnCall/OnCallResolverTestHarness.ts +440 -0
  119. package/Tests/Server/Utils/OnCall/OnCallShiftChangeListeners.test.ts +314 -0
  120. package/Tests/Server/Utils/OnCall/OnCallShiftMaterializer.test.ts +1502 -0
  121. package/Tests/Server/Utils/OnCall/OnCallShiftReminderListener.test.ts +491 -0
  122. package/Tests/Server/Utils/OnCall/OnCallShiftReminderRunner.test.ts +3088 -0
  123. package/Tests/Server/Utils/OnCall/OnCallShiftReminderTestHarness.ts +910 -0
  124. package/Tests/Server/Utils/ResponseSendCalendarResponse.test.ts +561 -0
  125. package/Tests/Server/Utils/SecurityEvent/ConnectorErrorMessage.test.ts +48 -9
  126. package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsAuth.test.ts +726 -0
  127. package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsClient.test.ts +74 -11
  128. package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsErrorHandling.test.ts +1107 -0
  129. package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsPoller.test.ts +20 -3
  130. package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsPollerFailureTaxonomy.test.ts +51 -11
  131. package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsPollerHardening.test.ts +1246 -0
  132. package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsRequestContract.test.ts +961 -0
  133. package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsResponseParsing.test.ts +653 -0
  134. package/Tests/Server/Utils/StartServerEncryptionSecretWarning.test.ts +64 -0
  135. package/Tests/Server/Utils/Telemetry/AppMetrics.test.ts +29 -0
  136. package/Tests/Server/Utils/Workspace/MicrosoftTeamsActionAuthorization.test.ts +880 -0
  137. package/Tests/Types/API/HTTPResponse.test.ts +175 -0
  138. package/Tests/Types/API/Route.test.ts +36 -4
  139. package/Tests/Types/Calendar/ICalendar.test.ts +488 -0
  140. package/Tests/Types/Database/DatabaseProperty.test.ts +171 -0
  141. package/Tests/Types/DateLocalShortDateTimeString.test.ts +518 -0
  142. package/Tests/Types/DateLocalTimeString.test.ts +259 -0
  143. package/Tests/Types/DateUserPrefers12HourFormat.test.ts +316 -0
  144. package/Tests/Types/NotificationSetting/NotificationSettingEventType.test.ts +53 -0
  145. package/Tests/Types/OnCallDutyPolicy/CalendarFeedTestFixtures.ts +310 -0
  146. package/Tests/Types/OnCallDutyPolicy/CalendarFeedWindow.test.ts +205 -0
  147. package/Tests/Types/OnCallDutyPolicy/LayerUtilLayerMetaAndIterationCap.test.ts +458 -0
  148. package/Tests/Types/OnCallDutyPolicy/MaterializedShift.test.ts +197 -0
  149. package/Tests/Types/OnCallDutyPolicy/OnCallCalendarFeedUtil.test.ts +1986 -0
  150. package/Tests/Types/OnCallDutyPolicy/ScheduleShiftUtilGroupKey.test.ts +678 -0
  151. package/Tests/Types/OnCallDutyPolicy/ShiftSeamUtil.test.ts +417 -0
  152. package/Tests/UI/Components/LogsHistogram.test.tsx +41 -2
  153. package/Tests/UI/Components/LogsViewerEmptyStateTimeRange.test.tsx +212 -0
  154. package/Tests/UI/Components/TelemetryChartClockFormat.test.tsx +359 -0
  155. package/Tests/UI/Components/TimeRangePickerClockFormat.test.tsx +422 -0
  156. package/Tests/UI/Components/TimeRangePickerDropdown.test.tsx +146 -8
  157. package/Tests/UI/MicrosoftTeams/MicrosoftTeamsMessagingEndpointGuidance.test.tsx +520 -0
  158. package/Tests/UI/Utils/Breadcrumb/fixtures/RealBreadcrumbTrails.ts +10 -0
  159. package/Tests/UI/Utils/Breadcrumb/fixtures/RealRoutePatterns.ts +2 -0
  160. package/Tests/UI/Utils/Navigation.test.ts +82 -0
  161. package/Tests/Utils/Array.test.ts +100 -0
  162. package/Tests/Utils/CertificateReissue.test.ts +225 -0
  163. package/Tests/Utils/Grok/Grok.test.ts +492 -0
  164. package/Tests/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.test.ts +181 -13
  165. package/Types/API/Route.ts +20 -5
  166. package/Types/Calendar/ICalendar.ts +442 -0
  167. package/Types/Date.ts +143 -13
  168. package/Types/Email/EmailTemplateType.ts +2 -0
  169. package/Types/NotificationSetting/NotificationSettingEventType.ts +4 -0
  170. package/Types/OnCallDutyPolicy/CalendarFeedWindow.ts +140 -0
  171. package/Types/OnCallDutyPolicy/Layer.ts +190 -35
  172. package/Types/OnCallDutyPolicy/MaterializedShift.ts +274 -0
  173. package/Types/OnCallDutyPolicy/OnCallCalendarFeedUtil.ts +1415 -0
  174. package/Types/OnCallDutyPolicy/ScheduleShiftUtil.ts +159 -4
  175. package/Types/OnCallDutyPolicy/ShiftSeamUtil.ts +106 -0
  176. package/Types/Rum/SessionReplay.ts +62 -1
  177. package/UI/Components/Date/TimeRangePickerDropdown.tsx +9 -5
  178. package/UI/Components/LogsViewer/LogsViewer.tsx +12 -7
  179. package/UI/Components/LogsViewer/components/HistogramTooltip.tsx +18 -17
  180. package/UI/Components/LogsViewer/components/LogsAnalyticsView.tsx +21 -28
  181. package/UI/Components/LogsViewer/components/LogsHistogram.tsx +2 -4
  182. package/UI/Components/TelemetryViewer/components/TelemetryHistogram.tsx +2 -4
  183. package/UI/Components/TelemetryViewer/components/TelemetryHistogramTooltip.tsx +17 -16
  184. package/UI/Utils/Navigation.ts +42 -0
  185. package/Utils/Array.ts +37 -0
  186. package/Utils/CertificateReissue.ts +128 -0
  187. package/Utils/Grok/Grok.ts +461 -0
  188. package/Utils/Grok/GrokPatterns.ts +118 -0
  189. package/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.ts +58 -6
  190. package/build/dist/Models/DatabaseModels/DashboardDomain.js +44 -0
  191. package/build/dist/Models/DatabaseModels/DashboardDomain.js.map +1 -1
  192. package/build/dist/Models/DatabaseModels/Index.js +11 -0
  193. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  194. package/build/dist/Models/DatabaseModels/MonitorTemplate.js +17 -4
  195. package/build/dist/Models/DatabaseModels/MonitorTemplate.js.map +1 -1
  196. package/build/dist/Models/DatabaseModels/OnCallDutyPolicySchedule.js +44 -0
  197. package/build/dist/Models/DatabaseModels/OnCallDutyPolicySchedule.js.map +1 -1
  198. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyScheduleCalendarFeed.js +699 -0
  199. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyScheduleCalendarFeed.js.map +1 -0
  200. package/build/dist/Models/DatabaseModels/ProjectOnCallCalendarFeed.js +620 -0
  201. package/build/dist/Models/DatabaseModels/ProjectOnCallCalendarFeed.js.map +1 -0
  202. package/build/dist/Models/DatabaseModels/StatusPageDomain.js +44 -0
  203. package/build/dist/Models/DatabaseModels/StatusPageDomain.js.map +1 -1
  204. package/build/dist/Models/DatabaseModels/UserOnCallCalendarFeed.js +585 -0
  205. package/build/dist/Models/DatabaseModels/UserOnCallCalendarFeed.js.map +1 -0
  206. package/build/dist/Models/DatabaseModels/UserOnCallShiftReminder.js +247 -0
  207. package/build/dist/Models/DatabaseModels/UserOnCallShiftReminder.js.map +1 -0
  208. package/build/dist/Models/DatabaseModels/UserOnCallShiftReminderLog.js +355 -0
  209. package/build/dist/Models/DatabaseModels/UserOnCallShiftReminderLog.js.map +1 -0
  210. package/build/dist/Server/API/DashboardDomainAPI.js +43 -1
  211. package/build/dist/Server/API/DashboardDomainAPI.js.map +1 -1
  212. package/build/dist/Server/API/MicrosoftTeamsAPI.js +74 -118
  213. package/build/dist/Server/API/MicrosoftTeamsAPI.js.map +1 -1
  214. package/build/dist/Server/API/OnCallCalendarAPI.js +1371 -0
  215. package/build/dist/Server/API/OnCallCalendarAPI.js.map +1 -0
  216. package/build/dist/Server/API/StatusPageDomainAPI.js +43 -1
  217. package/build/dist/Server/API/StatusPageDomainAPI.js.map +1 -1
  218. package/build/dist/Server/EnvironmentConfig.js +102 -0
  219. package/build/dist/Server/EnvironmentConfig.js.map +1 -1
  220. package/build/dist/Server/Infrastructure/OnCallCalendarFeedCache.js +663 -0
  221. package/build/dist/Server/Infrastructure/OnCallCalendarFeedCache.js.map +1 -0
  222. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790200000000-AllowNullMonitorNameOnMonitorTemplate.js +34 -0
  223. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790200000000-AllowNullMonitorNameOnMonitorTemplate.js.map +1 -0
  224. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790300000000-AddCertificateReissueRequestedAtToDomains.js +28 -0
  225. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790300000000-AddCertificateReissueRequestedAtToDomains.js.map +1 -0
  226. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790400000000-AddOnCallCalendarFeeds.js +103 -0
  227. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790400000000-AddOnCallCalendarFeeds.js.map +1 -0
  228. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +6 -0
  229. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  230. package/build/dist/Server/Middleware/OnCallCalendarFeedRateLimit.js +342 -0
  231. package/build/dist/Server/Middleware/OnCallCalendarFeedRateLimit.js.map +1 -0
  232. package/build/dist/Server/Middleware/ProjectAuthorization.js +6 -1
  233. package/build/dist/Server/Middleware/ProjectAuthorization.js.map +1 -1
  234. package/build/dist/Server/Services/DashboardDomainService.js +104 -0
  235. package/build/dist/Server/Services/DashboardDomainService.js.map +1 -1
  236. package/build/dist/Server/Services/Index.js +12 -0
  237. package/build/dist/Server/Services/Index.js.map +1 -1
  238. package/build/dist/Server/Services/LogPipelineProcessorService.js +65 -0
  239. package/build/dist/Server/Services/LogPipelineProcessorService.js.map +1 -1
  240. package/build/dist/Server/Services/MonitorTemplateService.js +57 -11
  241. package/build/dist/Server/Services/MonitorTemplateService.js.map +1 -1
  242. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.js +20 -0
  243. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.js.map +1 -1
  244. package/build/dist/Server/Services/OnCallDutyPolicyScheduleCalendarFeedService.js +219 -0
  245. package/build/dist/Server/Services/OnCallDutyPolicyScheduleCalendarFeedService.js.map +1 -0
  246. package/build/dist/Server/Services/OnCallDutyPolicyScheduleLayerService.js +47 -3
  247. package/build/dist/Server/Services/OnCallDutyPolicyScheduleLayerService.js.map +1 -1
  248. package/build/dist/Server/Services/OnCallDutyPolicyScheduleLayerUserService.js +112 -3
  249. package/build/dist/Server/Services/OnCallDutyPolicyScheduleLayerUserService.js.map +1 -1
  250. package/build/dist/Server/Services/OnCallDutyPolicyScheduleService.js +955 -9
  251. package/build/dist/Server/Services/OnCallDutyPolicyScheduleService.js.map +1 -1
  252. package/build/dist/Server/Services/OnCallDutyPolicyUserOverrideService.js +93 -6
  253. package/build/dist/Server/Services/OnCallDutyPolicyUserOverrideService.js.map +1 -1
  254. package/build/dist/Server/Services/ProjectOnCallCalendarFeedService.js +157 -0
  255. package/build/dist/Server/Services/ProjectOnCallCalendarFeedService.js.map +1 -0
  256. package/build/dist/Server/Services/StatusPageDomainService.js +132 -3
  257. package/build/dist/Server/Services/StatusPageDomainService.js.map +1 -1
  258. package/build/dist/Server/Services/StatusPagePrivateUserSessionService.js +112 -32
  259. package/build/dist/Server/Services/StatusPagePrivateUserSessionService.js.map +1 -1
  260. package/build/dist/Server/Services/TeamMemberService.js +396 -1
  261. package/build/dist/Server/Services/TeamMemberService.js.map +1 -1
  262. package/build/dist/Server/Services/TeamService.js +43 -0
  263. package/build/dist/Server/Services/TeamService.js.map +1 -1
  264. package/build/dist/Server/Services/UserNotificationSettingService.js +26 -1
  265. package/build/dist/Server/Services/UserNotificationSettingService.js.map +1 -1
  266. package/build/dist/Server/Services/UserOnCallCalendarFeedService.js +140 -0
  267. package/build/dist/Server/Services/UserOnCallCalendarFeedService.js.map +1 -0
  268. package/build/dist/Server/Services/UserOnCallShiftReminderLogService.js +116 -0
  269. package/build/dist/Server/Services/UserOnCallShiftReminderLogService.js.map +1 -0
  270. package/build/dist/Server/Services/UserOnCallShiftReminderService.js +92 -0
  271. package/build/dist/Server/Services/UserOnCallShiftReminderService.js.map +1 -0
  272. package/build/dist/Server/Types/Domain.js +16 -0
  273. package/build/dist/Server/Types/Domain.js.map +1 -1
  274. package/build/dist/Server/Utils/APIKey/AccessPermission.js +24 -0
  275. package/build/dist/Server/Utils/APIKey/AccessPermission.js.map +1 -1
  276. package/build/dist/Server/Utils/Greenlock/Greenlock.js +110 -35
  277. package/build/dist/Server/Utils/Greenlock/Greenlock.js.map +1 -1
  278. package/build/dist/Server/Utils/LogPipelineProcessorValidation.js +77 -0
  279. package/build/dist/Server/Utils/LogPipelineProcessorValidation.js.map +1 -0
  280. package/build/dist/Server/Utils/OnCall/CalendarFeedToken.js +161 -0
  281. package/build/dist/Server/Utils/OnCall/CalendarFeedToken.js.map +1 -0
  282. package/build/dist/Server/Utils/OnCall/OnCallCalendarFeedRenderer.js +983 -0
  283. package/build/dist/Server/Utils/OnCall/OnCallCalendarFeedRenderer.js.map +1 -0
  284. package/build/dist/Server/Utils/OnCall/OnCallCalendarFeedUrls.js +155 -0
  285. package/build/dist/Server/Utils/OnCall/OnCallCalendarFeedUrls.js.map +1 -0
  286. package/build/dist/Server/Utils/OnCall/OnCallShiftChangeListeners.js +145 -0
  287. package/build/dist/Server/Utils/OnCall/OnCallShiftChangeListeners.js.map +1 -0
  288. package/build/dist/Server/Utils/OnCall/OnCallShiftMaterializer.js +752 -0
  289. package/build/dist/Server/Utils/OnCall/OnCallShiftMaterializer.js.map +1 -0
  290. package/build/dist/Server/Utils/OnCall/OnCallShiftReminderListener.js +92 -0
  291. package/build/dist/Server/Utils/OnCall/OnCallShiftReminderListener.js.map +1 -0
  292. package/build/dist/Server/Utils/OnCall/OnCallShiftReminderRunner.js +1786 -0
  293. package/build/dist/Server/Utils/OnCall/OnCallShiftReminderRunner.js.map +1 -0
  294. package/build/dist/Server/Utils/Response.js +97 -0
  295. package/build/dist/Server/Utils/Response.js.map +1 -1
  296. package/build/dist/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsClient.js +777 -36
  297. package/build/dist/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsClient.js.map +1 -1
  298. package/build/dist/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsPoller.js +108 -17
  299. package/build/dist/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsPoller.js.map +1 -1
  300. package/build/dist/Server/Utils/StartServer.js +9 -1
  301. package/build/dist/Server/Utils/StartServer.js.map +1 -1
  302. package/build/dist/Server/Utils/Telemetry/AppMetrics.js +41 -0
  303. package/build/dist/Server/Utils/Telemetry/AppMetrics.js.map +1 -1
  304. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Alert.js +17 -6
  305. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Alert.js.map +1 -1
  306. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Auth.js +0 -159
  307. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Auth.js.map +1 -1
  308. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Authorization.js +128 -0
  309. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Authorization.js.map +1 -0
  310. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.js +17 -6
  311. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.js.map +1 -1
  312. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +25 -13
  313. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
  314. package/build/dist/Types/API/Route.js +16 -5
  315. package/build/dist/Types/API/Route.js.map +1 -1
  316. package/build/dist/Types/Calendar/ICalendar.js +290 -0
  317. package/build/dist/Types/Calendar/ICalendar.js.map +1 -0
  318. package/build/dist/Types/Date.js +115 -13
  319. package/build/dist/Types/Date.js.map +1 -1
  320. package/build/dist/Types/Email/EmailTemplateType.js +2 -0
  321. package/build/dist/Types/Email/EmailTemplateType.js.map +1 -1
  322. package/build/dist/Types/NotificationSetting/NotificationSettingEventType.js +3 -0
  323. package/build/dist/Types/NotificationSetting/NotificationSettingEventType.js.map +1 -1
  324. package/build/dist/Types/OnCallDutyPolicy/CalendarFeedWindow.js +97 -0
  325. package/build/dist/Types/OnCallDutyPolicy/CalendarFeedWindow.js.map +1 -0
  326. package/build/dist/Types/OnCallDutyPolicy/Layer.js +113 -19
  327. package/build/dist/Types/OnCallDutyPolicy/Layer.js.map +1 -1
  328. package/build/dist/Types/OnCallDutyPolicy/MaterializedShift.js +134 -0
  329. package/build/dist/Types/OnCallDutyPolicy/MaterializedShift.js.map +1 -0
  330. package/build/dist/Types/OnCallDutyPolicy/OnCallCalendarFeedUtil.js +800 -0
  331. package/build/dist/Types/OnCallDutyPolicy/OnCallCalendarFeedUtil.js.map +1 -0
  332. package/build/dist/Types/OnCallDutyPolicy/ScheduleShiftUtil.js +97 -3
  333. package/build/dist/Types/OnCallDutyPolicy/ScheduleShiftUtil.js.map +1 -1
  334. package/build/dist/Types/OnCallDutyPolicy/ShiftSeamUtil.js +77 -0
  335. package/build/dist/Types/OnCallDutyPolicy/ShiftSeamUtil.js.map +1 -0
  336. package/build/dist/Types/Rum/SessionReplay.js +38 -0
  337. package/build/dist/Types/Rum/SessionReplay.js.map +1 -1
  338. package/build/dist/UI/Components/Date/TimeRangePickerDropdown.js +9 -5
  339. package/build/dist/UI/Components/Date/TimeRangePickerDropdown.js.map +1 -1
  340. package/build/dist/UI/Components/LogsViewer/LogsViewer.js +12 -7
  341. package/build/dist/UI/Components/LogsViewer/LogsViewer.js.map +1 -1
  342. package/build/dist/UI/Components/LogsViewer/components/HistogramTooltip.js +15 -14
  343. package/build/dist/UI/Components/LogsViewer/components/HistogramTooltip.js.map +1 -1
  344. package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js +17 -22
  345. package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js.map +1 -1
  346. package/build/dist/UI/Components/LogsViewer/components/LogsHistogram.js +2 -4
  347. package/build/dist/UI/Components/LogsViewer/components/LogsHistogram.js.map +1 -1
  348. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogram.js +2 -4
  349. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogram.js.map +1 -1
  350. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogramTooltip.js +15 -14
  351. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogramTooltip.js.map +1 -1
  352. package/build/dist/UI/Utils/Navigation.js +30 -0
  353. package/build/dist/UI/Utils/Navigation.js.map +1 -1
  354. package/build/dist/Utils/Array.js +25 -0
  355. package/build/dist/Utils/Array.js.map +1 -1
  356. package/build/dist/Utils/CertificateReissue.js +97 -0
  357. package/build/dist/Utils/CertificateReissue.js.map +1 -0
  358. package/build/dist/Utils/Grok/Grok.js +324 -0
  359. package/build/dist/Utils/Grok/Grok.js.map +1 -0
  360. package/build/dist/Utils/Grok/GrokPatterns.js +112 -0
  361. package/build/dist/Utils/Grok/GrokPatterns.js.map +1 -0
  362. package/build/dist/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.js +63 -3
  363. package/build/dist/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.js.map +1 -1
  364. package/package.json +1 -1
  365. package/UI/Utils/JsonWebToken.ts +0 -14
  366. package/build/dist/UI/Utils/JsonWebToken.js +0 -10
  367. package/build/dist/UI/Utils/JsonWebToken.js.map +0 -1
@@ -0,0 +1,1019 @@
1
+ import "@testing-library/jest-dom";
2
+ import {
3
+ afterEach,
4
+ beforeEach,
5
+ describe,
6
+ expect,
7
+ jest,
8
+ test,
9
+ } from "@jest/globals";
10
+ import {
11
+ cleanup,
12
+ fireEvent,
13
+ render,
14
+ screen,
15
+ waitFor,
16
+ within,
17
+ } from "@testing-library/react";
18
+ import React from "react";
19
+ import getJestMockFunction, { MockFunction } from "../../MockType";
20
+
21
+ /*
22
+ * The shared (project-owned) calendar links: one per schedule, one for the
23
+ * whole project. Unlike the personal link these are gated - whoever holds the
24
+ * URL sees everybody's shifts - so the assertions here are as much about WHO
25
+ * gets a working button as about what the button does:
26
+ *
27
+ * - an editor publishes / regenerates / disables;
28
+ * - a reader with a real-but-insufficient permission sees the same buttons
29
+ * disabled, with the missing permission named (issue #3306 rule);
30
+ * - a reader whose permission snapshot has not landed sees no accusation,
31
+ * just "ask an editor";
32
+ * - and everybody who can see the schedule may copy a published link.
33
+ *
34
+ * PermissionGate is exercised for real: the permission list and the master
35
+ * admin flag are the only things mocked.
36
+ */
37
+
38
+ let isMasterAdminForTest: boolean = false;
39
+ let permissionsForTest: Array<unknown> = [];
40
+
41
+ const getMock: MockFunction = getJestMockFunction();
42
+ const postMock: MockFunction = getJestMockFunction();
43
+ const updateByIdMock: MockFunction = getJestMockFunction();
44
+
45
+ jest.mock("../../../UI/Utils/API/API", () => {
46
+ return {
47
+ __esModule: true,
48
+ default: {
49
+ get: (...args: Array<any>) => {
50
+ return getMock(...args);
51
+ },
52
+ post: (...args: Array<any>) => {
53
+ return postMock(...args);
54
+ },
55
+ getFriendlyMessage: (error: unknown): string => {
56
+ if (
57
+ error &&
58
+ typeof error === "object" &&
59
+ "message" in (error as Record<string, unknown>)
60
+ ) {
61
+ return String((error as Record<string, unknown>)["message"]);
62
+ }
63
+
64
+ return "Something went wrong";
65
+ },
66
+ },
67
+ };
68
+ });
69
+
70
+ jest.mock("../../../UI/Utils/ModelAPI/ModelAPI", () => {
71
+ return {
72
+ __esModule: true,
73
+ default: {
74
+ getCommonHeaders: (): Record<string, string> => {
75
+ return {};
76
+ },
77
+ updateById: (...args: Array<any>) => {
78
+ return updateByIdMock(...args);
79
+ },
80
+ deleteItem: async (): Promise<void> => {
81
+ return undefined;
82
+ },
83
+ },
84
+ };
85
+ });
86
+
87
+ jest.mock("../../../UI/Components/ModelDetail/CardModelDetail", () => {
88
+ return {
89
+ __esModule: true,
90
+ default: (props: Record<string, any>) => {
91
+ const fieldTitles: Array<string> = (props["formFields"] || []).map(
92
+ (field: Record<string, unknown>): string => {
93
+ return String(field["title"]);
94
+ },
95
+ );
96
+
97
+ return React.createElement(
98
+ "div",
99
+ {
100
+ "data-testid": "card-model-detail-stub",
101
+ "data-model-id": String(props["modelDetailProps"]?.modelId),
102
+ "data-model-name": String(
103
+ new props["modelDetailProps"].modelType().tableName,
104
+ ),
105
+ "data-editable": String(props["isEditable"]),
106
+ },
107
+ `${props["cardProps"]?.title}|${fieldTitles.join(",")}`,
108
+ );
109
+ },
110
+ };
111
+ });
112
+
113
+ jest.mock("../../../UI/Utils/User", () => {
114
+ return {
115
+ __esModule: true,
116
+ default: {
117
+ isMasterAdmin: (): boolean => {
118
+ return isMasterAdminForTest;
119
+ },
120
+ getUserId: (): null => {
121
+ return null;
122
+ },
123
+ },
124
+ };
125
+ });
126
+
127
+ jest.mock("../../../UI/Utils/Permission", () => {
128
+ return {
129
+ __esModule: true,
130
+ default: {
131
+ getAllPermissions: (): Array<unknown> => {
132
+ return permissionsForTest;
133
+ },
134
+ getProjectPermissions: (): null => {
135
+ return null;
136
+ },
137
+ getGlobalPermissions: (): { globalPermissions: Array<unknown> } => {
138
+ return { globalPermissions: [...permissionsForTest] };
139
+ },
140
+ },
141
+ };
142
+ });
143
+
144
+ jest.mock("react-i18next", () => {
145
+ return {
146
+ useTranslation: () => {
147
+ return {
148
+ t: (key: string, options?: { defaultValue?: string }): string => {
149
+ return options?.defaultValue ?? key;
150
+ },
151
+ };
152
+ },
153
+ };
154
+ });
155
+
156
+ import ObjectID from "../../../Types/ObjectID";
157
+ import Navigation from "../../../UI/Utils/Navigation";
158
+ import HTTPErrorResponse from "../../../Types/API/HTTPErrorResponse";
159
+ import HTTPResponse from "../../../Types/API/HTTPResponse";
160
+ import Route from "../../../Types/API/Route";
161
+ import { JSONObject } from "../../../Types/JSON";
162
+ import Permission from "../../../Types/Permission";
163
+ import OnCallDutyPolicyScheduleCalendarFeed from "../../../Models/DatabaseModels/OnCallDutyPolicyScheduleCalendarFeed";
164
+ import ProjectOnCallCalendarFeed from "../../../Models/DatabaseModels/ProjectOnCallCalendarFeed";
165
+ import {
166
+ PROJECT_FEED_CURRENT_PATH,
167
+ PROJECT_FEED_PUBLISH_PATH,
168
+ PROJECT_FEED_ROTATE_PATH,
169
+ SHARED_LINK_OWNERSHIP_COPY,
170
+ buildGoogleAddUrl,
171
+ getScheduleFeedCurrentPath,
172
+ getScheduleFeedPublishPath,
173
+ getScheduleFeedRotatePath,
174
+ } from "../../../../App/FeatureSet/Dashboard/src/Components/OnCallPolicy/CalendarFeed/CalendarFeedUtil";
175
+ import SharedCalendarFeedCard, {
176
+ SharedCalendarFeedKind,
177
+ getSharedFeedPaths,
178
+ } from "../../../../App/FeatureSet/Dashboard/src/Components/OnCallPolicy/CalendarFeed/SharedCalendarFeedCard";
179
+ import ScheduleSubscribeCard from "../../../../App/FeatureSet/Dashboard/src/Components/OnCallPolicy/CalendarFeed/ScheduleSubscribeCard";
180
+ import OnCallDutyCalendarFeeds from "../../../../App/FeatureSet/Dashboard/src/Pages/OnCallDuty/CalendarFeeds";
181
+ import PageMap from "../../../../App/FeatureSet/Dashboard/src/Utils/PageMap";
182
+ import RouteMap, {
183
+ RouteUtil,
184
+ } from "../../../../App/FeatureSet/Dashboard/src/Utils/RouteMap";
185
+
186
+ const PROJECT_ID: string = "8f2a1b3c-4d5e-4f60-9a7b-1c2d3e4f5a6b";
187
+ const FEED_ID: string = "55555555-5555-4555-8555-555555555555";
188
+ const SCHEDULE_ID: ObjectID = new ObjectID(
189
+ "33333333-3333-4333-8333-333333333333",
190
+ );
191
+ const NOW: Date = new Date("2026-08-31T12:00:00.000Z");
192
+
193
+ const HTTPS_URL: string = `https://oneuptime.example.com/api/on-call-calendar/schedule/abcdefghijklmnopqrstuvwxyz0123456789ABCDEFG/schedule.ics`;
194
+ const WEBCAL_URL: string = HTTPS_URL.replace("https:", "webcals:");
195
+
196
+ // Every project-level role that may publish a shared link.
197
+ const EDITOR_PERMISSIONS: Array<Permission> = [Permission.ProjectAdmin];
198
+ // May read the schedule (and therefore the card) but never edit it.
199
+ const READER_PERMISSIONS: Array<Permission> = [
200
+ Permission.ReadProjectOnCallDutyPolicySchedule,
201
+ ];
202
+
203
+ type StatusJsonFunction = (overrides?: JSONObject) => JSONObject;
204
+
205
+ const publishedJson: StatusJsonFunction = (
206
+ overrides?: JSONObject,
207
+ ): JSONObject => {
208
+ return {
209
+ exists: true,
210
+ feedId: FEED_ID,
211
+ isEnabled: true,
212
+ needsRegeneration: false,
213
+ tokenHint: "t34m",
214
+ rotatedAt: "2026-08-01T12:00:00.000Z",
215
+ previousTokenExpiresAt: null,
216
+ lastFetchedAt: null,
217
+ lastFetchedClient: null,
218
+ fetchCount: 0,
219
+ lastRenderTruncated: false,
220
+ settings: {
221
+ includeCoverageGaps: false,
222
+ minimumGapMinutes: 60,
223
+ pastDays: 2,
224
+ futureDays: 90,
225
+ rotateWhenMemberLeaves: false,
226
+ },
227
+ urls: {
228
+ https: HTTPS_URL,
229
+ webcal: WEBCAL_URL,
230
+ googleAdd: buildGoogleAddUrl(HTTPS_URL),
231
+ },
232
+ hostWarning: null,
233
+ protocolWarning: null,
234
+ ...overrides,
235
+ };
236
+ };
237
+
238
+ const UNPUBLISHED_JSON: JSONObject = {
239
+ exists: false,
240
+ feedId: null,
241
+ isEnabled: false,
242
+ needsRegeneration: false,
243
+ tokenHint: null,
244
+ rotatedAt: null,
245
+ previousTokenExpiresAt: null,
246
+ lastFetchedAt: null,
247
+ lastFetchedClient: null,
248
+ fetchCount: 0,
249
+ lastRenderTruncated: false,
250
+ settings: { pastDays: 2, futureDays: 90 },
251
+ urls: null,
252
+ hostWarning: null,
253
+ protocolWarning: null,
254
+ };
255
+
256
+ type OkFunction = (json: JSONObject) => HTTPResponse<JSONObject>;
257
+
258
+ const ok: OkFunction = (json: JSONObject): HTTPResponse<JSONObject> => {
259
+ return new HTTPResponse<JSONObject>(200, json, {});
260
+ };
261
+
262
+ type FailFunction = (status: number, message: string) => HTTPErrorResponse;
263
+
264
+ const fail: FailFunction = (
265
+ status: number,
266
+ message: string,
267
+ ): HTTPErrorResponse => {
268
+ return new HTTPErrorResponse(status, { message: message }, {});
269
+ };
270
+
271
+ type UrlOfFunction = (call: Array<unknown>) => string;
272
+
273
+ const urlOf: UrlOfFunction = (call: Array<unknown>): string => {
274
+ return String((call[0] as Record<string, unknown>)["url"]);
275
+ };
276
+
277
+ function goToSchedulePage(): void {
278
+ window.history.pushState(
279
+ {},
280
+ "",
281
+ `/dashboard/${PROJECT_ID}/on-call-duty/schedules/${SCHEDULE_ID.toString()}`,
282
+ );
283
+ }
284
+
285
+ function resetMocks(): void {
286
+ getMock.mockReset();
287
+ postMock.mockReset();
288
+ updateByIdMock.mockReset();
289
+ updateByIdMock.mockResolvedValue(undefined);
290
+ isMasterAdminForTest = false;
291
+ permissionsForTest = EDITOR_PERMISSIONS;
292
+ }
293
+
294
+ function renderScheduleCard(
295
+ timezone?: string | null | undefined,
296
+ ): ReturnType<typeof render> {
297
+ return render(
298
+ <SharedCalendarFeedCard
299
+ kind={SharedCalendarFeedKind.Schedule}
300
+ scheduleId={SCHEDULE_ID}
301
+ scheduleTimezone={timezone}
302
+ now={NOW}
303
+ />,
304
+ );
305
+ }
306
+
307
+ describe("getSharedFeedPaths", () => {
308
+ test("the schedule kind uses the schedule-feed routes with the id embedded", () => {
309
+ expect(
310
+ getSharedFeedPaths(SharedCalendarFeedKind.Schedule, SCHEDULE_ID),
311
+ ).toEqual({
312
+ current: getScheduleFeedCurrentPath(SCHEDULE_ID.toString()),
313
+ publish: getScheduleFeedPublishPath(SCHEDULE_ID.toString()),
314
+ rotate: getScheduleFeedRotatePath(SCHEDULE_ID.toString()),
315
+ });
316
+ });
317
+
318
+ test("the project kind uses the project-feed routes and ignores any schedule id", () => {
319
+ expect(
320
+ getSharedFeedPaths(SharedCalendarFeedKind.Project, SCHEDULE_ID),
321
+ ).toEqual({
322
+ current: PROJECT_FEED_CURRENT_PATH,
323
+ publish: PROJECT_FEED_PUBLISH_PATH,
324
+ rotate: PROJECT_FEED_ROTATE_PATH,
325
+ });
326
+ expect(
327
+ getSharedFeedPaths(SharedCalendarFeedKind.Project, undefined).current,
328
+ ).toBe(PROJECT_FEED_CURRENT_PATH);
329
+ });
330
+ });
331
+
332
+ describe("SharedCalendarFeedCard - publishing", () => {
333
+ beforeEach(() => {
334
+ resetMocks();
335
+ goToSchedulePage();
336
+ });
337
+
338
+ afterEach(() => {
339
+ cleanup();
340
+ });
341
+
342
+ test("reads the schedule feed's /current on mount", async () => {
343
+ getMock.mockResolvedValue(ok(UNPUBLISHED_JSON));
344
+
345
+ renderScheduleCard("Europe/Stockholm");
346
+
347
+ await waitFor(() => {
348
+ expect(
349
+ screen.getByTestId("schedule-shared-calendar-feed-empty"),
350
+ ).toBeInTheDocument();
351
+ });
352
+
353
+ expect(urlOf(getMock.mock.calls[0] as Array<unknown>)).toContain(
354
+ getScheduleFeedCurrentPath(SCHEDULE_ID.toString()),
355
+ );
356
+ });
357
+
358
+ /*
359
+ * buildAbsentFeedStatus sends hostWarning / protocolWarning even with no
360
+ * feed. A team link that will be unreachable from Google's servers is worth
361
+ * saying before an editor publishes it and tells everyone to subscribe.
362
+ */
363
+ test("the unpublished state shows the deployment warnings the server attached to it", async () => {
364
+ getMock.mockResolvedValue(
365
+ ok({
366
+ ...UNPUBLISHED_JSON,
367
+ hostWarning: "HOST is not set, so the link points at localhost.",
368
+ protocolWarning: "HTTP_PROTOCOL is http, so the link is not encrypted.",
369
+ }),
370
+ );
371
+
372
+ renderScheduleCard("Europe/Stockholm");
373
+
374
+ await waitFor(() => {
375
+ expect(
376
+ screen.getByTestId("schedule-shared-calendar-feed-empty"),
377
+ ).toBeInTheDocument();
378
+ });
379
+
380
+ expect(
381
+ screen.getByTestId("schedule-shared-calendar-feed-host-warning"),
382
+ ).toHaveTextContent("HOST is not set");
383
+ expect(
384
+ screen.getByTestId("schedule-shared-calendar-feed-protocol-warning"),
385
+ ).toHaveTextContent("HTTP_PROTOCOL is http");
386
+ expect(
387
+ screen.getByTestId("schedule-shared-calendar-feed-publish"),
388
+ ).toBeInTheDocument();
389
+ });
390
+
391
+ test("an editor gets a working Publish button that posts to /publish", async () => {
392
+ getMock.mockResolvedValue(ok(UNPUBLISHED_JSON));
393
+ postMock.mockResolvedValue(ok(publishedJson()));
394
+
395
+ renderScheduleCard("Europe/Stockholm");
396
+
397
+ const publish: HTMLElement = await screen.findByTestId(
398
+ "schedule-shared-calendar-feed-publish",
399
+ );
400
+ expect(publish).not.toBeDisabled();
401
+
402
+ fireEvent.click(publish);
403
+
404
+ await waitFor(() => {
405
+ expect(
406
+ screen.getByTestId("schedule-shared-calendar-feed-active"),
407
+ ).toBeInTheDocument();
408
+ });
409
+ expect(postMock).toHaveBeenCalledTimes(1);
410
+ expect(urlOf(postMock.mock.calls[0] as Array<unknown>)).toContain(
411
+ getScheduleFeedPublishPath(SCHEDULE_ID.toString()),
412
+ );
413
+ expect(
414
+ screen.getByTestId("schedule-shared-calendar-feed-webcal"),
415
+ ).toHaveAttribute("href", WEBCAL_URL);
416
+ });
417
+
418
+ test("a master admin is an editor everywhere", async () => {
419
+ isMasterAdminForTest = true;
420
+ permissionsForTest = [];
421
+ getMock.mockResolvedValue(ok(UNPUBLISHED_JSON));
422
+
423
+ renderScheduleCard("Europe/Stockholm");
424
+
425
+ const publish: HTMLElement = await screen.findByTestId(
426
+ "schedule-shared-calendar-feed-publish",
427
+ );
428
+ expect(publish).not.toBeDisabled();
429
+ });
430
+
431
+ test("a reader with an insufficient permission sees Publish disabled, with the reason", async () => {
432
+ permissionsForTest = READER_PERMISSIONS;
433
+ getMock.mockResolvedValue(ok(UNPUBLISHED_JSON));
434
+
435
+ renderScheduleCard("Europe/Stockholm");
436
+
437
+ const publish: HTMLElement = await screen.findByTestId(
438
+ "schedule-shared-calendar-feed-publish",
439
+ );
440
+ expect(publish).toBeDisabled();
441
+
442
+ fireEvent.click(publish);
443
+ expect(postMock).not.toHaveBeenCalled();
444
+ expect(
445
+ screen.queryByTestId("schedule-shared-calendar-feed-ask-editor"),
446
+ ).not.toBeInTheDocument();
447
+ });
448
+
449
+ test("with no permission snapshot yet, the card asks for an editor instead of accusing", async () => {
450
+ permissionsForTest = [];
451
+ getMock.mockResolvedValue(ok(UNPUBLISHED_JSON));
452
+
453
+ renderScheduleCard("Europe/Stockholm");
454
+
455
+ await waitFor(() => {
456
+ expect(
457
+ screen.getByTestId("schedule-shared-calendar-feed-ask-editor"),
458
+ ).toHaveTextContent("Ask an editor of this schedule to publish it.");
459
+ });
460
+ expect(
461
+ screen.queryByTestId("schedule-shared-calendar-feed-publish"),
462
+ ).not.toBeInTheDocument();
463
+ });
464
+
465
+ test("the project kind asks for a project editor and reads the project-feed route", async () => {
466
+ permissionsForTest = [];
467
+ getMock.mockResolvedValue(ok(UNPUBLISHED_JSON));
468
+
469
+ render(
470
+ <SharedCalendarFeedCard
471
+ kind={SharedCalendarFeedKind.Project}
472
+ now={NOW}
473
+ />,
474
+ );
475
+
476
+ await waitFor(() => {
477
+ expect(
478
+ screen.getByTestId("project-shared-calendar-feed-ask-editor"),
479
+ ).toHaveTextContent("Ask a project editor to publish it.");
480
+ });
481
+ expect(urlOf(getMock.mock.calls[0] as Array<unknown>)).toContain(
482
+ PROJECT_FEED_CURRENT_PATH,
483
+ );
484
+ expect(
485
+ screen.getByText(/No project-wide link has been published yet/),
486
+ ).toBeInTheDocument();
487
+ });
488
+
489
+ test("a failed publish keeps the empty state and shows the server's message", async () => {
490
+ getMock.mockResolvedValue(ok(UNPUBLISHED_JSON));
491
+ postMock.mockResolvedValue(fail(403, "You need Edit on this schedule"));
492
+
493
+ renderScheduleCard("Europe/Stockholm");
494
+
495
+ fireEvent.click(
496
+ await screen.findByTestId("schedule-shared-calendar-feed-publish"),
497
+ );
498
+
499
+ await waitFor(() => {
500
+ expect(
501
+ screen.getByText("You need Edit on this schedule"),
502
+ ).toBeInTheDocument();
503
+ });
504
+ expect(
505
+ screen.getByTestId("schedule-shared-calendar-feed-empty"),
506
+ ).toBeInTheDocument();
507
+ });
508
+ });
509
+
510
+ describe("SharedCalendarFeedCard - a published link", () => {
511
+ beforeEach(() => {
512
+ resetMocks();
513
+ goToSchedulePage();
514
+ });
515
+
516
+ afterEach(() => {
517
+ cleanup();
518
+ });
519
+
520
+ test("shows the links, the rotation age, the ownership copy and the settings card", async () => {
521
+ getMock.mockResolvedValue(ok(publishedJson()));
522
+
523
+ renderScheduleCard("Europe/Stockholm");
524
+
525
+ await waitFor(() => {
526
+ expect(
527
+ screen.getByTestId("schedule-shared-calendar-feed-active"),
528
+ ).toBeInTheDocument();
529
+ });
530
+
531
+ expect(
532
+ screen.getByTestId("schedule-shared-calendar-feed-links"),
533
+ ).toBeInTheDocument();
534
+ expect(
535
+ screen.getByTestId("schedule-shared-calendar-feed-status-line"),
536
+ ).toHaveTextContent("Last rotated 30 days ago");
537
+ expect(
538
+ screen.getByTestId("schedule-shared-calendar-feed-status-line"),
539
+ ).toHaveTextContent("…t34m");
540
+ expect(
541
+ screen.getByTestId("schedule-shared-calendar-feed-ownership"),
542
+ ).toHaveTextContent(SHARED_LINK_OWNERSHIP_COPY);
543
+
544
+ const settings: HTMLElement = screen.getByTestId("card-model-detail-stub");
545
+ expect(settings).toHaveAttribute("data-model-id", FEED_ID);
546
+ expect(settings).toHaveAttribute(
547
+ "data-model-name",
548
+ new OnCallDutyPolicyScheduleCalendarFeed().tableName,
549
+ );
550
+ expect(settings).toHaveAttribute("data-editable", "true");
551
+ expect(settings).toHaveTextContent(
552
+ "Shared link settings|Show coverage gaps,Minimum gap to show (minutes),Days of past shifts,Days ahead,Regenerate when someone leaves the project",
553
+ );
554
+
555
+ expect(
556
+ screen.getByRole("button", { name: "Regenerate link" }),
557
+ ).not.toBeDisabled();
558
+ expect(screen.getByRole("button", { name: "Disable" })).not.toBeDisabled();
559
+ });
560
+
561
+ test("a reader may copy the link but the editor buttons are disabled and the settings read-only", async () => {
562
+ permissionsForTest = READER_PERMISSIONS;
563
+ getMock.mockResolvedValue(ok(publishedJson()));
564
+
565
+ renderScheduleCard("Europe/Stockholm");
566
+
567
+ await waitFor(() => {
568
+ expect(
569
+ screen.getByTestId("schedule-shared-calendar-feed-links"),
570
+ ).toBeInTheDocument();
571
+ });
572
+
573
+ expect(
574
+ screen.getByRole("button", { name: "Regenerate link" }),
575
+ ).toBeDisabled();
576
+ expect(screen.getByRole("button", { name: "Disable" })).toBeDisabled();
577
+ expect(screen.getByTestId("card-model-detail-stub")).toHaveAttribute(
578
+ "data-editable",
579
+ "false",
580
+ );
581
+ });
582
+
583
+ test("without a permission snapshot the editor buttons are hidden, the link still copyable", async () => {
584
+ permissionsForTest = [];
585
+ getMock.mockResolvedValue(ok(publishedJson()));
586
+
587
+ renderScheduleCard("Europe/Stockholm");
588
+
589
+ await waitFor(() => {
590
+ expect(
591
+ screen.getByTestId("schedule-shared-calendar-feed-links"),
592
+ ).toBeInTheDocument();
593
+ });
594
+
595
+ expect(
596
+ screen.queryByRole("button", { name: "Regenerate link" }),
597
+ ).not.toBeInTheDocument();
598
+ expect(
599
+ screen.queryByRole("button", { name: "Disable" }),
600
+ ).not.toBeInTheDocument();
601
+ });
602
+
603
+ test("Regenerate confirms first, then posts to /rotate", async () => {
604
+ getMock.mockResolvedValue(ok(publishedJson()));
605
+ postMock.mockResolvedValue(
606
+ ok(
607
+ publishedJson({
608
+ tokenHint: "r0t8",
609
+ previousTokenExpiresAt: "2026-09-30T12:00:00.000Z",
610
+ }),
611
+ ),
612
+ );
613
+
614
+ renderScheduleCard("Europe/Stockholm");
615
+
616
+ fireEvent.click(
617
+ await screen.findByRole("button", { name: "Regenerate link" }),
618
+ );
619
+
620
+ const modal: HTMLElement = await screen.findByTestId("modal");
621
+ expect(postMock).not.toHaveBeenCalled();
622
+
623
+ fireEvent.click(
624
+ within(modal).getByRole("button", { name: "Regenerate link" }),
625
+ );
626
+
627
+ await waitFor(() => {
628
+ expect(postMock).toHaveBeenCalledTimes(1);
629
+ });
630
+ expect(urlOf(postMock.mock.calls[0] as Array<unknown>)).toContain(
631
+ getScheduleFeedRotatePath(SCHEDULE_ID.toString()),
632
+ );
633
+
634
+ /*
635
+ * The rotated-out shared link answers with an EMPTY calendar for its grace
636
+ * window (spec 2.1). An editor who rotated because somebody left must not
637
+ * be told the old link keeps serving the roster for another month.
638
+ */
639
+ await waitFor(() => {
640
+ expect(
641
+ screen.getByTestId("schedule-shared-calendar-feed-previous-link"),
642
+ ).toHaveTextContent("The previous link returns an empty calendar until");
643
+ });
644
+ expect(
645
+ screen.getByTestId("schedule-shared-calendar-feed-previous-link"),
646
+ ).not.toHaveTextContent("keeps working");
647
+ expect(
648
+ screen.getByTestId("schedule-shared-calendar-feed-status-line"),
649
+ ).toHaveTextContent("…r0t8");
650
+ });
651
+
652
+ test("Disable writes isEnabled=false on the schedule feed model and re-reads", async () => {
653
+ getMock
654
+ .mockResolvedValueOnce(ok(publishedJson()))
655
+ .mockResolvedValueOnce(ok(publishedJson({ isEnabled: false })));
656
+
657
+ renderScheduleCard("Europe/Stockholm");
658
+
659
+ fireEvent.click(await screen.findByRole("button", { name: "Disable" }));
660
+
661
+ await waitFor(() => {
662
+ expect(updateByIdMock).toHaveBeenCalledTimes(1);
663
+ });
664
+
665
+ const call: Record<string, unknown> = updateByIdMock.mock
666
+ .calls[0]![0] as Record<string, unknown>;
667
+ expect(call["modelType"]).toBe(OnCallDutyPolicyScheduleCalendarFeed);
668
+ expect(String(call["id"])).toBe(FEED_ID);
669
+ expect(call["data"]).toEqual({ isEnabled: false });
670
+
671
+ await waitFor(() => {
672
+ expect(
673
+ screen.getByTestId("schedule-shared-calendar-feed-disabled"),
674
+ ).toBeInTheDocument();
675
+ });
676
+ expect(screen.getByRole("button", { name: "Enable" })).toBeInTheDocument();
677
+ // The link stays visible under the warning so an editor can still copy it.
678
+ expect(
679
+ screen.getByTestId("schedule-shared-calendar-feed-links"),
680
+ ).toBeInTheDocument();
681
+ });
682
+
683
+ test("the project kind writes to the project feed model and posts to the project routes", async () => {
684
+ getMock
685
+ .mockResolvedValueOnce(ok(publishedJson()))
686
+ .mockResolvedValueOnce(ok(publishedJson({ isEnabled: false })));
687
+ postMock.mockResolvedValue(ok(publishedJson({ tokenHint: "pr0j" })));
688
+
689
+ render(
690
+ <SharedCalendarFeedCard
691
+ kind={SharedCalendarFeedKind.Project}
692
+ now={NOW}
693
+ />,
694
+ );
695
+
696
+ await waitFor(() => {
697
+ expect(
698
+ screen.getByTestId("project-shared-calendar-feed-active"),
699
+ ).toBeInTheDocument();
700
+ });
701
+
702
+ expect(screen.getByTestId("card-model-detail-stub")).toHaveAttribute(
703
+ "data-model-name",
704
+ new ProjectOnCallCalendarFeed().tableName,
705
+ );
706
+
707
+ fireEvent.click(screen.getByRole("button", { name: "Regenerate link" }));
708
+ const modal: HTMLElement = await screen.findByTestId("modal");
709
+ fireEvent.click(
710
+ within(modal).getByRole("button", { name: "Regenerate link" }),
711
+ );
712
+
713
+ await waitFor(() => {
714
+ expect(postMock).toHaveBeenCalledTimes(1);
715
+ });
716
+ expect(urlOf(postMock.mock.calls[0] as Array<unknown>)).toContain(
717
+ PROJECT_FEED_ROTATE_PATH,
718
+ );
719
+
720
+ // Let the rotate round trip settle: the card buttons are inert while busy.
721
+ await waitFor(() => {
722
+ expect(screen.queryByTestId("modal")).not.toBeInTheDocument();
723
+ });
724
+ await waitFor(() => {
725
+ expect(
726
+ screen.getByTestId("project-shared-calendar-feed-status-line"),
727
+ ).toHaveTextContent("…pr0j");
728
+ });
729
+
730
+ fireEvent.click(screen.getByRole("button", { name: "Disable" }));
731
+
732
+ await waitFor(() => {
733
+ expect(updateByIdMock).toHaveBeenCalledTimes(1);
734
+ });
735
+ expect(
736
+ (updateByIdMock.mock.calls[0]![0] as Record<string, unknown>)[
737
+ "modelType"
738
+ ],
739
+ ).toBe(ProjectOnCallCalendarFeed);
740
+ });
741
+
742
+ test("an unreadable stored link tells the reader an editor must regenerate it", async () => {
743
+ getMock.mockResolvedValue(
744
+ ok(publishedJson({ needsRegeneration: true, urls: null })),
745
+ );
746
+
747
+ renderScheduleCard("Europe/Stockholm");
748
+
749
+ await waitFor(() => {
750
+ expect(
751
+ screen.getByTestId("schedule-shared-calendar-feed-needs-regeneration"),
752
+ ).toHaveTextContent("until an editor regenerates it");
753
+ });
754
+ expect(
755
+ screen.queryByTestId("schedule-shared-calendar-feed-links"),
756
+ ).not.toBeInTheDocument();
757
+ });
758
+
759
+ test("a 404 hides the feature; any other failure shows the message", async () => {
760
+ getMock.mockResolvedValueOnce(fail(404, "Not found"));
761
+
762
+ const first: ReturnType<typeof render> =
763
+ renderScheduleCard("Europe/Stockholm");
764
+
765
+ await waitFor(() => {
766
+ expect(
767
+ screen.getByTestId("schedule-shared-calendar-feed-unsupported"),
768
+ ).toBeInTheDocument();
769
+ });
770
+ first.unmount();
771
+
772
+ getMock.mockResolvedValueOnce(fail(500, "Database is away"));
773
+ renderScheduleCard("Europe/Stockholm");
774
+
775
+ await waitFor(() => {
776
+ expect(screen.getByText("Database is away")).toBeInTheDocument();
777
+ });
778
+ });
779
+ });
780
+
781
+ describe("SharedCalendarFeedCard - legacy timezone warning", () => {
782
+ beforeEach(() => {
783
+ resetMocks();
784
+ goToSchedulePage();
785
+ getMock.mockResolvedValue(ok(publishedJson()));
786
+ });
787
+
788
+ afterEach(() => {
789
+ cleanup();
790
+ });
791
+
792
+ test("warns for a schedule without a timezone (null or empty)", async () => {
793
+ renderScheduleCard(null);
794
+
795
+ await waitFor(() => {
796
+ expect(
797
+ screen.getByTestId("schedule-shared-calendar-feed-timezone-warning"),
798
+ ).toHaveTextContent("shown in UTC");
799
+ });
800
+ cleanup();
801
+
802
+ renderScheduleCard("");
803
+
804
+ await waitFor(() => {
805
+ expect(
806
+ screen.getByTestId("schedule-shared-calendar-feed-timezone-warning"),
807
+ ).toBeInTheDocument();
808
+ });
809
+ });
810
+
811
+ test("stays quiet while the schedule is still loading (undefined) and for a real timezone", async () => {
812
+ renderScheduleCard(undefined);
813
+
814
+ await waitFor(() => {
815
+ expect(
816
+ screen.getByTestId("schedule-shared-calendar-feed-active"),
817
+ ).toBeInTheDocument();
818
+ });
819
+ expect(
820
+ screen.queryByTestId("schedule-shared-calendar-feed-timezone-warning"),
821
+ ).not.toBeInTheDocument();
822
+ cleanup();
823
+
824
+ renderScheduleCard("America/New_York");
825
+
826
+ await waitFor(() => {
827
+ expect(
828
+ screen.getByTestId("schedule-shared-calendar-feed-active"),
829
+ ).toBeInTheDocument();
830
+ });
831
+ expect(
832
+ screen.queryByTestId("schedule-shared-calendar-feed-timezone-warning"),
833
+ ).not.toBeInTheDocument();
834
+ });
835
+
836
+ test("the project kind never warns about a timezone", async () => {
837
+ render(
838
+ <SharedCalendarFeedCard
839
+ kind={SharedCalendarFeedKind.Project}
840
+ scheduleTimezone={null}
841
+ now={NOW}
842
+ />,
843
+ );
844
+
845
+ await waitFor(() => {
846
+ expect(
847
+ screen.getByTestId("project-shared-calendar-feed-active"),
848
+ ).toBeInTheDocument();
849
+ });
850
+ expect(
851
+ screen.queryByTestId("project-shared-calendar-feed-timezone-warning"),
852
+ ).not.toBeInTheDocument();
853
+ });
854
+ });
855
+
856
+ describe("ScheduleSubscribeCard", () => {
857
+ beforeEach(() => {
858
+ resetMocks();
859
+ goToSchedulePage();
860
+ });
861
+
862
+ afterEach(() => {
863
+ cleanup();
864
+ });
865
+
866
+ test("renders the personal half narrowed to the schedule and the shared half", async () => {
867
+ getMock.mockImplementation((args: Record<string, unknown>) => {
868
+ const url: string = String(args["url"]);
869
+
870
+ if (url.includes("/feed/current")) {
871
+ return Promise.resolve(
872
+ ok(
873
+ publishedJson({
874
+ urls: {
875
+ https: HTTPS_URL.replace("/schedule/", "/user/"),
876
+ webcal: WEBCAL_URL.replace("/schedule/", "/user/"),
877
+ googleAdd: buildGoogleAddUrl(
878
+ HTTPS_URL.replace("/schedule/", "/user/"),
879
+ ),
880
+ },
881
+ }),
882
+ ),
883
+ );
884
+ }
885
+
886
+ return Promise.resolve(ok(publishedJson()));
887
+ });
888
+
889
+ render(
890
+ <ScheduleSubscribeCard
891
+ scheduleId={SCHEDULE_ID}
892
+ scheduleTimezone="Europe/Stockholm"
893
+ now={NOW}
894
+ />,
895
+ );
896
+
897
+ expect(screen.getByText("Subscribe to this schedule")).toBeInTheDocument();
898
+
899
+ await waitFor(() => {
900
+ expect(
901
+ screen.getByTestId("schedule-personal-calendar-feed-links"),
902
+ ).toBeInTheDocument();
903
+ });
904
+ await waitFor(() => {
905
+ expect(
906
+ screen.getByTestId("schedule-shared-calendar-feed-links"),
907
+ ).toBeInTheDocument();
908
+ });
909
+
910
+ expect(
911
+ screen.getByTestId("schedule-personal-calendar-feed-webcal"),
912
+ ).toHaveAttribute(
913
+ "href",
914
+ `${WEBCAL_URL.replace("/schedule/", "/user/")}?schedule=${SCHEDULE_ID.toString()}`,
915
+ );
916
+ // The shared link is never narrowed - it is per schedule by construction.
917
+ expect(
918
+ screen.getByTestId("schedule-shared-calendar-feed-webcal"),
919
+ ).toHaveAttribute("href", WEBCAL_URL);
920
+
921
+ // Two status reads: the personal feed and the schedule feed.
922
+ const urls: Array<string> = getMock.mock.calls.map(
923
+ (call: Array<unknown>): string => {
924
+ return urlOf(call);
925
+ },
926
+ );
927
+ expect(
928
+ urls.some((url: string): boolean => {
929
+ return url.includes("/feed/current");
930
+ }),
931
+ ).toBe(true);
932
+ expect(
933
+ urls.some((url: string): boolean => {
934
+ return url.includes(getScheduleFeedCurrentPath(SCHEDULE_ID.toString()));
935
+ }),
936
+ ).toBe(true);
937
+ });
938
+ });
939
+
940
+ describe("On-Call Duty > Calendar Feeds page", () => {
941
+ beforeEach(() => {
942
+ resetMocks();
943
+ window.history.pushState(
944
+ {},
945
+ "",
946
+ `/dashboard/${PROJECT_ID}/on-call-duty/calendar-feeds`,
947
+ );
948
+ getMock.mockResolvedValue(ok(UNPUBLISHED_JSON));
949
+ });
950
+
951
+ afterEach(() => {
952
+ cleanup();
953
+ });
954
+
955
+ test("maps the three kinds of link and hosts the project-wide card", async () => {
956
+ render(
957
+ <OnCallDutyCalendarFeeds
958
+ pageRoute={new Route("/")}
959
+ currentProject={null}
960
+ hasPaymentMethod={false}
961
+ />,
962
+ );
963
+
964
+ const personalRoute: Route = RouteUtil.populateRouteParams(
965
+ RouteMap[PageMap.USER_SETTINGS_ON_CALL_CALENDAR_FEED] as Route,
966
+ );
967
+ const schedulesRoute: Route = RouteUtil.populateRouteParams(
968
+ RouteMap[PageMap.ON_CALL_DUTY_SCHEDULES] as Route,
969
+ );
970
+
971
+ expect(
972
+ within(screen.getByTestId("calendar-feeds-personal-pointer"))
973
+ .getByText("Open your calendar feed")
974
+ .closest("a"),
975
+ ).toHaveAttribute("href", personalRoute.toString());
976
+ expect(
977
+ within(screen.getByTestId("calendar-feeds-schedule-pointer"))
978
+ .getByText("Go to schedules")
979
+ .closest("a"),
980
+ ).toHaveAttribute("href", schedulesRoute.toString());
981
+ expect(
982
+ screen.getByTestId("calendar-feeds-project-pointer"),
983
+ ).toBeInTheDocument();
984
+
985
+ await waitFor(() => {
986
+ expect(
987
+ screen.getByTestId("project-shared-calendar-feed-empty"),
988
+ ).toBeInTheDocument();
989
+ });
990
+ expect(urlOf(getMock.mock.calls[0] as Array<unknown>)).toContain(
991
+ PROJECT_FEED_CURRENT_PATH,
992
+ );
993
+ });
994
+
995
+ test("the 'Your personal feed' button navigates to the User Settings page", () => {
996
+ const navigateMock: MockFunction = getJestMockFunction();
997
+ const originalNavigate: typeof Navigation.navigate = Navigation.navigate;
998
+ Navigation.navigate = navigateMock as unknown as typeof Navigation.navigate;
999
+
1000
+ render(
1001
+ <OnCallDutyCalendarFeeds
1002
+ pageRoute={new Route("/")}
1003
+ currentProject={null}
1004
+ hasPaymentMethod={false}
1005
+ />,
1006
+ );
1007
+
1008
+ fireEvent.click(screen.getByRole("button", { name: "Your personal feed" }));
1009
+
1010
+ expect(navigateMock).toHaveBeenCalledTimes(1);
1011
+ expect(String(navigateMock.mock.calls[0]![0])).toBe(
1012
+ RouteUtil.populateRouteParams(
1013
+ RouteMap[PageMap.USER_SETTINGS_ON_CALL_CALENDAR_FEED] as Route,
1014
+ ).toString(),
1015
+ );
1016
+
1017
+ Navigation.navigate = originalNavigate;
1018
+ });
1019
+ });