@oneuptime/common 13.0.7 → 14.0.0

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 (759) hide show
  1. package/Models/AnalyticsModels/AuditLog.ts +23 -15
  2. package/Models/DatabaseModels/GlobalConfig.ts +24 -0
  3. package/Models/DatabaseModels/Monitor.ts +3 -2
  4. package/Models/DatabaseModels/MonitorTemplate.ts +2 -1
  5. package/Models/DatabaseModels/OnCallDutyPolicyScheduleCalendarFeed.ts +1 -1
  6. package/Models/DatabaseModels/ProjectOnCallCalendarFeed.ts +1 -1
  7. package/Models/DatabaseModels/UserOnCallCalendarFeed.ts +1 -1
  8. package/Scripts/generate-service-worker.js +65 -4
  9. package/Server/API/AIBillingAPI.ts +1 -0
  10. package/Server/API/AIInsightAPI.ts +1 -4
  11. package/Server/API/AIInvestigationAPI.ts +1 -4
  12. package/Server/API/AIReadinessAPI.ts +4 -5
  13. package/Server/API/AlertAPI.ts +1 -0
  14. package/Server/API/AutoRemediationAPI.ts +1 -4
  15. package/Server/API/BaseAPI.ts +34 -0
  16. package/Server/API/BaseAnalyticsAPI.ts +80 -9
  17. package/Server/API/BillingAPI.ts +2 -0
  18. package/Server/API/BillingInvoiceAPI.ts +1 -0
  19. package/Server/API/BillingPaymentMethodAPI.ts +2 -0
  20. package/Server/API/CodeFixRunAPI.ts +1 -3
  21. package/Server/API/CommonAPI.ts +68 -4
  22. package/Server/API/DashboardAPI.ts +12 -12
  23. package/Server/API/GlobalConfigAPI.ts +243 -460
  24. package/Server/API/IncidentAPI.ts +2 -0
  25. package/Server/API/IncidentEpisodeAPI.ts +1 -0
  26. package/Server/API/NotificationAPI.ts +1 -0
  27. package/Server/API/OnCallCalendarAPI.ts +4 -5
  28. package/Server/API/OnCallDutyPolicyAPI.ts +7 -0
  29. package/Server/API/OnCallReadinessAPI.ts +4 -1
  30. package/Server/API/OnCallScheduleTimelineAPI.ts +731 -0
  31. package/Server/API/ProjectAPI.ts +1 -0
  32. package/Server/API/ProjectOIDC.ts +25 -16
  33. package/Server/API/ProjectSSO.ts +24 -16
  34. package/Server/API/ScheduledMaintenanceAPI.ts +1 -0
  35. package/Server/API/StatusPageAPI.ts +261 -74
  36. package/Server/API/TeamMemberAPI.ts +13 -1
  37. package/Server/API/UserCallAPI.ts +2 -0
  38. package/Server/API/UserEmailAPI.ts +2 -0
  39. package/Server/API/UserIncomingCallNumberAPI.ts +2 -0
  40. package/Server/API/UserMicrosoftTeamsAPI.ts +1 -0
  41. package/Server/API/UserSlackAPI.ts +1 -0
  42. package/Server/API/UserSmsAPI.ts +2 -0
  43. package/Server/API/UserTotpAuthAPI.ts +1 -0
  44. package/Server/API/UserWebAuthnAPI.ts +2 -0
  45. package/Server/API/UserWebhookAPI.ts +1 -0
  46. package/Server/API/UserWhatsAppAPI.ts +2 -0
  47. package/Server/Enterprise/EnterpriseEdition.ts +649 -0
  48. package/Server/Enterprise/EnterpriseFeature.ts +48 -0
  49. package/Server/Enterprise/EnterpriseLicenseSnapshot.ts +192 -0
  50. package/Server/Enterprise/EnterpriseServerModule.ts +233 -0
  51. package/Server/EnvironmentConfig.ts +147 -2
  52. package/Server/Infrastructure/Postgres/SchemaMigrations/1793900000000-AddEnterpriseEditionFirstSeenAt.ts +25 -0
  53. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +2 -0
  54. package/Server/Middleware/MasterAdminAuthorization.ts +13 -2
  55. package/Server/Middleware/UserAuthorization.ts +117 -21
  56. package/Server/Services/ApiKeyPermissionService.ts +101 -4
  57. package/Server/Services/AuditLogService.ts +84 -1079
  58. package/Server/Services/DatabaseService.ts +245 -0
  59. package/Server/Services/DetectionRuleService.ts +5 -4
  60. package/Server/Services/EnterpriseLicenseService.ts +38 -0
  61. package/Server/Services/GlobalConfigService.ts +80 -1
  62. package/Server/Services/MonitorProbeService.ts +116 -20
  63. package/Server/Services/MonitorService.ts +56 -4
  64. package/Server/Services/MonitorStatusTimelineService.ts +284 -0
  65. package/Server/Services/MonitorTemplateService.ts +26 -0
  66. package/Server/Services/OnCallDutyPolicyChildService.ts +261 -0
  67. package/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.ts +2 -2
  68. package/Server/Services/OnCallDutyPolicyEscalationRuleService.ts +2 -2
  69. package/Server/Services/OnCallDutyPolicyEscalationRuleTeamService.ts +2 -2
  70. package/Server/Services/OnCallDutyPolicyEscalationRuleUserService.ts +2 -2
  71. package/Server/Services/PayAsYouGoBillingService.ts +23 -28
  72. package/Server/Services/ProjectService.ts +302 -0
  73. package/Server/Services/RumSessionPinService.ts +293 -22
  74. package/Server/Services/StatusPagePrivateUserService.ts +7 -3
  75. package/Server/Services/StatusPageService.ts +78 -1
  76. package/Server/Services/TeamMemberService.ts +11 -0
  77. package/Server/Services/TeamPermissionService.ts +120 -26
  78. package/Server/Services/TeamService.ts +10 -0
  79. package/Server/Services/ThreatIntelFeedService.ts +6 -3
  80. package/Server/Services/UserService.ts +10 -17
  81. package/Server/Types/Database/Permissions/BasePermission.ts +9 -7
  82. package/Server/Types/Database/Permissions/CreatePermission.ts +15 -0
  83. package/Server/Types/Database/Permissions/EditionPermission.ts +208 -18
  84. package/Server/Types/Database/Permissions/Index.ts +75 -11
  85. package/Server/Types/Database/Permissions/TablePermission.ts +34 -31
  86. package/Server/Types/Database/Permissions/UpdatePermission.ts +29 -0
  87. package/Server/Types/Markdown.ts +41 -0
  88. package/Server/Utils/AI/AIChatPrivacyFilter.ts +8 -0
  89. package/Server/Utils/AI/AIRunPrivacyFilter.ts +8 -0
  90. package/Server/Utils/CommunityEditionSsoReport.ts +361 -0
  91. package/Server/Utils/EditionEnforcement.ts +233 -0
  92. package/Server/Utils/FrontendEnvironment.ts +41 -2
  93. package/Server/Utils/InstanceHealth/RedisHealth.ts +12 -277
  94. package/Server/Utils/MasterAdminPageAccess.ts +61 -0
  95. package/Server/Utils/Monitor/AffectedResourceList.ts +190 -0
  96. package/Server/Utils/Monitor/Criteria/EvaluateOverTime.ts +10 -1
  97. package/Server/Utils/Monitor/MonitorAlert.ts +3 -1
  98. package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +543 -333
  99. package/Server/Utils/Monitor/MonitorIncident.ts +3 -1
  100. package/Server/Utils/Monitor/MonitoringIntervalValidator.ts +116 -0
  101. package/Server/Utils/OnCall/OnCallCalendarFeedRenderer.ts +1 -1
  102. package/Server/Utils/Realtime.ts +163 -45
  103. package/Server/Utils/SecurityEvent/DetectionRuleEvaluator.ts +11 -28
  104. package/Server/Utils/SecurityEvent/GoogleSecOpsSeverityRepair.ts +170 -0
  105. package/Server/Utils/SecurityEvent/Sigma/SigmaClickhouseCompiler.ts +1 -1
  106. package/Server/Utils/SecurityEvent/ThreatIntel/ThreatIntelFeedPoller.ts +6 -2
  107. package/Server/Utils/SecurityEvent/ThreatIntel/ThreatIntelMatcher.ts +10 -9
  108. package/Server/Utils/Workspace/Slack/Slack.ts +423 -10
  109. package/Tests/App/Accounts/LoginPasskey.test.tsx +18 -0
  110. package/Tests/App/Accounts/LoginSsoEdition.test.tsx +616 -0
  111. package/Tests/App/AdminDashboard/AdminAuthenticationSsoEdition.test.tsx +217 -0
  112. package/Tests/App/AdminDashboard/AdminEnterprisePluginPage.test.tsx +357 -0
  113. package/Tests/App/AdminDashboard/EnterpriseLicensesShell.test.tsx +214 -0
  114. package/Tests/App/AdminDashboard/GlobalSSOEnterpriseShells.test.tsx +271 -0
  115. package/Tests/App/AdminDashboard/HealthShells.test.tsx +659 -0
  116. package/Tests/App/Dashboard/AffectedResourcesPicker.test.tsx +1059 -0
  117. package/Tests/App/Dashboard/AffectedResourcesPickerEditForm.test.tsx +376 -0
  118. package/Tests/App/Dashboard/AffectedResourcesPickerHiddenTypes.test.tsx +757 -0
  119. package/Tests/App/Dashboard/AffectedResourcesSummaryStep.test.tsx +307 -0
  120. package/Tests/App/Dashboard/ArgumentsFormGroupByUnlistedAttributes.test.tsx +131 -0
  121. package/Tests/App/Dashboard/AuditLogsAndComplianceShells.test.tsx +572 -0
  122. package/Tests/App/Dashboard/DashboardHeaderRequests.test.tsx +1 -1
  123. package/Tests/App/Dashboard/DashboardMonitorLabelVariable.test.tsx +9 -1
  124. package/Tests/App/Dashboard/DashboardVariableAllContract.test.tsx +24 -0
  125. package/Tests/App/Dashboard/DashboardVariableSelector.test.tsx +162 -8
  126. package/Tests/App/Dashboard/EnterpriseFeatureUpgrade.test.tsx +547 -0
  127. package/Tests/App/Dashboard/EnterprisePluginPage.test.tsx +667 -0
  128. package/Tests/App/Dashboard/EntityFilterDropdownArchivedSlos.test.tsx +105 -6
  129. package/Tests/App/Dashboard/EntityFilterDropdownUnlistedSelection.test.tsx +434 -0
  130. package/Tests/App/Dashboard/EscalationRuleReadiness.test.tsx +276 -1
  131. package/Tests/App/Dashboard/IdentityEnterpriseShells.test.tsx +319 -0
  132. package/Tests/App/Dashboard/OnCallCalendarFeedSideMenus.test.tsx +9 -1
  133. package/Tests/App/Dashboard/OnboardingSsoEmptyState.test.tsx +185 -0
  134. package/Tests/App/Dashboard/PayAsYouGoConsentGate.test.tsx +58 -42
  135. package/Tests/App/Dashboard/PayAsYouGoNotices.test.tsx +42 -5
  136. package/Tests/App/Dashboard/ResourceAuditLogsSideMenus.test.tsx +34 -23
  137. package/Tests/App/Dashboard/ScheduleTimeline.test.tsx +1580 -0
  138. package/Tests/App/Dashboard/ScheduleTimelineAPI.test.ts +280 -0
  139. package/Tests/App/Dashboard/ScheduleTimelineModel.test.ts +673 -0
  140. package/Tests/App/Dashboard/SecurityEventConnectionDiagnostics.test.tsx +133 -70
  141. package/Tests/App/Dashboard/SecurityEventDetailPanel.test.tsx +399 -0
  142. package/Tests/App/Dashboard/SecurityEventListRow.test.tsx +214 -0
  143. package/Tests/App/Dashboard/SecurityEventsDetectionRulesPage.test.tsx +86 -3
  144. package/Tests/App/Dashboard/SecurityEventsEmptyState.test.tsx +1 -58
  145. package/Tests/App/Dashboard/SecurityEventsGuideContent.test.ts +1313 -0
  146. package/Tests/App/Dashboard/SecurityEventsHowItWorksCard.test.tsx +536 -0
  147. package/Tests/App/Dashboard/SecurityEventsNavTabs.test.tsx +248 -0
  148. package/Tests/App/Dashboard/SecurityEventsNoResults.test.tsx +222 -0
  149. package/Tests/App/Dashboard/SecurityEventsThreatIntelPage.test.tsx +199 -0
  150. package/Tests/App/Dashboard/SecurityEventsViewer.test.tsx +1203 -0
  151. package/Tests/App/Dashboard/SloOverviewHero.test.tsx +171 -36
  152. package/Tests/App/Dashboard/SloViewSideMenu.test.tsx +215 -0
  153. package/Tests/App/Dashboard/SloViewSideMenuCounts.test.tsx +40 -3
  154. package/Tests/App/Dashboard/UserEmailCallSites.test.tsx +11 -91
  155. package/Tests/App/PublicDashboard/DashboardVariableSelector.test.tsx +12 -4
  156. package/Tests/App/PublicDashboard/PublicDashboardWidgetListAuth.test.tsx +623 -0
  157. package/Tests/App/StatusPage/StatusPageSsoEmptyState.test.tsx +319 -0
  158. package/Tests/Helpers/RefreshAwareApiScan.ts +757 -0
  159. package/Tests/Helpers/TenantScopedRequestScan.ts +515 -0
  160. package/Tests/Models/OnCallCalendarFeedModels.test.ts +23 -1
  161. package/Tests/Scripts/GenerateServiceWorker.test.ts +931 -0
  162. package/Tests/Server/API/AIChatRouteAuthorization.test.ts +138 -5
  163. package/Tests/Server/API/AIGenerationProjectAIToggle.test.ts +187 -0
  164. package/Tests/Server/API/AIInvestigationAvailabilityAuthorization.test.ts +51 -3
  165. package/Tests/Server/API/AIInvestigationEvidenceAPI.test.ts +55 -2
  166. package/Tests/Server/API/BaseAPICreateItemDoesNotMutateModel.test.ts +130 -0
  167. package/Tests/Server/API/BaseAnalyticsAPIAggregateCache.test.ts +729 -0
  168. package/Tests/Server/API/BillingPayAsYouGoStatus.test.ts +93 -7
  169. package/Tests/Server/API/BillingPaymentMethodAPI.test.ts +36 -3
  170. package/Tests/Server/API/CommonAPIAuthGuard.test.ts +1003 -24
  171. package/Tests/Server/API/DashboardPublicRateLimit.test.ts +6 -3
  172. package/Tests/Server/API/ExpiredSessionAIRoutes.test.ts +708 -0
  173. package/Tests/Server/API/ExpiredSessionCustomRoutes.test.ts +928 -0
  174. package/Tests/Server/API/ExpiredSessionMonitorTemplate.test.ts +319 -0
  175. package/Tests/Server/API/ExpiredSessionOnCallDutyPolicy.test.ts +336 -0
  176. package/Tests/Server/API/ExpiredSessionRouteGuards.test.ts +820 -0
  177. package/Tests/Server/API/GlobalConfigLicense.test.ts +766 -381
  178. package/Tests/Server/API/NotificationChannelVerificationAPI.test.ts +115 -12
  179. package/Tests/Server/API/OnCallCalendarAPI.test.ts +375 -8
  180. package/Tests/Server/API/OnCallReadinessAPI.test.ts +196 -912
  181. package/Tests/Server/API/OnCallScheduleTimelineAPI.test.ts +1513 -0
  182. package/Tests/Server/API/OnCallSetupReminderAPI.test.ts +119 -7
  183. package/Tests/Server/API/ProjectScopedCustomRouteAuth.test.ts +111 -4
  184. package/Tests/Server/API/PublicRouteInvalidAccessToken.test.ts +615 -0
  185. package/Tests/Server/API/SecurityEventAttributesAPI.test.ts +89 -2
  186. package/Tests/Server/API/SessionReplayAPI.test.ts +121 -0
  187. package/Tests/Server/API/SsoProviderListingsEdition.test.ts +432 -0
  188. package/Tests/Server/API/StatusPageMasterPasswordRateLimit.test.ts +4 -2
  189. package/Tests/Server/API/StatusPagePostmortemVisibility.test.ts +441 -0
  190. package/Tests/Server/API/StatusPageTestEmailReportAuth.test.ts +664 -0
  191. package/Tests/Server/API/TeamMemberLeaveScimLockEdition.test.ts +252 -0
  192. package/Tests/Server/API/TeamMemberRemoveUserFromProjectAPI.test.ts +7 -0
  193. package/Tests/Server/API/UserIncomingCallNumberApi.test.ts +6 -0
  194. package/Tests/Server/API/UserNotificationSettingAPI.test.ts +65 -4
  195. package/Tests/Server/API/UserProjectsAPI.test.ts +63 -3
  196. package/Tests/Server/API/UserTotpAuthAPI.test.ts +48 -2
  197. package/Tests/Server/API/UserWebAuthnAuthenticationOptionsRoute.test.ts +11 -2
  198. package/Tests/Server/API/UserWebAuthnBackupCodeMinting.test.ts +46 -3
  199. package/Tests/Server/API/UserWebAuthnPasskeyAPI.test.ts +18 -2
  200. package/Tests/Server/API/ZZVerifyChatsAuth.test.ts +24 -7
  201. package/Tests/Server/Enterprise/EnterpriseEdition.test.ts +824 -0
  202. package/Tests/Server/Enterprise/EnterpriseFeatureActive.test.ts +643 -0
  203. package/Tests/Server/Enterprise/EnterpriseLicenseSnapshot.test.ts +294 -0
  204. package/Tests/Server/Enterprise/EnterpriseModelFeatureMap.test.ts +93 -0
  205. package/Tests/Server/Enterprise/EnterpriseOnlyModulesLiveInEe.test.ts +582 -0
  206. package/Tests/Server/Enterprise/EnterpriseServerModule.test.ts +247 -0
  207. package/Tests/Server/Enterprise/FakeEnterpriseModule.ts +502 -0
  208. package/Tests/Server/Enterprise/TestBillingFlag.ts +70 -0
  209. package/Tests/Server/EnvironmentConfigEnterpriseEdition.test.ts +314 -0
  210. package/Tests/Server/Infrastructure/ContainerBootConfiguration.test.ts +289 -0
  211. package/Tests/Server/Infrastructure/E2EClickHouseFixtureAccess.test.ts +5 -1
  212. package/Tests/Server/Middleware/ExpiredSessionUserMiddleware.test.ts +563 -0
  213. package/Tests/Server/Middleware/MasterAdminAuthorization.test.ts +299 -19
  214. package/Tests/Server/Middleware/PublicRouteUserMiddleware.test.ts +453 -0
  215. package/Tests/Server/Middleware/UserAuthorization.test.ts +37 -2
  216. package/Tests/Server/Middleware/UserAuthorizationEditionEnforcement.test.ts +516 -0
  217. package/Tests/Server/Services/AIChatPrivacyPin.test.ts +24 -0
  218. package/Tests/Server/Services/ApiKeyPermissionSecurity.test.ts +2 -0
  219. package/Tests/Server/Services/ApiKeyPermissionService.test.ts +3 -3
  220. package/Tests/Server/Services/AuditLogService.test.ts +460 -990
  221. package/Tests/Server/Services/CreateAndReadResponsesKeepIds.test.ts +263 -0
  222. package/Tests/Server/Services/DatabaseServiceCreateSuppliedId.test.ts +147 -0
  223. package/Tests/Server/Services/DatabaseServiceLoginBeforeHooks.test.ts +1078 -0
  224. package/Tests/Server/Services/DatabaseServiceTenantRelationGuard.test.ts +259 -0
  225. package/Tests/Server/Services/DatabaseServiceWriteOperatorValues.test.ts +147 -0
  226. package/Tests/Server/Services/EnterpriseLicenseServiceKeyGeneration.test.ts +151 -0
  227. package/Tests/Server/Services/GlobalConfigServiceLicenseColumns.test.ts +357 -0
  228. package/Tests/Server/Services/MonitorDestinationInfoIPv6.test.ts +101 -0
  229. package/Tests/Server/Services/MonitorPayAsYouGoBilling.test.ts +115 -1
  230. package/Tests/Server/Services/MonitorProbeServiceIntervalScheduling.test.ts +333 -0
  231. package/Tests/Server/Services/MonitorStatusTimelineDailyAggregates.test.ts +303 -0
  232. package/Tests/Server/Services/OnCallChildCreateAuthorizationPostgres.test.ts +711 -0
  233. package/Tests/Server/Services/OnCallDutyPolicyChildService.test.ts +733 -0
  234. package/Tests/Server/Services/PayAsYouGoBillingService.test.ts +190 -140
  235. package/Tests/Server/Services/PayAsYouGoStripeRateLimitBudget.test.ts +4 -9
  236. package/Tests/Server/Services/PermissionBlockDeletion.test.ts +780 -0
  237. package/Tests/Server/Services/PermissionBlockDeletionPostgres.test.ts +628 -0
  238. package/Tests/Server/Services/ProjectServiceAuditLogLicense.test.ts +701 -0
  239. package/Tests/Server/Services/ProjectServiceAuditLogSettingsCache.test.ts +31 -35
  240. package/Tests/Server/Services/ProjectServiceCreateSubscriptionStarted.test.ts +77 -1
  241. package/Tests/Server/Services/RumSessionPinCreatePermission.test.ts +715 -0
  242. package/Tests/Server/Services/ScimPushGroupsTeamLocksEdition.test.ts +431 -0
  243. package/Tests/Server/Services/SsoRequirementReadMasking.test.ts +419 -0
  244. package/Tests/Server/Services/SsoRequirementWriteGuard.test.ts +769 -0
  245. package/Tests/Server/Services/StatusPagePrivateUserInvitationPolicy.test.ts +94 -0
  246. package/Tests/Server/Services/TeamMemberMultiTenantReadPostgres.test.ts +394 -0
  247. package/Tests/Server/Services/TeamPrivilegeEscalation.test.ts +6 -0
  248. package/Tests/Server/Services/TelemetryPayAsYouGoBilling.test.ts +131 -0
  249. package/Tests/Server/Services/UserAuthenticationService.test.ts +35 -0
  250. package/Tests/Server/Services/UserNotificationRuleAdminGuards.test.ts +13 -2
  251. package/Tests/Server/Services/UserServiceSeatLimit.test.ts +94 -56
  252. package/Tests/Server/Types/Database/Permissions/EditionPermission.test.ts +1823 -0
  253. package/Tests/Server/Types/Database/Permissions/ModelPermissionAnonymousRefusal.test.ts +518 -0
  254. package/Tests/Server/Types/Database/Permissions/MultiTenantPermissions.test.ts +350 -0
  255. package/Tests/Server/Types/Database/Permissions/OnCallCalendarFeedPermissions.test.ts +282 -25
  256. package/Tests/Server/Types/Database/Permissions/TableLevelBlockPermission.test.ts +459 -0
  257. package/Tests/Server/Types/Markdown.test.ts +181 -0
  258. package/Tests/Server/Utils/AI/AIRunPrivacyFilter.test.ts +101 -9
  259. package/Tests/Server/Utils/AI/ChatAgentHelpers.test.ts +88 -4
  260. package/Tests/Server/Utils/AI/Toolbox/ExceptionTools.test.ts +459 -0
  261. package/Tests/Server/Utils/AI/Toolbox/LogTools.test.ts +794 -0
  262. package/Tests/Server/Utils/AI/Toolbox/RecentChangesTools.test.ts +574 -0
  263. package/Tests/Server/Utils/AI/Toolbox/SecurityEventTools.test.ts +596 -0
  264. package/Tests/Server/Utils/CaptchaRequestAndLogging.test.ts +395 -0
  265. package/Tests/Server/Utils/CommunityEditionSsoReport.test.ts +565 -0
  266. package/Tests/Server/Utils/EditionEnforcement.test.ts +303 -0
  267. package/Tests/Server/Utils/FrontendEnvironment.test.ts +367 -0
  268. package/Tests/Server/Utils/InstanceHealth/RedisHealth.test.ts +38 -418
  269. package/Tests/Server/Utils/MasterAdminPageAccess.test.ts +251 -0
  270. package/Tests/Server/Utils/Monitor/AffectedResourceList.test.ts +518 -0
  271. package/Tests/Server/Utils/Monitor/Criteria/EvaluateOverTime.test.ts +24 -4
  272. package/Tests/Server/Utils/Monitor/MonitorActionTimestamps.test.ts +616 -0
  273. package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluator.test.ts +934 -96
  274. package/Tests/Server/Utils/Monitor/MonitoringIntervalValidator.test.ts +188 -0
  275. package/Tests/Server/Utils/Realtime.test.ts +502 -0
  276. package/Tests/Server/Utils/SecurityEvent/Connectors/GoogleSecOps/GoogleSecOpsConnectorSeverity.test.ts +216 -0
  277. package/Tests/Server/Utils/SecurityEvent/DetectionRuleEvaluator.test.ts +142 -0
  278. package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsSeverityRepair.test.ts +140 -0
  279. package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsSeverityRepairClickhouseIntegration.test.ts +542 -0
  280. package/Tests/Server/Utils/Telemetry/ChangeEventRow.test.ts +743 -0
  281. package/Tests/Server/Utils/Telemetry/RuntimeMetrics.test.ts +465 -0
  282. package/Tests/Server/Utils/Workspace/MicrosoftTeams/Messages/Alert.test.ts +202 -0
  283. package/Tests/Server/Utils/Workspace/MicrosoftTeams/Messages/Incident.test.ts +194 -0
  284. package/Tests/Server/Utils/Workspace/MicrosoftTeams/Messages/ScheduledMaintenance.test.ts +231 -0
  285. package/Tests/Server/Utils/Workspace/Slack/Messages/Alert.test.ts +194 -0
  286. package/Tests/Server/Utils/Workspace/Slack/Messages/AlertEpisode.test.ts +194 -0
  287. package/Tests/Server/Utils/Workspace/Slack/Messages/Incident.test.ts +194 -0
  288. package/Tests/Server/Utils/Workspace/Slack/Messages/IncidentEpisode.test.ts +194 -0
  289. package/Tests/Server/Utils/Workspace/Slack/Messages/ScheduledMaintenance.test.ts +247 -0
  290. package/Tests/Server/Utils/Workspace/Slack/SlackMarkdownSectionSplit.test.ts +1209 -0
  291. package/Tests/Types/AI/AIInsightEvidence.test.ts +206 -0
  292. package/Tests/Types/API/HostnameFromAuthority.test.ts +20 -10
  293. package/Tests/Types/API/HostnameIPv6.test.ts +149 -0
  294. package/Tests/Types/BaseDatabase/DatabaseCommonInteractionPropsUtilAnonymous.test.ts +144 -0
  295. package/Tests/Types/Call/CallProvider.test.ts +528 -0
  296. package/Tests/Types/Database/CanonicalModelInstance.test.ts +190 -0
  297. package/Tests/Types/Kubernetes/KubernetesCostIngest.test.ts +236 -0
  298. package/Tests/Types/OnCallDutyPolicy/ScheduleTimeline.test.ts +736 -0
  299. package/Tests/Types/OnCallDutyPolicy/ScheduleTimelineLayout.test.ts +996 -0
  300. package/Tests/Types/SecurityEvent/SigmaRule.test.ts +142 -0
  301. package/Tests/Types/Workflow/Components/JSON.test.ts +182 -0
  302. package/Tests/UI/Components/BasicFormDropdownNormalization.test.tsx +116 -1
  303. package/Tests/UI/Components/Dropdown/DropdownAsyncOptions.test.tsx +81 -0
  304. package/Tests/UI/Components/EditionLabelLapseCopy.ts +104 -0
  305. package/Tests/UI/Components/EditionLabelLicenseManagerWiring.test.tsx +254 -0
  306. package/Tests/UI/Components/EditionLabelLicenseRefresh.test.tsx +165 -171
  307. package/Tests/UI/Components/EditionLabelLicenseStatus.test.tsx +1586 -0
  308. package/Tests/UI/Components/EditionLabelLicenseWritesGuard.test.tsx +157 -0
  309. package/Tests/UI/Components/EventAttachmentList.test.tsx +174 -0
  310. package/Tests/UI/Components/Forms/BasicFormBurnRateTemplateDefaults.test.tsx +395 -0
  311. package/Tests/UI/Components/Forms/BasicFormCollapsibleSections.test.tsx +286 -0
  312. package/Tests/UI/Components/Forms/FormSummary.test.tsx +128 -0
  313. package/Tests/UI/Components/Graphs/DayUptimeGraph.test.tsx +49 -2
  314. package/Tests/UI/Components/MarkdownConverters.test.ts +684 -0
  315. package/Tests/UI/Components/ModelListEmptyState.test.tsx +289 -0
  316. package/Tests/UI/Components/ModelTable/BaseModelTableQueryChange.test.tsx +365 -0
  317. package/Tests/UI/Components/OpenAuthenticatedUrl.test.ts +358 -0
  318. package/Tests/UI/Components/SessionAwareImage.test.tsx +496 -0
  319. package/Tests/UI/Components/TelemetryHistogramZeroBuckets.test.tsx +190 -0
  320. package/Tests/UI/Components/TelemetryViewer/TelemetryViewerEmptyContent.test.tsx +131 -0
  321. package/Tests/UI/Components/TooltipInteractive.test.tsx +97 -0
  322. package/Tests/UI/EsbuildEnterpriseAlias.test.ts +1127 -0
  323. package/Tests/UI/MicrosoftTeams/MicrosoftTeamsChatsCardInstallGuidance.test.tsx +1 -1
  324. package/Tests/UI/Monitor/EvaluationLogListTimestamps.test.tsx +367 -0
  325. package/Tests/UI/Utils/API/BaseAPIRefreshSession.test.ts +1087 -0
  326. package/Tests/UI/Utils/API/DashboardHost.ts +12 -0
  327. package/Tests/UI/Utils/API/FakeAxiosServer.ts +183 -0
  328. package/Tests/UI/Utils/Breadcrumb/fixtures/RealBreadcrumbTrails.ts +10 -0
  329. package/Tests/UI/Utils/Breadcrumb/fixtures/RealRoutePatterns.ts +2 -0
  330. package/Tests/UI/Utils/Cookie.test.ts +440 -0
  331. package/Tests/UI/Utils/ModelAPIRequestClient.test.ts +278 -0
  332. package/Tests/UI/Utils/Navigation.test.ts +33 -0
  333. package/Tests/UI/Utils/PricingPlan.test.ts +295 -0
  334. package/Tests/UI/Utils/Realtime.test.ts +759 -0
  335. package/Tests/UI/Utils/Telemetry/Telemetry.test.ts +500 -0
  336. package/Tests/Utils/APIInstanceDispatch.test.ts +311 -0
  337. package/Tests/Utils/HostAddressUtil.test.ts +138 -0
  338. package/Tests/Utils/Monitor/MonitorDestinationUtil.test.ts +313 -0
  339. package/Tests/Utils/Monitor/MonitoringIntervalUtil.test.ts +304 -0
  340. package/Tests/Utils/SecurityEvent/GoogleSecOpsAlertNormalizerSeverity.test.ts +565 -0
  341. package/Tests/Utils/StatusPage/UptimeDailyAggregateUtil.test.ts +248 -0
  342. package/Types/API/Hostname.ts +63 -16
  343. package/Types/BaseDatabase/DatabaseCommonInteractionPropsUtil.ts +40 -0
  344. package/Types/Database/AccessControl/ColumnAccessControl.ts +19 -7
  345. package/Types/Database/AccessControl/ColumnBillingAccessControl.ts +19 -7
  346. package/Types/Database/AccessControl/OwnerOnlyColumn.ts +15 -6
  347. package/Types/Database/CanonicalModelInstance.ts +102 -0
  348. package/Types/Database/TableColumn.ts +20 -6
  349. package/Types/EnterpriseLicense/EnterpriseLicensePeriods.ts +29 -0
  350. package/Types/IP/IPv6.ts +6 -1
  351. package/Types/Monitor/MonitorEvaluationSummary.ts +3 -0
  352. package/Types/Monitor/MonitorStep.ts +6 -1
  353. package/Types/Monitor/ProxmoxAlertTemplates.ts +1 -1
  354. package/Types/OnCallDutyPolicy/ScheduleTimeline.ts +507 -0
  355. package/Types/OnCallDutyPolicy/ScheduleTimelineLayout.ts +589 -0
  356. package/Types/Realtime/EventName.ts +11 -0
  357. package/Types/SecurityEvent/DetectionFindingConstants.ts +14 -0
  358. package/Types/SecurityEvent/OcsfSeverity.ts +31 -26
  359. package/Types/SecurityEvent/SigmaRule.ts +51 -0
  360. package/Types/SecurityEvent/ThreatIntelConstants.ts +18 -0
  361. package/Types/StatusPage/UptimeDailyAggregate.ts +110 -0
  362. package/UI/Components/AttachmentList/EventAttachmentList.tsx +30 -2
  363. package/UI/Components/Dashboard/DashboardVariableControl.tsx +12 -2
  364. package/UI/Components/Dropdown/Dropdown.tsx +6 -1
  365. package/UI/Components/EditionLabel/EditionLabel.tsx +808 -1195
  366. package/UI/Components/EditionLabel/LicenseManager.ts +119 -0
  367. package/UI/Components/EventItem/EventItem.tsx +12 -1
  368. package/UI/Components/Forms/BasicForm.tsx +151 -126
  369. package/UI/Components/Forms/CollapsibleFormSection.tsx +63 -0
  370. package/UI/Components/Forms/Fields/FormField.tsx +10 -4
  371. package/UI/Components/Forms/FormSummary.tsx +7 -0
  372. package/UI/Components/Forms/Types/Field.ts +10 -0
  373. package/UI/Components/Graphs/DayUptimeGraph.tsx +123 -15
  374. package/UI/Components/Markdown.tsx/MarkdownConverters.ts +0 -0
  375. package/UI/Components/Markdown.tsx/MarkdownViewer.tsx +2 -1
  376. package/UI/Components/Markdown.tsx/SessionAwareImage.tsx +122 -0
  377. package/UI/Components/ModelList/ModelList.tsx +11 -1
  378. package/UI/Components/ModelTable/BaseModelTable.tsx +34 -5
  379. package/UI/Components/ModelTable/TableView.tsx +1 -1
  380. package/UI/Components/MonitorGraphs/Uptime.tsx +69 -0
  381. package/UI/Components/TelemetryViewer/TelemetryViewer.tsx +28 -12
  382. package/UI/Components/TelemetryViewer/components/TelemetryHistogram.tsx +8 -1
  383. package/UI/Components/Tooltip/Tooltip.tsx +8 -1
  384. package/UI/Utils/API/API.ts +173 -12
  385. package/UI/Utils/API/RequestOptions.ts +11 -0
  386. package/UI/Utils/AnalyticsModelAPI/AnalyticsModelAPI.ts +31 -7
  387. package/UI/Utils/ModelAPI/ModelAPI.ts +30 -9
  388. package/UI/Utils/Navigation.ts +9 -0
  389. package/UI/Utils/OpenAuthenticatedUrl.ts +154 -0
  390. package/UI/Utils/Realtime.ts +176 -30
  391. package/UI/esbuild-enterprise.js +263 -0
  392. package/Utils/API.ts +14 -5
  393. package/Utils/EnterpriseLicense/EnterpriseLicenseUsage.ts +7 -2
  394. package/Utils/HostAddressUtil.ts +94 -0
  395. package/Utils/Monitor/MonitorDestinationUtil.ts +157 -0
  396. package/Utils/Monitor/MonitoringIntervalUtil.ts +436 -0
  397. package/Utils/SecurityEvent/GoogleSecOpsAlertNormalizer.ts +157 -7
  398. package/Utils/SecurityEvent/Sigma/SigmaRuleParser.ts +9 -17
  399. package/Utils/Slo/SloBurnRateTemplate.ts +2 -2
  400. package/Utils/StatusPage/UptimeDailyAggregateUtil.ts +192 -0
  401. package/build/dist/Models/AnalyticsModels/AuditLog.js +23 -15
  402. package/build/dist/Models/AnalyticsModels/AuditLog.js.map +1 -1
  403. package/build/dist/Models/DatabaseModels/GlobalConfig.js +25 -0
  404. package/build/dist/Models/DatabaseModels/GlobalConfig.js.map +1 -1
  405. package/build/dist/Models/DatabaseModels/Monitor.js +2 -2
  406. package/build/dist/Models/DatabaseModels/Monitor.js.map +1 -1
  407. package/build/dist/Models/DatabaseModels/MonitorTemplate.js +2 -1
  408. package/build/dist/Models/DatabaseModels/MonitorTemplate.js.map +1 -1
  409. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyScheduleCalendarFeed.js +1 -1
  410. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyScheduleCalendarFeed.js.map +1 -1
  411. package/build/dist/Models/DatabaseModels/ProjectOnCallCalendarFeed.js +1 -1
  412. package/build/dist/Models/DatabaseModels/ProjectOnCallCalendarFeed.js.map +1 -1
  413. package/build/dist/Models/DatabaseModels/UserOnCallCalendarFeed.js +1 -1
  414. package/build/dist/Models/DatabaseModels/UserOnCallCalendarFeed.js.map +1 -1
  415. package/build/dist/Server/API/AIBillingAPI.js +1 -1
  416. package/build/dist/Server/API/AIBillingAPI.js.map +1 -1
  417. package/build/dist/Server/API/AIInsightAPI.js +1 -4
  418. package/build/dist/Server/API/AIInsightAPI.js.map +1 -1
  419. package/build/dist/Server/API/AIInvestigationAPI.js +1 -4
  420. package/build/dist/Server/API/AIInvestigationAPI.js.map +1 -1
  421. package/build/dist/Server/API/AIReadinessAPI.js +1 -3
  422. package/build/dist/Server/API/AIReadinessAPI.js.map +1 -1
  423. package/build/dist/Server/API/AlertAPI.js +1 -1
  424. package/build/dist/Server/API/AlertAPI.js.map +1 -1
  425. package/build/dist/Server/API/AutoRemediationAPI.js +1 -4
  426. package/build/dist/Server/API/AutoRemediationAPI.js.map +1 -1
  427. package/build/dist/Server/API/BaseAPI.js +33 -0
  428. package/build/dist/Server/API/BaseAPI.js.map +1 -1
  429. package/build/dist/Server/API/BaseAnalyticsAPI.js +55 -10
  430. package/build/dist/Server/API/BaseAnalyticsAPI.js.map +1 -1
  431. package/build/dist/Server/API/BillingAPI.js +2 -2
  432. package/build/dist/Server/API/BillingAPI.js.map +1 -1
  433. package/build/dist/Server/API/BillingInvoiceAPI.js +1 -1
  434. package/build/dist/Server/API/BillingInvoiceAPI.js.map +1 -1
  435. package/build/dist/Server/API/BillingPaymentMethodAPI.js +2 -2
  436. package/build/dist/Server/API/BillingPaymentMethodAPI.js.map +1 -1
  437. package/build/dist/Server/API/CodeFixRunAPI.js +1 -3
  438. package/build/dist/Server/API/CodeFixRunAPI.js.map +1 -1
  439. package/build/dist/Server/API/CommonAPI.js +50 -5
  440. package/build/dist/Server/API/CommonAPI.js.map +1 -1
  441. package/build/dist/Server/API/DashboardAPI.js +12 -12
  442. package/build/dist/Server/API/DashboardAPI.js.map +1 -1
  443. package/build/dist/Server/API/GlobalConfigAPI.js +158 -342
  444. package/build/dist/Server/API/GlobalConfigAPI.js.map +1 -1
  445. package/build/dist/Server/API/IncidentAPI.js +2 -2
  446. package/build/dist/Server/API/IncidentAPI.js.map +1 -1
  447. package/build/dist/Server/API/IncidentEpisodeAPI.js +1 -1
  448. package/build/dist/Server/API/IncidentEpisodeAPI.js.map +1 -1
  449. package/build/dist/Server/API/NotificationAPI.js +1 -1
  450. package/build/dist/Server/API/NotificationAPI.js.map +1 -1
  451. package/build/dist/Server/API/OnCallCalendarAPI.js +1 -4
  452. package/build/dist/Server/API/OnCallCalendarAPI.js.map +1 -1
  453. package/build/dist/Server/API/OnCallDutyPolicyAPI.js +6 -0
  454. package/build/dist/Server/API/OnCallDutyPolicyAPI.js.map +1 -1
  455. package/build/dist/Server/API/OnCallReadinessAPI.js +4 -1
  456. package/build/dist/Server/API/OnCallReadinessAPI.js.map +1 -1
  457. package/build/dist/Server/API/OnCallScheduleTimelineAPI.js +494 -0
  458. package/build/dist/Server/API/OnCallScheduleTimelineAPI.js.map +1 -0
  459. package/build/dist/Server/API/ProjectAPI.js +1 -1
  460. package/build/dist/Server/API/ProjectAPI.js.map +1 -1
  461. package/build/dist/Server/API/ProjectOIDC.js +24 -16
  462. package/build/dist/Server/API/ProjectOIDC.js.map +1 -1
  463. package/build/dist/Server/API/ProjectSSO.js +24 -16
  464. package/build/dist/Server/API/ProjectSSO.js.map +1 -1
  465. package/build/dist/Server/API/ScheduledMaintenanceAPI.js +1 -1
  466. package/build/dist/Server/API/ScheduledMaintenanceAPI.js.map +1 -1
  467. package/build/dist/Server/API/StatusPageAPI.js +218 -76
  468. package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
  469. package/build/dist/Server/API/TeamMemberAPI.js +11 -4
  470. package/build/dist/Server/API/TeamMemberAPI.js.map +1 -1
  471. package/build/dist/Server/API/UserCallAPI.js +2 -2
  472. package/build/dist/Server/API/UserCallAPI.js.map +1 -1
  473. package/build/dist/Server/API/UserEmailAPI.js +2 -2
  474. package/build/dist/Server/API/UserEmailAPI.js.map +1 -1
  475. package/build/dist/Server/API/UserIncomingCallNumberAPI.js +2 -2
  476. package/build/dist/Server/API/UserIncomingCallNumberAPI.js.map +1 -1
  477. package/build/dist/Server/API/UserMicrosoftTeamsAPI.js +1 -1
  478. package/build/dist/Server/API/UserMicrosoftTeamsAPI.js.map +1 -1
  479. package/build/dist/Server/API/UserSlackAPI.js +1 -1
  480. package/build/dist/Server/API/UserSlackAPI.js.map +1 -1
  481. package/build/dist/Server/API/UserSmsAPI.js +2 -2
  482. package/build/dist/Server/API/UserSmsAPI.js.map +1 -1
  483. package/build/dist/Server/API/UserTotpAuthAPI.js +1 -1
  484. package/build/dist/Server/API/UserTotpAuthAPI.js.map +1 -1
  485. package/build/dist/Server/API/UserWebAuthnAPI.js +2 -2
  486. package/build/dist/Server/API/UserWebAuthnAPI.js.map +1 -1
  487. package/build/dist/Server/API/UserWebhookAPI.js +1 -1
  488. package/build/dist/Server/API/UserWebhookAPI.js.map +1 -1
  489. package/build/dist/Server/API/UserWhatsAppAPI.js +2 -2
  490. package/build/dist/Server/API/UserWhatsAppAPI.js.map +1 -1
  491. package/build/dist/Server/Enterprise/EnterpriseEdition.js +466 -0
  492. package/build/dist/Server/Enterprise/EnterpriseEdition.js.map +1 -0
  493. package/build/dist/Server/Enterprise/EnterpriseFeature.js +39 -0
  494. package/build/dist/Server/Enterprise/EnterpriseFeature.js.map +1 -0
  495. package/build/dist/Server/Enterprise/EnterpriseLicenseSnapshot.js +70 -0
  496. package/build/dist/Server/Enterprise/EnterpriseLicenseSnapshot.js.map +1 -0
  497. package/build/dist/Server/Enterprise/EnterpriseServerModule.js +84 -0
  498. package/build/dist/Server/Enterprise/EnterpriseServerModule.js.map +1 -0
  499. package/build/dist/Server/EnvironmentConfig.js +84 -2
  500. package/build/dist/Server/EnvironmentConfig.js.map +1 -1
  501. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1793900000000-AddEnterpriseEditionFirstSeenAt.js +18 -0
  502. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1793900000000-AddEnterpriseEditionFirstSeenAt.js.map +1 -0
  503. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +2 -0
  504. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  505. package/build/dist/Server/Middleware/MasterAdminAuthorization.js +11 -2
  506. package/build/dist/Server/Middleware/MasterAdminAuthorization.js.map +1 -1
  507. package/build/dist/Server/Middleware/UserAuthorization.js +94 -15
  508. package/build/dist/Server/Middleware/UserAuthorization.js.map +1 -1
  509. package/build/dist/Server/Services/ApiKeyPermissionService.js +80 -4
  510. package/build/dist/Server/Services/ApiKeyPermissionService.js.map +1 -1
  511. package/build/dist/Server/Services/AuditLogService.js +71 -740
  512. package/build/dist/Server/Services/AuditLogService.js.map +1 -1
  513. package/build/dist/Server/Services/DatabaseService.js +171 -0
  514. package/build/dist/Server/Services/DatabaseService.js.map +1 -1
  515. package/build/dist/Server/Services/DetectionRuleService.js +4 -4
  516. package/build/dist/Server/Services/DetectionRuleService.js.map +1 -1
  517. package/build/dist/Server/Services/EnterpriseLicenseService.js +34 -0
  518. package/build/dist/Server/Services/EnterpriseLicenseService.js.map +1 -1
  519. package/build/dist/Server/Services/GlobalConfigService.js +63 -0
  520. package/build/dist/Server/Services/GlobalConfigService.js.map +1 -1
  521. package/build/dist/Server/Services/MonitorProbeService.js +90 -17
  522. package/build/dist/Server/Services/MonitorProbeService.js.map +1 -1
  523. package/build/dist/Server/Services/MonitorService.js +45 -4
  524. package/build/dist/Server/Services/MonitorService.js.map +1 -1
  525. package/build/dist/Server/Services/MonitorStatusTimelineService.js +242 -0
  526. package/build/dist/Server/Services/MonitorStatusTimelineService.js.map +1 -1
  527. package/build/dist/Server/Services/MonitorTemplateService.js +16 -0
  528. package/build/dist/Server/Services/MonitorTemplateService.js.map +1 -1
  529. package/build/dist/Server/Services/OnCallDutyPolicyChildService.js +170 -0
  530. package/build/dist/Server/Services/OnCallDutyPolicyChildService.js.map +1 -0
  531. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.js +2 -2
  532. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.js.map +1 -1
  533. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleService.js +2 -2
  534. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleService.js.map +1 -1
  535. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleTeamService.js +2 -2
  536. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleTeamService.js.map +1 -1
  537. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleUserService.js +2 -2
  538. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleUserService.js.map +1 -1
  539. package/build/dist/Server/Services/PayAsYouGoBillingService.js +20 -27
  540. package/build/dist/Server/Services/PayAsYouGoBillingService.js.map +1 -1
  541. package/build/dist/Server/Services/ProjectService.js +231 -0
  542. package/build/dist/Server/Services/ProjectService.js.map +1 -1
  543. package/build/dist/Server/Services/RumSessionPinService.js +198 -16
  544. package/build/dist/Server/Services/RumSessionPinService.js.map +1 -1
  545. package/build/dist/Server/Services/StatusPagePrivateUserService.js +7 -3
  546. package/build/dist/Server/Services/StatusPagePrivateUserService.js.map +1 -1
  547. package/build/dist/Server/Services/StatusPageService.js +71 -0
  548. package/build/dist/Server/Services/StatusPageService.js.map +1 -1
  549. package/build/dist/Server/Services/TeamMemberService.js +10 -0
  550. package/build/dist/Server/Services/TeamMemberService.js.map +1 -1
  551. package/build/dist/Server/Services/TeamPermissionService.js +89 -27
  552. package/build/dist/Server/Services/TeamPermissionService.js.map +1 -1
  553. package/build/dist/Server/Services/TeamService.js +9 -0
  554. package/build/dist/Server/Services/TeamService.js.map +1 -1
  555. package/build/dist/Server/Services/ThreatIntelFeedService.js +5 -4
  556. package/build/dist/Server/Services/ThreatIntelFeedService.js.map +1 -1
  557. package/build/dist/Server/Services/UserService.js +10 -16
  558. package/build/dist/Server/Services/UserService.js.map +1 -1
  559. package/build/dist/Server/Types/Database/Permissions/BasePermission.js +9 -6
  560. package/build/dist/Server/Types/Database/Permissions/BasePermission.js.map +1 -1
  561. package/build/dist/Server/Types/Database/Permissions/CreatePermission.js +10 -0
  562. package/build/dist/Server/Types/Database/Permissions/CreatePermission.js.map +1 -1
  563. package/build/dist/Server/Types/Database/Permissions/EditionPermission.js +154 -14
  564. package/build/dist/Server/Types/Database/Permissions/EditionPermission.js.map +1 -1
  565. package/build/dist/Server/Types/Database/Permissions/Index.js +60 -6
  566. package/build/dist/Server/Types/Database/Permissions/Index.js.map +1 -1
  567. package/build/dist/Server/Types/Database/Permissions/TablePermission.js +24 -16
  568. package/build/dist/Server/Types/Database/Permissions/TablePermission.js.map +1 -1
  569. package/build/dist/Server/Types/Database/Permissions/UpdatePermission.js +17 -0
  570. package/build/dist/Server/Types/Database/Permissions/UpdatePermission.js.map +1 -1
  571. package/build/dist/Server/Types/Markdown.js +31 -0
  572. package/build/dist/Server/Types/Markdown.js.map +1 -1
  573. package/build/dist/Server/Utils/AI/AIChatPrivacyFilter.js +7 -0
  574. package/build/dist/Server/Utils/AI/AIChatPrivacyFilter.js.map +1 -1
  575. package/build/dist/Server/Utils/AI/AIRunPrivacyFilter.js +7 -0
  576. package/build/dist/Server/Utils/AI/AIRunPrivacyFilter.js.map +1 -1
  577. package/build/dist/Server/Utils/CommunityEditionSsoReport.js +223 -0
  578. package/build/dist/Server/Utils/CommunityEditionSsoReport.js.map +1 -0
  579. package/build/dist/Server/Utils/EditionEnforcement.js +187 -0
  580. package/build/dist/Server/Utils/EditionEnforcement.js.map +1 -0
  581. package/build/dist/Server/Utils/FrontendEnvironment.js +31 -2
  582. package/build/dist/Server/Utils/FrontendEnvironment.js.map +1 -1
  583. package/build/dist/Server/Utils/InstanceHealth/RedisHealth.js +6 -193
  584. package/build/dist/Server/Utils/InstanceHealth/RedisHealth.js.map +1 -1
  585. package/build/dist/Server/Utils/MasterAdminPageAccess.js +39 -0
  586. package/build/dist/Server/Utils/MasterAdminPageAccess.js.map +1 -0
  587. package/build/dist/Server/Utils/Monitor/AffectedResourceList.js +96 -0
  588. package/build/dist/Server/Utils/Monitor/AffectedResourceList.js.map +1 -0
  589. package/build/dist/Server/Utils/Monitor/Criteria/EvaluateOverTime.js +8 -1
  590. package/build/dist/Server/Utils/Monitor/Criteria/EvaluateOverTime.js.map +1 -1
  591. package/build/dist/Server/Utils/Monitor/MonitorAlert.js +3 -1
  592. package/build/dist/Server/Utils/Monitor/MonitorAlert.js.map +1 -1
  593. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +449 -261
  594. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
  595. package/build/dist/Server/Utils/Monitor/MonitorIncident.js +3 -1
  596. package/build/dist/Server/Utils/Monitor/MonitorIncident.js.map +1 -1
  597. package/build/dist/Server/Utils/Monitor/MonitoringIntervalValidator.js +88 -0
  598. package/build/dist/Server/Utils/Monitor/MonitoringIntervalValidator.js.map +1 -0
  599. package/build/dist/Server/Utils/Realtime.js +126 -37
  600. package/build/dist/Server/Utils/Realtime.js.map +1 -1
  601. package/build/dist/Server/Utils/SecurityEvent/DetectionRuleEvaluator.js +7 -25
  602. package/build/dist/Server/Utils/SecurityEvent/DetectionRuleEvaluator.js.map +1 -1
  603. package/build/dist/Server/Utils/SecurityEvent/GoogleSecOpsSeverityRepair.js +127 -0
  604. package/build/dist/Server/Utils/SecurityEvent/GoogleSecOpsSeverityRepair.js.map +1 -0
  605. package/build/dist/Server/Utils/SecurityEvent/Sigma/SigmaClickhouseCompiler.js +1 -1
  606. package/build/dist/Server/Utils/SecurityEvent/Sigma/SigmaClickhouseCompiler.js.map +1 -1
  607. package/build/dist/Server/Utils/SecurityEvent/ThreatIntel/ThreatIntelFeedPoller.js +3 -2
  608. package/build/dist/Server/Utils/SecurityEvent/ThreatIntel/ThreatIntelFeedPoller.js.map +1 -1
  609. package/build/dist/Server/Utils/SecurityEvent/ThreatIntel/ThreatIntelMatcher.js +4 -8
  610. package/build/dist/Server/Utils/SecurityEvent/ThreatIntel/ThreatIntelMatcher.js.map +1 -1
  611. package/build/dist/Server/Utils/Workspace/Slack/Slack.js +331 -11
  612. package/build/dist/Server/Utils/Workspace/Slack/Slack.js.map +1 -1
  613. package/build/dist/Types/API/Hostname.js +59 -11
  614. package/build/dist/Types/API/Hostname.js.map +1 -1
  615. package/build/dist/Types/BaseDatabase/DatabaseCommonInteractionPropsUtil.js +31 -1
  616. package/build/dist/Types/BaseDatabase/DatabaseCommonInteractionPropsUtil.js.map +1 -1
  617. package/build/dist/Types/Database/AccessControl/ColumnAccessControl.js +19 -7
  618. package/build/dist/Types/Database/AccessControl/ColumnAccessControl.js.map +1 -1
  619. package/build/dist/Types/Database/AccessControl/ColumnBillingAccessControl.js +19 -7
  620. package/build/dist/Types/Database/AccessControl/ColumnBillingAccessControl.js.map +1 -1
  621. package/build/dist/Types/Database/AccessControl/OwnerOnlyColumn.js +13 -6
  622. package/build/dist/Types/Database/AccessControl/OwnerOnlyColumn.js.map +1 -1
  623. package/build/dist/Types/Database/CanonicalModelInstance.js +88 -0
  624. package/build/dist/Types/Database/CanonicalModelInstance.js.map +1 -0
  625. package/build/dist/Types/Database/TableColumn.js +19 -6
  626. package/build/dist/Types/Database/TableColumn.js.map +1 -1
  627. package/build/dist/Types/EnterpriseLicense/EnterpriseLicensePeriods.js +28 -0
  628. package/build/dist/Types/EnterpriseLicense/EnterpriseLicensePeriods.js.map +1 -0
  629. package/build/dist/Types/IP/IPv6.js +6 -1
  630. package/build/dist/Types/IP/IPv6.js.map +1 -1
  631. package/build/dist/Types/Monitor/MonitorStep.js +5 -0
  632. package/build/dist/Types/Monitor/MonitorStep.js.map +1 -1
  633. package/build/dist/Types/Monitor/ProxmoxAlertTemplates.js +1 -1
  634. package/build/dist/Types/OnCallDutyPolicy/ScheduleTimeline.js +321 -0
  635. package/build/dist/Types/OnCallDutyPolicy/ScheduleTimeline.js.map +1 -0
  636. package/build/dist/Types/OnCallDutyPolicy/ScheduleTimelineLayout.js +372 -0
  637. package/build/dist/Types/OnCallDutyPolicy/ScheduleTimelineLayout.js.map +1 -0
  638. package/build/dist/Types/Realtime/EventName.js +10 -0
  639. package/build/dist/Types/Realtime/EventName.js.map +1 -1
  640. package/build/dist/Types/SecurityEvent/DetectionFindingConstants.js +11 -0
  641. package/build/dist/Types/SecurityEvent/DetectionFindingConstants.js.map +1 -1
  642. package/build/dist/Types/SecurityEvent/OcsfSeverity.js +30 -25
  643. package/build/dist/Types/SecurityEvent/OcsfSeverity.js.map +1 -1
  644. package/build/dist/Types/SecurityEvent/SigmaRule.js +47 -0
  645. package/build/dist/Types/SecurityEvent/SigmaRule.js.map +1 -1
  646. package/build/dist/Types/SecurityEvent/ThreatIntelConstants.js +14 -0
  647. package/build/dist/Types/SecurityEvent/ThreatIntelConstants.js.map +1 -1
  648. package/build/dist/Types/StatusPage/UptimeDailyAggregate.js +32 -0
  649. package/build/dist/Types/StatusPage/UptimeDailyAggregate.js.map +1 -0
  650. package/build/dist/UI/Components/AttachmentList/EventAttachmentList.js +9 -4
  651. package/build/dist/UI/Components/AttachmentList/EventAttachmentList.js.map +1 -1
  652. package/build/dist/UI/Components/Dashboard/DashboardVariableControl.js +10 -2
  653. package/build/dist/UI/Components/Dashboard/DashboardVariableControl.js.map +1 -1
  654. package/build/dist/UI/Components/Dropdown/Dropdown.js +5 -1
  655. package/build/dist/UI/Components/Dropdown/Dropdown.js.map +1 -1
  656. package/build/dist/UI/Components/EditionLabel/EditionLabel.js +425 -585
  657. package/build/dist/UI/Components/EditionLabel/EditionLabel.js.map +1 -1
  658. package/build/dist/UI/Components/EditionLabel/LicenseManager.js +2 -0
  659. package/build/dist/UI/Components/EditionLabel/LicenseManager.js.map +1 -0
  660. package/build/dist/UI/Components/EventItem/EventItem.js +2 -2
  661. package/build/dist/UI/Components/EventItem/EventItem.js.map +1 -1
  662. package/build/dist/UI/Components/Forms/BasicForm.js +70 -64
  663. package/build/dist/UI/Components/Forms/BasicForm.js.map +1 -1
  664. package/build/dist/UI/Components/Forms/CollapsibleFormSection.js +27 -0
  665. package/build/dist/UI/Components/Forms/CollapsibleFormSection.js.map +1 -0
  666. package/build/dist/UI/Components/Forms/Fields/FormField.js +10 -7
  667. package/build/dist/UI/Components/Forms/Fields/FormField.js.map +1 -1
  668. package/build/dist/UI/Components/Forms/FormSummary.js +7 -0
  669. package/build/dist/UI/Components/Forms/FormSummary.js.map +1 -1
  670. package/build/dist/UI/Components/Graphs/DayUptimeGraph.js +90 -17
  671. package/build/dist/UI/Components/Graphs/DayUptimeGraph.js.map +1 -1
  672. package/build/dist/UI/Components/Markdown.tsx/MarkdownConverters.js +0 -0
  673. package/build/dist/UI/Components/Markdown.tsx/MarkdownConverters.js.map +1 -1
  674. package/build/dist/UI/Components/Markdown.tsx/MarkdownViewer.js +2 -1
  675. package/build/dist/UI/Components/Markdown.tsx/MarkdownViewer.js.map +1 -1
  676. package/build/dist/UI/Components/Markdown.tsx/SessionAwareImage.js +84 -0
  677. package/build/dist/UI/Components/Markdown.tsx/SessionAwareImage.js.map +1 -0
  678. package/build/dist/UI/Components/ModelList/ModelList.js +4 -1
  679. package/build/dist/UI/Components/ModelList/ModelList.js.map +1 -1
  680. package/build/dist/UI/Components/ModelTable/BaseModelTable.js +14 -1
  681. package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
  682. package/build/dist/UI/Components/ModelTable/TableView.js +1 -1
  683. package/build/dist/UI/Components/ModelTable/TableView.js.map +1 -1
  684. package/build/dist/UI/Components/MonitorGraphs/Uptime.js +42 -1
  685. package/build/dist/UI/Components/MonitorGraphs/Uptime.js.map +1 -1
  686. package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js +2 -2
  687. package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js.map +1 -1
  688. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogram.js +8 -1
  689. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogram.js.map +1 -1
  690. package/build/dist/UI/Components/Tooltip/Tooltip.js +2 -1
  691. package/build/dist/UI/Components/Tooltip/Tooltip.js.map +1 -1
  692. package/build/dist/UI/Utils/API/API.js +143 -19
  693. package/build/dist/UI/Utils/API/API.js.map +1 -1
  694. package/build/dist/UI/Utils/AnalyticsModelAPI/AnalyticsModelAPI.js +21 -7
  695. package/build/dist/UI/Utils/AnalyticsModelAPI/AnalyticsModelAPI.js.map +1 -1
  696. package/build/dist/UI/Utils/ModelAPI/ModelAPI.js +26 -9
  697. package/build/dist/UI/Utils/ModelAPI/ModelAPI.js.map +1 -1
  698. package/build/dist/UI/Utils/Navigation.js +8 -0
  699. package/build/dist/UI/Utils/Navigation.js.map +1 -1
  700. package/build/dist/UI/Utils/OpenAuthenticatedUrl.js +92 -0
  701. package/build/dist/UI/Utils/OpenAuthenticatedUrl.js.map +1 -0
  702. package/build/dist/UI/Utils/Realtime.js +120 -15
  703. package/build/dist/UI/Utils/Realtime.js.map +1 -1
  704. package/build/dist/Utils/API.js +13 -5
  705. package/build/dist/Utils/API.js.map +1 -1
  706. package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseUsage.js +6 -2
  707. package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseUsage.js.map +1 -1
  708. package/build/dist/Utils/HostAddressUtil.js +80 -0
  709. package/build/dist/Utils/HostAddressUtil.js.map +1 -0
  710. package/build/dist/Utils/Monitor/MonitorDestinationUtil.js +127 -0
  711. package/build/dist/Utils/Monitor/MonitorDestinationUtil.js.map +1 -0
  712. package/build/dist/Utils/Monitor/MonitoringIntervalUtil.js +374 -0
  713. package/build/dist/Utils/Monitor/MonitoringIntervalUtil.js.map +1 -0
  714. package/build/dist/Utils/SecurityEvent/GoogleSecOpsAlertNormalizer.js +123 -6
  715. package/build/dist/Utils/SecurityEvent/GoogleSecOpsAlertNormalizer.js.map +1 -1
  716. package/build/dist/Utils/SecurityEvent/Sigma/SigmaRuleParser.js +4 -18
  717. package/build/dist/Utils/SecurityEvent/Sigma/SigmaRuleParser.js.map +1 -1
  718. package/build/dist/Utils/Slo/SloBurnRateTemplate.js +2 -2
  719. package/build/dist/Utils/StatusPage/UptimeDailyAggregateUtil.js +148 -0
  720. package/build/dist/Utils/StatusPage/UptimeDailyAggregateUtil.js.map +1 -0
  721. package/jest.config.json +3 -1
  722. package/package.json +1 -1
  723. package/test-setup.sh +103 -34
  724. package/tsconfig.json +13 -0
  725. package/Server/API/EnterpriseLicenseAPI.ts +0 -925
  726. package/Server/API/TeamComplianceAPI.ts +0 -158
  727. package/Server/Middleware/SCIMAuthorization.ts +0 -141
  728. package/Server/Services/TeamComplianceService.ts +0 -733
  729. package/Server/Utils/EnterpriseLicense/EnterpriseLicenseSeatUtil.ts +0 -186
  730. package/Server/Utils/InstanceHealth/PostgresHealth.ts +0 -286
  731. package/Tests/App/AdminDashboard/EnterpriseLicenseInstanceActivity.test.tsx +0 -451
  732. package/Tests/App/Dashboard/AuditLogsTable.test.tsx +0 -598
  733. package/Tests/App/Dashboard/SecurityEventsSideMenu.test.tsx +0 -247
  734. package/Tests/App/Dashboard/SecurityEventsTableColumns.test.tsx +0 -382
  735. package/Tests/Server/API/EnterpriseLicenseReportUserCount.test.ts +0 -1851
  736. package/Tests/Server/Middleware/SCIMAuthorization.test.ts +0 -525
  737. package/Tests/Server/Services/TeamComplianceServiceBehaviour.test.ts +0 -1845
  738. package/Tests/Server/Utils/EnterpriseLicense/EnterpriseLicenseSeatUtil.test.ts +0 -495
  739. package/Tests/Server/Utils/InstanceHealth/PostgresHealth.test.ts +0 -337
  740. package/Tests/Utils/EnterpriseLicenseSeats.test.ts +0 -576
  741. package/Tests/Utils/EnterpriseLicenseSync.test.ts +0 -568
  742. package/Utils/EnterpriseLicense/EnterpriseLicenseSeats.ts +0 -287
  743. package/Utils/EnterpriseLicense/EnterpriseLicenseSync.ts +0 -244
  744. package/build/dist/Server/API/EnterpriseLicenseAPI.js +0 -646
  745. package/build/dist/Server/API/EnterpriseLicenseAPI.js.map +0 -1
  746. package/build/dist/Server/API/TeamComplianceAPI.js +0 -111
  747. package/build/dist/Server/API/TeamComplianceAPI.js.map +0 -1
  748. package/build/dist/Server/Middleware/SCIMAuthorization.js +0 -124
  749. package/build/dist/Server/Middleware/SCIMAuthorization.js.map +0 -1
  750. package/build/dist/Server/Services/TeamComplianceService.js +0 -537
  751. package/build/dist/Server/Services/TeamComplianceService.js.map +0 -1
  752. package/build/dist/Server/Utils/EnterpriseLicense/EnterpriseLicenseSeatUtil.js +0 -145
  753. package/build/dist/Server/Utils/EnterpriseLicense/EnterpriseLicenseSeatUtil.js.map +0 -1
  754. package/build/dist/Server/Utils/InstanceHealth/PostgresHealth.js +0 -194
  755. package/build/dist/Server/Utils/InstanceHealth/PostgresHealth.js.map +0 -1
  756. package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseSeats.js +0 -168
  757. package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseSeats.js.map +0 -1
  758. package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseSync.js +0 -174
  759. package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseSync.js.map +0 -1
@@ -1,1851 +0,0 @@
1
- import EnterpriseLicenseAPI from "../../../Server/API/EnterpriseLicenseAPI";
2
- import EnterpriseLicenseService from "../../../Server/Services/EnterpriseLicenseService";
3
- import EnterpriseLicenseInstanceService from "../../../Server/Services/EnterpriseLicenseInstanceService";
4
- import JSONWebToken from "../../../Server/Utils/JsonWebToken";
5
- import Response from "../../../Server/Utils/Response";
6
- import EnterpriseLicense from "../../../Models/DatabaseModels/EnterpriseLicense";
7
- import EnterpriseLicenseInstance from "../../../Models/DatabaseModels/EnterpriseLicenseInstance";
8
- import BadDataException from "../../../Types/Exception/BadDataException";
9
- import ObjectID from "../../../Types/ObjectID";
10
- import OneUptimeDate from "../../../Types/Date";
11
- import PositiveNumber from "../../../Types/PositiveNumber";
12
- import { JSONObject } from "../../../Types/JSON";
13
- import EnterpriseLicenseSyncUtil, {
14
- EnterpriseLicenseSyncResult,
15
- } from "../../../Utils/EnterpriseLicense/EnterpriseLicenseSync";
16
- import EnterpriseLicenseUsageUtil from "../../../Utils/EnterpriseLicense/EnterpriseLicenseUsage";
17
- import EnterpriseLicenseUserCountSource from "../../../Types/EnterpriseLicense/EnterpriseLicenseUserCountSource";
18
- import EnterpriseLicenseInstanceSummary from "../../../Types/EnterpriseLicense/EnterpriseLicenseInstanceSummary";
19
- import MasterAdminAuthorization from "../../../Server/Middleware/MasterAdminAuthorization";
20
- import {
21
- NextFunction,
22
- OneUptimeRequest,
23
- OneUptimeResponse,
24
- } from "../../../Server/Utils/Express";
25
- import { mockRouter } from "./Helpers";
26
- import { beforeEach, afterEach, describe, expect, it } from "@jest/globals";
27
-
28
- /*
29
- * The license-server half of the seat-limit sync.
30
- *
31
- * /report-user-count is the daily call a self-hosted installation makes, and
32
- * its response is the ONLY thing that installation can learn from without a
33
- * human re-typing the license key. It used to answer with the usage numbers
34
- * and the seat limit but nothing else, and the job on the other end dropped
35
- * the seat limit on the floor — so raising a customer's limit on oneuptime.com
36
- * never reached the installation enforcing it.
37
- *
38
- * These tests pin the response as a complete statement of the license terms,
39
- * and pin the one thing that must NOT be unconditional: an expired license
40
- * still gets a straight answer (it has to keep reporting, the expiry emails
41
- * are built from those reports) but it does not get a fresh token.
42
- */
43
-
44
- /*
45
- * PasswordHash carries a pre-existing TS diagnostic that fails any suite whose
46
- * require graph reaches it, and DatabaseService — the base of every concrete
47
- * service below — imports it. Replaced with a factory rather than automocked,
48
- * because an automock still type-checks the real file.
49
- */
50
- jest.mock("../../../Server/Utils/PasswordHash", () => {
51
- return {
52
- __esModule: true,
53
- default: {
54
- hash: jest.fn(),
55
- verify: jest.fn(),
56
- generateSalt: jest.fn(),
57
- needsUpgrade: jest.fn(),
58
- applyPepper: jest.fn(),
59
- },
60
- };
61
- });
62
-
63
- /*
64
- * Same story as PasswordHash: a pre-existing local-only TS diagnostic in a
65
- * module the service graph drags in. Nothing here verifies a code.
66
- */
67
- jest.mock("../../../Server/Utils/VerificationCode", () => {
68
- return {
69
- __esModule: true,
70
- default: {
71
- generate: jest.fn(),
72
- hashCode: jest.fn(),
73
- isHashEqual: jest.fn(),
74
- generateUnusableHash: jest.fn(),
75
- },
76
- };
77
- });
78
-
79
- jest.mock("../../../Server/Utils/Express", () => {
80
- return {
81
- getRouter: () => {
82
- return mockRouter;
83
- },
84
- };
85
- });
86
-
87
- jest.mock("../../../Server/Utils/Response", () => {
88
- return {
89
- sendEntityArrayResponse: jest.fn().mockImplementation((...args: []) => {
90
- return args;
91
- }),
92
- sendJsonObjectResponse: jest.fn().mockImplementation((...args: []) => {
93
- return args;
94
- }),
95
- sendEmptySuccessResponse: jest.fn(),
96
- sendEntityResponse: jest.fn().mockImplementation((...args: []) => {
97
- return args;
98
- }),
99
- sendErrorResponse: jest.fn().mockImplementation((...args: []) => {
100
- return args;
101
- }),
102
- };
103
- });
104
-
105
- /*
106
- * Signed with the server's own secret in production; here it only has to be
107
- * deterministic and countable, so the expiry gating can be asserted on whether
108
- * it was called at all.
109
- */
110
- jest.mock("../../../Server/Utils/JsonWebToken", () => {
111
- return {
112
- __esModule: true,
113
- default: {
114
- signJsonPayload: jest.fn().mockReturnValue("signed.jwt.token"),
115
- },
116
- };
117
- });
118
-
119
- jest.mock("../../../Server/Services/EnterpriseLicenseService");
120
- jest.mock("../../../Server/Services/EnterpriseLicenseInstanceService");
121
-
122
- const REPORT_ROUTE: string = "/enterprise-license/report-user-count";
123
- const VALIDATE_ROUTE: string = "/enterprise-license/validate";
124
- const ACTIVE_USAGE_ROUTE: string =
125
- "/enterprise-license/:enterpriseLicenseId/active-usage";
126
-
127
- const LICENSE_ID: ObjectID = ObjectID.generate();
128
- const LICENSE_KEY: string = "acme-license-key";
129
-
130
- // A hash the sanitizer accepts: 64 hex characters.
131
- type HashFunction = (seed: string) => string;
132
-
133
- const hashOf: HashFunction = (seed: string): string => {
134
- return seed
135
- .repeat(64)
136
- .replace(/[^a-f0-9]/g, "a")
137
- .substring(0, 64);
138
- };
139
-
140
- /*
141
- * Overrides are loosely typed on purpose: several tests need to express
142
- * "this column is not set", which exactOptionalPropertyTypes forbids on
143
- * Partial<EnterpriseLicense>.
144
- */
145
- type MakeLicenseFunction = (
146
- overrides?: Record<string, unknown>,
147
- ) => EnterpriseLicense;
148
-
149
- const makeLicense: MakeLicenseFunction = (
150
- overrides?: Record<string, unknown>,
151
- ): EnterpriseLicense => {
152
- return {
153
- id: LICENSE_ID,
154
- companyName: "Acme Inc",
155
- licenseKey: LICENSE_KEY,
156
- expiresAt: OneUptimeDate.addRemoveDays(OneUptimeDate.getCurrentDate(), 90),
157
- userLimit: 150,
158
- currentUserCount: 42,
159
- userCountUpdatedAt: OneUptimeDate.getCurrentDate(),
160
- isEvaluationLicense: false,
161
- ...overrides,
162
- } as unknown as EnterpriseLicense;
163
- };
164
-
165
- type MakeInstanceFunction = (
166
- overrides?: Record<string, unknown>,
167
- ) => EnterpriseLicenseInstance;
168
-
169
- interface LicenseUsageReportResultForTest {
170
- reportedAt: Date;
171
- instances: Array<EnterpriseLicenseInstance>;
172
- currentUserCount: number;
173
- }
174
-
175
- const makeInstance: MakeInstanceFunction = (
176
- overrides?: Record<string, unknown>,
177
- ): EnterpriseLicenseInstance => {
178
- return {
179
- id: ObjectID.generate(),
180
- instanceId: "instance-1",
181
- host: "oneuptime.acme.internal",
182
- userCount: 2,
183
- userEmailHashes: [hashOf("1"), hashOf("2")],
184
- lastReportedAt: OneUptimeDate.getCurrentDate(),
185
- oneuptimeVersion: "12.0.19",
186
- ...overrides,
187
- } as unknown as EnterpriseLicenseInstance;
188
- };
189
-
190
- type GetResponseBodyFunction = () => JSONObject;
191
-
192
- /*
193
- * The third argument of the single sendJsonObjectResponse call: the literal
194
- * body that goes on the wire.
195
- */
196
- const getResponseBody: GetResponseBodyFunction = (): JSONObject => {
197
- const calls: Array<Array<unknown>> = (
198
- Response.sendJsonObjectResponse as unknown as jest.Mock
199
- ).mock.calls as Array<Array<unknown>>;
200
-
201
- expect(calls).toHaveLength(1);
202
-
203
- return calls[0]![2] as JSONObject;
204
- };
205
-
206
- describe("EnterpriseLicenseAPI POST /enterprise-license/report-user-count", () => {
207
- let mockRequest: OneUptimeRequest;
208
- let mockResponse: OneUptimeResponse;
209
- let nextFunction: NextFunction;
210
-
211
- type CallRouteFunction = (route?: string) => Promise<void>;
212
-
213
- const callRoute: CallRouteFunction = async (
214
- route: string = REPORT_ROUTE,
215
- ): Promise<void> => {
216
- await mockRouter
217
- .match("post", route)
218
- .handlerFunction(mockRequest, mockResponse, nextFunction);
219
- };
220
-
221
- beforeEach(() => {
222
- jest.clearAllMocks();
223
-
224
- new EnterpriseLicenseAPI();
225
-
226
- EnterpriseLicenseService.findOneBy = jest
227
- .fn()
228
- .mockResolvedValue(makeLicense());
229
- EnterpriseLicenseService.updateOneById = jest
230
- .fn()
231
- .mockResolvedValue(undefined);
232
- EnterpriseLicenseService.runWithUsageAggregationLock = jest
233
- .fn()
234
- .mockImplementation(
235
- async (data: { fn: () => Promise<unknown> }): Promise<unknown> => {
236
- return await data.fn();
237
- },
238
- );
239
-
240
- EnterpriseLicenseInstanceService.findBy = jest.fn().mockResolvedValue([]);
241
- EnterpriseLicenseInstanceService.findOneBy = jest
242
- .fn()
243
- .mockResolvedValue(null);
244
- EnterpriseLicenseInstanceService.updateOneById = jest
245
- .fn()
246
- .mockResolvedValue(undefined);
247
- EnterpriseLicenseInstanceService.create = jest
248
- .fn()
249
- .mockResolvedValue(undefined);
250
- EnterpriseLicenseInstanceService.countBy = jest
251
- .fn()
252
- .mockResolvedValue(new PositiveNumber(0));
253
-
254
- mockRequest = {
255
- body: {
256
- licenseKey: LICENSE_KEY,
257
- userCount: 2,
258
- instanceId: "instance-1",
259
- host: "oneuptime.acme.internal",
260
- version: "12.0.19",
261
- userEmailHashes: [hashOf("1"), hashOf("2")],
262
- masterAdminEmails: ["admin@acme.com"],
263
- },
264
- } as unknown as OneUptimeRequest;
265
-
266
- mockResponse = {
267
- send: jest.fn(),
268
- json: jest.fn(),
269
- status: jest.fn().mockReturnThis(),
270
- } as unknown as OneUptimeResponse;
271
-
272
- nextFunction = jest.fn();
273
- });
274
-
275
- afterEach(() => {
276
- jest.restoreAllMocks();
277
- });
278
-
279
- describe("the license terms it reports back", () => {
280
- it("returns the seat limit, which is the field the installation was missing", async () => {
281
- await callRoute();
282
-
283
- expect(getResponseBody()["userLimit"]).toBe(150);
284
- });
285
-
286
- it("returns null for a license with no seat limit, so the limit can be cleared", async () => {
287
- EnterpriseLicenseService.findOneBy = jest
288
- .fn()
289
- .mockResolvedValue(makeLicense({ userLimit: undefined }));
290
-
291
- await callRoute();
292
-
293
- expect(getResponseBody()).toHaveProperty("userLimit", null);
294
- });
295
-
296
- it("returns the company name and the expiry, so a renewal reaches the installation", async () => {
297
- const expiresAt: Date = new Date("2027-01-01T00:00:00.000Z");
298
-
299
- EnterpriseLicenseService.findOneBy = jest
300
- .fn()
301
- .mockResolvedValue(makeLicense({ expiresAt: expiresAt }));
302
-
303
- await callRoute();
304
-
305
- const body: JSONObject = getResponseBody();
306
-
307
- expect(body["companyName"]).toBe("Acme Inc");
308
- expect(body["expiresAt"]).toBe("2027-01-01T00:00:00.000Z");
309
- });
310
-
311
- it("selects the term columns it reports - a trimmed select is how this broke", async () => {
312
- await callRoute();
313
-
314
- expect(EnterpriseLicenseService.findOneBy).toHaveBeenCalledWith(
315
- expect.objectContaining({
316
- query: { licenseKey: LICENSE_KEY },
317
- select: expect.objectContaining({
318
- companyName: true,
319
- expiresAt: true,
320
- licenseKey: true,
321
- userLimit: true,
322
- isEvaluationLicense: true,
323
- }),
324
- }),
325
- );
326
- });
327
-
328
- it("mirrors the evaluation flag", async () => {
329
- EnterpriseLicenseService.findOneBy = jest
330
- .fn()
331
- .mockResolvedValue(makeLicense({ isEvaluationLicense: true }));
332
-
333
- await callRoute();
334
-
335
- expect(getResponseBody()["isEvaluationLicense"]).toBe(true);
336
- });
337
-
338
- it("reports the usage numbers alongside the terms", async () => {
339
- await callRoute();
340
-
341
- const body: JSONObject = getResponseBody();
342
-
343
- expect(body["currentUserCount"]).toBe(2);
344
- expect(typeof body["userCountUpdatedAt"]).toBe("string");
345
- });
346
- });
347
-
348
- describe("the token", () => {
349
- it("mints one for a live license", async () => {
350
- await callRoute();
351
-
352
- expect(getResponseBody()["token"]).toBe("signed.jwt.token");
353
- expect(JSONWebToken.signJsonPayload).toHaveBeenCalledTimes(1);
354
- });
355
-
356
- it("signs the current terms into it, not a stale copy", async () => {
357
- await callRoute();
358
-
359
- expect(JSONWebToken.signJsonPayload).toHaveBeenCalledWith(
360
- expect.objectContaining({
361
- companyName: "Acme Inc",
362
- licenseKey: LICENSE_KEY,
363
- userLimit: 150,
364
- }),
365
- expect.any(Number),
366
- );
367
- });
368
-
369
- it("withholds it once the license has expired, rather than renewing it daily", async () => {
370
- EnterpriseLicenseService.findOneBy = jest.fn().mockResolvedValue(
371
- makeLicense({
372
- expiresAt: OneUptimeDate.addRemoveDays(
373
- OneUptimeDate.getCurrentDate(),
374
- -1,
375
- ),
376
- }),
377
- );
378
-
379
- await callRoute();
380
-
381
- expect(getResponseBody()).toHaveProperty("token", null);
382
- expect(JSONWebToken.signJsonPayload).not.toHaveBeenCalled();
383
- });
384
-
385
- it("still answers an expired license in full, because it must keep reporting", async () => {
386
- const expiredAt: Date = OneUptimeDate.addRemoveDays(
387
- OneUptimeDate.getCurrentDate(),
388
- -1,
389
- );
390
-
391
- EnterpriseLicenseService.findOneBy = jest
392
- .fn()
393
- .mockResolvedValue(makeLicense({ expiresAt: expiredAt }));
394
-
395
- await callRoute();
396
-
397
- const body: JSONObject = getResponseBody();
398
-
399
- /*
400
- * The expiry notification emails are built from what expired instances
401
- * keep reporting, so this route deliberately does not reject them the
402
- * way /validate does. Telling them the truth about the expiry is what
403
- * makes the installation show itself as expired.
404
- */
405
- expect(nextFunction).not.toHaveBeenCalled();
406
- expect(body["expiresAt"]).toBe(expiredAt.toISOString());
407
- expect(body["userLimit"]).toBe(150);
408
- });
409
-
410
- it("withholds it for a license with no expiry set at all", async () => {
411
- EnterpriseLicenseService.findOneBy = jest
412
- .fn()
413
- .mockResolvedValue(makeLicense({ expiresAt: undefined }));
414
-
415
- await callRoute();
416
-
417
- const body: JSONObject = getResponseBody();
418
-
419
- expect(body).toHaveProperty("token", null);
420
- expect(body).toHaveProperty("expiresAt", null);
421
- });
422
- });
423
-
424
- describe("input validation", () => {
425
- it("rejects a missing license key", async () => {
426
- mockRequest.body["licenseKey"] = undefined;
427
-
428
- await callRoute();
429
-
430
- expect(nextFunction).toHaveBeenCalledWith(
431
- new BadDataException("License key is required"),
432
- );
433
- expect(Response.sendJsonObjectResponse).not.toHaveBeenCalled();
434
- });
435
-
436
- it.each([
437
- ["a missing count", undefined],
438
- ["a negative count", -1],
439
- ["a fractional count", 1.5],
440
- ["a non-numeric count", "many"],
441
- ])("rejects %s", async (_label: string, value: unknown) => {
442
- mockRequest.body["userCount"] = value;
443
-
444
- await callRoute();
445
-
446
- expect(nextFunction).toHaveBeenCalledWith(
447
- new BadDataException("userCount must be a non-negative integer"),
448
- );
449
- });
450
-
451
- it("accepts a count of zero", async () => {
452
- mockRequest.body["userCount"] = 0;
453
- mockRequest.body["userEmailHashes"] = [];
454
-
455
- await callRoute();
456
-
457
- expect(nextFunction).not.toHaveBeenCalled();
458
- expect(getResponseBody()["currentUserCount"]).toBe(0);
459
- });
460
-
461
- it("rejects an unknown license key", async () => {
462
- EnterpriseLicenseService.findOneBy = jest.fn().mockResolvedValue(null);
463
-
464
- await callRoute();
465
-
466
- expect(nextFunction).toHaveBeenCalledWith(
467
- new BadDataException("License key is invalid"),
468
- );
469
- });
470
-
471
- it("stays unauthenticated - instances report before anyone signs in", () => {
472
- expect(mockRouter.match("post", REPORT_ROUTE).middlewares).toHaveLength(
473
- 0,
474
- );
475
- });
476
- });
477
-
478
- describe("usage accounting", () => {
479
- it.each([false, true])(
480
- "keeps instance, license, and dashboard activity aligned when the instance already exists: %s",
481
- async (alreadyExists: boolean) => {
482
- const reportedAt: Date = new Date("2026-09-17T08:00:00.000Z");
483
- const registeredAt: Date = new Date("2026-09-01T08:00:00.000Z");
484
- const previousReportedAt: Date = new Date("2026-09-16T08:00:00.000Z");
485
- const instanceId: ObjectID = ObjectID.generate();
486
- const license: EnterpriseLicense = makeLicense({
487
- userCountUpdatedAt: previousReportedAt,
488
- });
489
- let storedInstance: EnterpriseLicenseInstance | null = alreadyExists
490
- ? makeInstance({
491
- id: instanceId,
492
- createdAt: registeredAt,
493
- lastReportedAt: previousReportedAt,
494
- })
495
- : null;
496
-
497
- jest.spyOn(OneUptimeDate, "getCurrentDate").mockReturnValue(reportedAt);
498
- EnterpriseLicenseInstanceService.findOneBy = jest
499
- .fn()
500
- .mockImplementation(
501
- async (): Promise<EnterpriseLicenseInstance | null> => {
502
- return storedInstance;
503
- },
504
- );
505
- EnterpriseLicenseInstanceService.create = jest
506
- .fn()
507
- .mockImplementation(
508
- async (args: {
509
- data: EnterpriseLicenseInstance;
510
- }): Promise<EnterpriseLicenseInstance> => {
511
- storedInstance = makeInstance({
512
- ...args.data,
513
- id: instanceId,
514
- createdAt: reportedAt,
515
- });
516
-
517
- return storedInstance;
518
- },
519
- );
520
- EnterpriseLicenseInstanceService.updateOneById = jest
521
- .fn()
522
- .mockImplementation(
523
- async (args: {
524
- data: Partial<EnterpriseLicenseInstance>;
525
- }): Promise<number> => {
526
- Object.assign(storedInstance!, args.data);
527
-
528
- return 1;
529
- },
530
- );
531
- EnterpriseLicenseInstanceService.findBy = jest
532
- .fn()
533
- .mockImplementation(
534
- async (): Promise<Array<EnterpriseLicenseInstance>> => {
535
- return storedInstance ? [storedInstance] : [];
536
- },
537
- );
538
- EnterpriseLicenseService.updateOneById = jest
539
- .fn()
540
- .mockImplementation(
541
- async (args: {
542
- data: Partial<EnterpriseLicense>;
543
- }): Promise<number> => {
544
- Object.assign(license, args.data);
545
-
546
- return 1;
547
- },
548
- );
549
- EnterpriseLicenseService.findOneById = jest
550
- .fn()
551
- .mockResolvedValue(license);
552
-
553
- await callRoute();
554
-
555
- expect(nextFunction).not.toHaveBeenCalled();
556
- expect(storedInstance).toEqual(
557
- expect.objectContaining({
558
- lastReportedAt: reportedAt,
559
- createdAt: alreadyExists ? registeredAt : reportedAt,
560
- }),
561
- );
562
- expect(license.userCountUpdatedAt).toEqual(reportedAt);
563
- expect(getResponseBody()).toEqual(
564
- expect.objectContaining({
565
- userCountUpdatedAt: reportedAt.toISOString(),
566
- instances: [
567
- expect.objectContaining({
568
- instanceId: "instance-1",
569
- lastReportedAt: reportedAt.toISOString(),
570
- isCountedTowardsUsage: true,
571
- }),
572
- ],
573
- }),
574
- );
575
- expect(EnterpriseLicenseInstanceService.findBy).toHaveBeenCalledWith(
576
- expect.objectContaining({
577
- query: { enterpriseLicenseId: LICENSE_ID },
578
- select: expect.objectContaining({
579
- _id: true,
580
- createdAt: true,
581
- lastReportedAt: true,
582
- }),
583
- }),
584
- );
585
- expect(EnterpriseLicenseInstanceService.create).toHaveBeenCalledTimes(
586
- alreadyExists ? 0 : 1,
587
- );
588
- expect(
589
- EnterpriseLicenseInstanceService.updateOneById,
590
- ).toHaveBeenCalledTimes(alreadyExists ? 1 : 0);
591
-
592
- jest.mocked(Response.sendJsonObjectResponse).mockClear();
593
- mockRequest.params = {
594
- enterpriseLicenseId: LICENSE_ID.toString(),
595
- };
596
-
597
- await mockRouter
598
- .match("get", ACTIVE_USAGE_ROUTE)
599
- .handlerFunction(mockRequest, mockResponse, nextFunction);
600
-
601
- expect(nextFunction).not.toHaveBeenCalled();
602
- expect(getResponseBody()).toEqual(
603
- expect.objectContaining({
604
- currentUserCount: 2,
605
- activeInstanceIds: [instanceId.toString()],
606
- lastUsageReportedAt: reportedAt.toISOString(),
607
- }),
608
- );
609
- },
610
- );
611
-
612
- it("records this instance's usage against the license", async () => {
613
- await callRoute();
614
-
615
- expect(EnterpriseLicenseInstanceService.create).toHaveBeenCalledWith(
616
- expect.objectContaining({
617
- data: expect.objectContaining({
618
- instanceId: "instance-1",
619
- userCount: 2,
620
- }),
621
- }),
622
- );
623
- expect(
624
- EnterpriseLicenseService.runWithUsageAggregationLock,
625
- ).toHaveBeenCalledWith(
626
- expect.objectContaining({
627
- licenseId: LICENSE_ID,
628
- fn: expect.any(Function),
629
- }),
630
- );
631
- });
632
-
633
- it("serializes concurrent reports so an older partial snapshot cannot win", async () => {
634
- const instanceRows: Array<EnterpriseLicenseInstance> = [];
635
- const events: Array<string> = [];
636
- let lockTail: Promise<void> = Promise.resolve();
637
-
638
- EnterpriseLicenseService.runWithUsageAggregationLock = jest
639
- .fn()
640
- .mockImplementation(
641
- async (data: {
642
- fn: () => Promise<LicenseUsageReportResultForTest>;
643
- }): Promise<LicenseUsageReportResultForTest> => {
644
- const predecessor: Promise<void> = lockTail;
645
- let releaseLock: () => void = (): void => {};
646
- lockTail = new Promise<void>((resolve: () => void) => {
647
- releaseLock = resolve;
648
- });
649
-
650
- await predecessor;
651
- try {
652
- return await data.fn();
653
- } finally {
654
- releaseLock();
655
- }
656
- },
657
- );
658
- EnterpriseLicenseInstanceService.findOneBy = jest
659
- .fn()
660
- .mockImplementation(
661
- async (args: {
662
- query: { instanceId: string };
663
- }): Promise<EnterpriseLicenseInstance | null> => {
664
- return (
665
- instanceRows.find(
666
- (instance: EnterpriseLicenseInstance): boolean => {
667
- return instance.instanceId === args.query.instanceId;
668
- },
669
- ) || null
670
- );
671
- },
672
- );
673
- EnterpriseLicenseInstanceService.create = jest
674
- .fn()
675
- .mockImplementation(
676
- async (args: {
677
- data: EnterpriseLicenseInstance;
678
- }): Promise<EnterpriseLicenseInstance> => {
679
- instanceRows.push(args.data);
680
- events.push(`upsert:${args.data.instanceId}`);
681
- await Promise.resolve();
682
- return args.data;
683
- },
684
- );
685
- EnterpriseLicenseInstanceService.findBy = jest
686
- .fn()
687
- .mockImplementation(
688
- async (): Promise<Array<EnterpriseLicenseInstance>> => {
689
- events.push(
690
- `read:${instanceRows
691
- .map((instance: EnterpriseLicenseInstance): string => {
692
- return instance.instanceId || "";
693
- })
694
- .join(",")}`,
695
- );
696
- await Promise.resolve();
697
- return [...instanceRows];
698
- },
699
- );
700
- EnterpriseLicenseService.updateOneById = jest
701
- .fn()
702
- .mockImplementation(
703
- async (args: {
704
- data: { currentUserCount: number };
705
- }): Promise<number> => {
706
- events.push(`write:${args.data.currentUserCount}`);
707
- await Promise.resolve();
708
- return 1;
709
- },
710
- );
711
-
712
- const makeConcurrentRequest: (
713
- instanceId: string,
714
- hash: string,
715
- ) => OneUptimeRequest = (
716
- instanceId: string,
717
- hash: string,
718
- ): OneUptimeRequest => {
719
- return {
720
- body: {
721
- licenseKey: LICENSE_KEY,
722
- userCount: 1,
723
- instanceId,
724
- userEmailHashes: [hash],
725
- },
726
- } as unknown as OneUptimeRequest;
727
- };
728
- const firstNext: NextFunction = jest.fn();
729
- const secondNext: NextFunction = jest.fn();
730
- const route: ReturnType<typeof mockRouter.match> = mockRouter.match(
731
- "post",
732
- REPORT_ROUTE,
733
- );
734
-
735
- await Promise.all([
736
- route.handlerFunction(
737
- makeConcurrentRequest("instance-a", hashOf("a")),
738
- mockResponse,
739
- firstNext,
740
- ),
741
- route.handlerFunction(
742
- makeConcurrentRequest("instance-b", hashOf("b")),
743
- mockResponse,
744
- secondNext,
745
- ),
746
- ]);
747
-
748
- expect(firstNext).not.toHaveBeenCalled();
749
- expect(secondNext).not.toHaveBeenCalled();
750
- expect(events).toEqual([
751
- "upsert:instance-a",
752
- "read:instance-a",
753
- "write:1",
754
- "upsert:instance-b",
755
- "read:instance-a,instance-b",
756
- "write:2",
757
- ]);
758
- expect(
759
- (
760
- EnterpriseLicenseService.updateOneById as unknown as jest.Mock
761
- ).mock.calls.map((call: Array<unknown>): number => {
762
- return (call[0] as { data: { currentUserCount: number } }).data
763
- .currentUserCount;
764
- }),
765
- ).toEqual([1, 2]);
766
- });
767
-
768
- it("counts a user on two instances once", async () => {
769
- const shared: string = hashOf("1");
770
-
771
- EnterpriseLicenseInstanceService.findBy = jest.fn().mockResolvedValue([
772
- makeInstance({
773
- instanceId: "instance-1",
774
- userCount: 2,
775
- userEmailHashes: [shared, hashOf("2")],
776
- }),
777
- makeInstance({
778
- instanceId: "instance-2",
779
- userCount: 2,
780
- userEmailHashes: [shared, hashOf("3")],
781
- }),
782
- ]);
783
-
784
- await callRoute();
785
-
786
- // Union of {1,2} and {1,3} is three seats, not four.
787
- expect(getResponseBody()["currentUserCount"]).toBe(3);
788
- });
789
-
790
- it("writes the deduplicated count back onto the license row", async () => {
791
- EnterpriseLicenseInstanceService.findBy = jest
792
- .fn()
793
- .mockResolvedValue([makeInstance()]);
794
-
795
- await callRoute();
796
-
797
- expect(EnterpriseLicenseService.updateOneById).toHaveBeenCalledWith(
798
- expect.objectContaining({
799
- id: LICENSE_ID,
800
- data: expect.objectContaining({ currentUserCount: 2 }),
801
- }),
802
- );
803
- });
804
-
805
- it("never writes the seat limit back from a report", async () => {
806
- /*
807
- * The limit flows one way only: oneuptime.com to the installation. A
808
- * report that could raise it would make the license meaningless.
809
- */
810
- await callRoute();
811
-
812
- const updateCall: JSONObject = (
813
- EnterpriseLicenseService.updateOneById as unknown as jest.Mock
814
- ).mock.calls[0]![0] as JSONObject;
815
-
816
- expect(Object.keys(updateCall["data"] as JSONObject)).toEqual([
817
- "currentUserCount",
818
- "userCountUpdatedAt",
819
- "userCountSource",
820
- ]);
821
- expect((updateCall["data"] as JSONObject)["userCountSource"]).toBe(
822
- EnterpriseLicenseUserCountSource.Instance,
823
- );
824
- });
825
-
826
- it("marks a week-silent instance inactive, excludes its users, and still lists it", async () => {
827
- const reportedAt: Date = new Date("2026-09-02T12:00:00.000Z");
828
-
829
- jest.spyOn(OneUptimeDate, "getCurrentDate").mockReturnValue(reportedAt);
830
-
831
- EnterpriseLicenseInstanceService.findBy = jest.fn().mockResolvedValue([
832
- makeInstance({
833
- instanceId: "live",
834
- userCount: 1,
835
- userEmailHashes: [hashOf("1")],
836
- lastReportedAt: reportedAt,
837
- }),
838
- makeInstance({
839
- instanceId: "inactive",
840
- userCount: 2,
841
- userEmailHashes: [hashOf("2"), hashOf("3")],
842
- lastReportedAt: new Date(
843
- reportedAt.getTime() -
844
- EnterpriseLicenseUsageUtil.InstanceUsageFreshnessInDays *
845
- 24 *
846
- 60 *
847
- 60 *
848
- 1000,
849
- ),
850
- }),
851
- ]);
852
-
853
- await callRoute();
854
-
855
- const body: JSONObject = getResponseBody();
856
-
857
- expect(body["currentUserCount"]).toBe(1);
858
- expect(body["instances"]).toHaveLength(2);
859
- expect(body["instances"]).toEqual(
860
- expect.arrayContaining([
861
- expect.objectContaining({
862
- instanceId: "live",
863
- isCountedTowardsUsage: true,
864
- }),
865
- expect.objectContaining({
866
- instanceId: "inactive",
867
- userCount: 2,
868
- isCountedTowardsUsage: false,
869
- }),
870
- ]),
871
- );
872
- expect(EnterpriseLicenseService.updateOneById).toHaveBeenCalledWith(
873
- expect.objectContaining({
874
- id: LICENSE_ID,
875
- data: expect.objectContaining({ currentUserCount: 1 }),
876
- }),
877
- );
878
- });
879
-
880
- it("counts users an instance could not send hashes for, so a huge install is not undercounted", async () => {
881
- /*
882
- * Hash lists are capped, so an instance may report more users than
883
- * hashes. The overflow cannot be deduplicated against anyone else and is
884
- * counted as-is.
885
- */
886
- EnterpriseLicenseInstanceService.findBy = jest.fn().mockResolvedValue([
887
- makeInstance({
888
- instanceId: "big",
889
- userCount: 10,
890
- userEmailHashes: [hashOf("1"), hashOf("2")],
891
- }),
892
- ]);
893
-
894
- await callRoute();
895
-
896
- expect(getResponseBody()["currentUserCount"]).toBe(10);
897
- });
898
-
899
- it("retains a legacy heartbeat without letting it stomp the active modern count", async () => {
900
- mockRequest.body["instanceId"] = undefined;
901
- mockRequest.body["userCount"] = 99;
902
-
903
- EnterpriseLicenseInstanceService.findBy = jest
904
- .fn()
905
- .mockResolvedValue([makeInstance()]);
906
-
907
- await callRoute();
908
-
909
- expect(getResponseBody()["currentUserCount"]).toBe(2);
910
- expect(EnterpriseLicenseService.updateOneById).toHaveBeenCalledWith(
911
- expect.objectContaining({
912
- id: LICENSE_ID,
913
- data: {
914
- legacyUserCount: 99,
915
- legacyUserCountUpdatedAt: expect.any(Date),
916
- },
917
- }),
918
- );
919
- });
920
-
921
- it("accepts a live legacy report after every modern instance becomes inactive", async () => {
922
- const reportedAt: Date = new Date("2026-09-02T12:00:00.000Z");
923
- jest.spyOn(OneUptimeDate, "getCurrentDate").mockReturnValue(reportedAt);
924
- mockRequest.body["instanceId"] = undefined;
925
- mockRequest.body["userCount"] = 19;
926
-
927
- EnterpriseLicenseInstanceService.findBy = jest.fn().mockResolvedValue([
928
- makeInstance({
929
- lastReportedAt: OneUptimeDate.addRemoveDays(reportedAt, -7),
930
- userCount: 50,
931
- }),
932
- ]);
933
-
934
- await callRoute();
935
-
936
- expect(getResponseBody()["currentUserCount"]).toBe(19);
937
- expect(EnterpriseLicenseService.updateOneById).toHaveBeenCalledWith(
938
- expect.objectContaining({
939
- data: expect.objectContaining({
940
- currentUserCount: 19,
941
- legacyUserCount: 19,
942
- legacyUserCountUpdatedAt: reportedAt,
943
- userCountSource: EnterpriseLicenseUserCountSource.Legacy,
944
- }),
945
- }),
946
- );
947
- });
948
-
949
- it("accepts a legacy report while the only tracked instance has registered but not reported usage", async () => {
950
- mockRequest.body["instanceId"] = undefined;
951
- mockRequest.body["userCount"] = 19;
952
-
953
- EnterpriseLicenseInstanceService.findBy = jest.fn().mockResolvedValue([
954
- makeInstance({
955
- createdAt: OneUptimeDate.addRemoveDays(
956
- OneUptimeDate.getCurrentDate(),
957
- -1,
958
- ),
959
- lastReportedAt: undefined,
960
- userCount: undefined,
961
- userEmailHashes: undefined,
962
- }),
963
- ]);
964
-
965
- await callRoute();
966
-
967
- expect(getResponseBody()["currentUserCount"]).toBe(19);
968
- expect(EnterpriseLicenseService.updateOneById).toHaveBeenCalledWith(
969
- expect.objectContaining({
970
- id: LICENSE_ID,
971
- data: expect.objectContaining({ currentUserCount: 19 }),
972
- }),
973
- );
974
- });
975
- });
976
- });
977
-
978
- describe("EnterpriseLicenseAPI GET active usage snapshot", () => {
979
- let mockRequest: OneUptimeRequest;
980
- let mockResponse: OneUptimeResponse;
981
- let nextFunction: NextFunction;
982
- const calculatedAt: Date = new Date("2026-09-02T12:00:00.000Z");
983
-
984
- beforeEach(() => {
985
- jest.clearAllMocks();
986
-
987
- new EnterpriseLicenseAPI();
988
- jest.spyOn(OneUptimeDate, "getCurrentDate").mockReturnValue(calculatedAt);
989
-
990
- EnterpriseLicenseService.findOneById = jest
991
- .fn()
992
- .mockResolvedValue(makeLicense({ userCountUpdatedAt: calculatedAt }));
993
- EnterpriseLicenseService.runWithUsageAggregationLock = jest
994
- .fn()
995
- .mockImplementation(
996
- async (data: { fn: () => Promise<unknown> }): Promise<unknown> => {
997
- return await data.fn();
998
- },
999
- );
1000
- EnterpriseLicenseInstanceService.findBy = jest.fn().mockResolvedValue([]);
1001
-
1002
- mockRequest = {
1003
- params: {
1004
- enterpriseLicenseId: LICENSE_ID.toString(),
1005
- },
1006
- } as unknown as OneUptimeRequest;
1007
- mockResponse = {
1008
- send: jest.fn(),
1009
- json: jest.fn(),
1010
- status: jest.fn().mockReturnThis(),
1011
- } as unknown as OneUptimeResponse;
1012
- nextFunction = jest.fn();
1013
- });
1014
-
1015
- afterEach(() => {
1016
- jest.restoreAllMocks();
1017
- });
1018
-
1019
- const callRoute: () => Promise<void> = async (): Promise<void> => {
1020
- await mockRouter
1021
- .match("get", ACTIVE_USAGE_ROUTE)
1022
- .handlerFunction(mockRequest, mockResponse, nextFunction);
1023
- };
1024
-
1025
- it("is restricted to authenticated master admins", () => {
1026
- expect(mockRouter.match("get", ACTIVE_USAGE_ROUTE).middleware).toBe(
1027
- MasterAdminAuthorization.isAuthorizedMasterAdminMiddleware,
1028
- );
1029
- });
1030
-
1031
- it("returns one cutoff-aligned count and active-instance list without hashes", async () => {
1032
- const active: EnterpriseLicenseInstance = makeInstance({
1033
- lastReportedAt: OneUptimeDate.addRemoveDays(calculatedAt, -1),
1034
- userEmailHashes: [hashOf("1"), hashOf("2")],
1035
- masterAdminEmails: [" Admin@Acme.com "],
1036
- });
1037
- const newlyRegistered: EnterpriseLicenseInstance = makeInstance({
1038
- lastReportedAt: undefined,
1039
- createdAt: OneUptimeDate.addRemoveDays(calculatedAt, -1),
1040
- userCount: undefined,
1041
- userEmailHashes: undefined,
1042
- masterAdminEmails: ["admin@acme.com", "owner@acme.com"],
1043
- });
1044
- const inactive: EnterpriseLicenseInstance = makeInstance({
1045
- lastReportedAt: OneUptimeDate.addRemoveDays(calculatedAt, -7),
1046
- userCount: 2,
1047
- userEmailHashes: [hashOf("2"), hashOf("3")],
1048
- });
1049
-
1050
- EnterpriseLicenseInstanceService.findBy = jest
1051
- .fn()
1052
- .mockResolvedValue([active, newlyRegistered, inactive]);
1053
-
1054
- await callRoute();
1055
-
1056
- expect(nextFunction).not.toHaveBeenCalled();
1057
- expect(getResponseBody()).toEqual({
1058
- currentUserCount: 2,
1059
- activeInstanceIds: [
1060
- active.id!.toString(),
1061
- newlyRegistered.id!.toString(),
1062
- ],
1063
- masterAdminEmails: ["admin@acme.com", "owner@acme.com"],
1064
- calculatedAt: calculatedAt.toISOString(),
1065
- lastUsageReportedAt: calculatedAt.toISOString(),
1066
- nextInstanceStatusChangeAt: OneUptimeDate.addRemoveDays(
1067
- calculatedAt,
1068
- 6,
1069
- ).toISOString(),
1070
- });
1071
- expect(JSON.stringify(getResponseBody())).not.toContain(hashOf("1"));
1072
- });
1073
-
1074
- it("returns zero when every tracked instance is inactive", async () => {
1075
- EnterpriseLicenseInstanceService.findBy = jest.fn().mockResolvedValue([
1076
- makeInstance({
1077
- lastReportedAt: OneUptimeDate.addRemoveDays(calculatedAt, -8),
1078
- userCount: 50,
1079
- }),
1080
- ]);
1081
-
1082
- await callRoute();
1083
-
1084
- expect(getResponseBody()).toEqual(
1085
- expect.objectContaining({
1086
- currentUserCount: 0,
1087
- activeInstanceIds: [],
1088
- nextInstanceStatusChangeAt: null,
1089
- }),
1090
- );
1091
- });
1092
-
1093
- it("uses a fresh legacy count after every modern instance becomes inactive", async () => {
1094
- EnterpriseLicenseService.findOneById = jest.fn().mockResolvedValue(
1095
- makeLicense({
1096
- currentUserCount: 17,
1097
- userCountUpdatedAt: OneUptimeDate.addRemoveDays(calculatedAt, -7),
1098
- legacyUserCount: 17,
1099
- legacyUserCountUpdatedAt: OneUptimeDate.addRemoveDays(calculatedAt, -1),
1100
- }),
1101
- );
1102
- EnterpriseLicenseInstanceService.findBy = jest.fn().mockResolvedValue([
1103
- makeInstance({
1104
- lastReportedAt: OneUptimeDate.addRemoveDays(calculatedAt, -7),
1105
- userCount: 50,
1106
- }),
1107
- ]);
1108
-
1109
- await callRoute();
1110
-
1111
- expect(getResponseBody()).toEqual(
1112
- expect.objectContaining({
1113
- currentUserCount: 17,
1114
- activeInstanceIds: [],
1115
- }),
1116
- );
1117
- });
1118
-
1119
- it("does not preserve an inactive modern aggregate because another instance registered later", async () => {
1120
- const inactiveAt: Date = OneUptimeDate.addRemoveDays(calculatedAt, -7);
1121
- EnterpriseLicenseService.findOneById = jest.fn().mockResolvedValue(
1122
- makeLicense({
1123
- currentUserCount: 50,
1124
- userCountUpdatedAt: inactiveAt,
1125
- }),
1126
- );
1127
- const newlyRegistered: EnterpriseLicenseInstance = makeInstance({
1128
- createdAt: OneUptimeDate.addRemoveDays(calculatedAt, -1),
1129
- lastReportedAt: undefined,
1130
- userCount: undefined,
1131
- userEmailHashes: undefined,
1132
- });
1133
- EnterpriseLicenseInstanceService.findBy = jest.fn().mockResolvedValue([
1134
- makeInstance({
1135
- lastReportedAt: inactiveAt,
1136
- userCount: 50,
1137
- }),
1138
- newlyRegistered,
1139
- ]);
1140
-
1141
- await callRoute();
1142
-
1143
- expect(getResponseBody()).toEqual(
1144
- expect.objectContaining({
1145
- currentUserCount: 0,
1146
- activeInstanceIds: [newlyRegistered.id!.toString()],
1147
- }),
1148
- );
1149
- });
1150
-
1151
- it("schedules an exact refresh when a legacy-only heartbeat expires", async () => {
1152
- const legacyReportedAt: Date = OneUptimeDate.addRemoveDays(
1153
- calculatedAt,
1154
- -1,
1155
- );
1156
- EnterpriseLicenseService.findOneById = jest.fn().mockResolvedValue(
1157
- makeLicense({
1158
- currentUserCount: 17,
1159
- userCountUpdatedAt: legacyReportedAt,
1160
- legacyUserCount: 17,
1161
- legacyUserCountUpdatedAt: legacyReportedAt,
1162
- }),
1163
- );
1164
-
1165
- await callRoute();
1166
-
1167
- expect(getResponseBody()).toEqual(
1168
- expect.objectContaining({
1169
- currentUserCount: 17,
1170
- activeInstanceIds: [],
1171
- nextInstanceStatusChangeAt: OneUptimeDate.addRemoveDays(
1172
- calculatedAt,
1173
- 6,
1174
- ).toISOString(),
1175
- }),
1176
- );
1177
- });
1178
-
1179
- it("preserves a legacy stored count when no instance rows exist", async () => {
1180
- EnterpriseLicenseService.findOneById = jest
1181
- .fn()
1182
- .mockResolvedValue(makeLicense({ currentUserCount: 17 }));
1183
-
1184
- await callRoute();
1185
-
1186
- expect(getResponseBody()).toEqual(
1187
- expect.objectContaining({
1188
- currentUserCount: 17,
1189
- activeInstanceIds: [],
1190
- nextInstanceStatusChangeAt: OneUptimeDate.addRemoveDays(
1191
- calculatedAt,
1192
- 7,
1193
- ).toISOString(),
1194
- }),
1195
- );
1196
- });
1197
-
1198
- it("preserves a legacy stored count when an instance has registered but not reported usage", async () => {
1199
- EnterpriseLicenseService.findOneById = jest.fn().mockResolvedValue(
1200
- makeLicense({
1201
- currentUserCount: 17,
1202
- userCountUpdatedAt: OneUptimeDate.addRemoveDays(calculatedAt, -8),
1203
- }),
1204
- );
1205
- const registeredInstance: EnterpriseLicenseInstance = makeInstance({
1206
- createdAt: OneUptimeDate.addRemoveDays(calculatedAt, -1),
1207
- lastReportedAt: undefined,
1208
- userCount: undefined,
1209
- userEmailHashes: undefined,
1210
- });
1211
- EnterpriseLicenseInstanceService.findBy = jest
1212
- .fn()
1213
- .mockResolvedValue([registeredInstance]);
1214
-
1215
- await callRoute();
1216
-
1217
- expect(getResponseBody()).toEqual(
1218
- expect.objectContaining({
1219
- currentUserCount: 17,
1220
- activeInstanceIds: [registeredInstance.id!.toString()],
1221
- }),
1222
- );
1223
- });
1224
-
1225
- it("drops a legacy count when its report and every registration reach one week old", async () => {
1226
- const inactiveAt: Date = OneUptimeDate.addRemoveDays(calculatedAt, -7);
1227
- EnterpriseLicenseService.findOneById = jest.fn().mockResolvedValue(
1228
- makeLicense({
1229
- currentUserCount: 17,
1230
- userCountUpdatedAt: inactiveAt,
1231
- }),
1232
- );
1233
- EnterpriseLicenseInstanceService.findBy = jest.fn().mockResolvedValue([
1234
- makeInstance({
1235
- createdAt: inactiveAt,
1236
- lastReportedAt: undefined,
1237
- userCount: undefined,
1238
- userEmailHashes: undefined,
1239
- }),
1240
- ]);
1241
-
1242
- await callRoute();
1243
-
1244
- expect(getResponseBody()).toEqual(
1245
- expect.objectContaining({
1246
- currentUserCount: 0,
1247
- activeInstanceIds: [],
1248
- nextInstanceStatusChangeAt: null,
1249
- }),
1250
- );
1251
- });
1252
-
1253
- it("does not extend an expired dedicated legacy heartbeat with a newer modern timestamp", async () => {
1254
- EnterpriseLicenseService.findOneById = jest.fn().mockResolvedValue(
1255
- makeLicense({
1256
- currentUserCount: 17,
1257
- userCountUpdatedAt: OneUptimeDate.addRemoveDays(calculatedAt, -1),
1258
- userCountSource: EnterpriseLicenseUserCountSource.Legacy,
1259
- legacyUserCount: 17,
1260
- legacyUserCountUpdatedAt: OneUptimeDate.addRemoveDays(calculatedAt, -7),
1261
- }),
1262
- );
1263
-
1264
- await callRoute();
1265
-
1266
- expect(getResponseBody()).toEqual(
1267
- expect.objectContaining({
1268
- currentUserCount: 0,
1269
- activeInstanceIds: [],
1270
- nextInstanceStatusChangeAt: null,
1271
- }),
1272
- );
1273
- });
1274
-
1275
- it("selects the legacy report timestamp needed for the bounded fallback", async () => {
1276
- await callRoute();
1277
-
1278
- expect(EnterpriseLicenseService.findOneById).toHaveBeenCalledWith(
1279
- expect.objectContaining({
1280
- select: expect.objectContaining({
1281
- currentUserCount: true,
1282
- userCountUpdatedAt: true,
1283
- userCountSource: true,
1284
- legacyUserCount: true,
1285
- legacyUserCountUpdatedAt: true,
1286
- }),
1287
- }),
1288
- );
1289
- });
1290
-
1291
- it("serializes the license and instance reads with usage reports", async () => {
1292
- await callRoute();
1293
-
1294
- expect(
1295
- EnterpriseLicenseService.runWithUsageAggregationLock,
1296
- ).toHaveBeenCalledWith({
1297
- licenseId: LICENSE_ID,
1298
- fn: expect.any(Function),
1299
- });
1300
- expect(
1301
- (EnterpriseLicenseService.findOneById as unknown as jest.Mock).mock
1302
- .invocationCallOrder[0],
1303
- ).toBeGreaterThan(
1304
- (
1305
- EnterpriseLicenseService.runWithUsageAggregationLock as unknown as jest.Mock
1306
- ).mock.invocationCallOrder[0]!,
1307
- );
1308
- });
1309
-
1310
- it("cannot expose a reported instance with the previous license timestamp", async () => {
1311
- const previousReportAt: Date = OneUptimeDate.addRemoveDays(
1312
- calculatedAt,
1313
- -8,
1314
- );
1315
- const licenseState: EnterpriseLicense = makeLicense({
1316
- currentUserCount: 50,
1317
- userCountUpdatedAt: previousReportAt,
1318
- });
1319
- const instanceState: EnterpriseLicenseInstance = makeInstance({
1320
- lastReportedAt: previousReportAt,
1321
- userCount: 50,
1322
- });
1323
- let lockTail: Promise<void> = Promise.resolve();
1324
- let releaseInstanceUpdate: () => void = (): void => {};
1325
- let markInstanceUpdateStarted: () => void = (): void => {};
1326
- const instanceUpdateStarted: Promise<void> = new Promise<void>(
1327
- (resolve: () => void) => {
1328
- markInstanceUpdateStarted = resolve;
1329
- },
1330
- );
1331
- const instanceUpdateGate: Promise<void> = new Promise<void>(
1332
- (resolve: () => void) => {
1333
- releaseInstanceUpdate = resolve;
1334
- },
1335
- );
1336
-
1337
- EnterpriseLicenseService.runWithUsageAggregationLock = jest
1338
- .fn()
1339
- .mockImplementation(
1340
- async (data: { fn: () => Promise<unknown> }): Promise<unknown> => {
1341
- const predecessor: Promise<void> = lockTail;
1342
- let releaseLock: () => void = (): void => {};
1343
- lockTail = new Promise<void>((resolve: () => void) => {
1344
- releaseLock = resolve;
1345
- });
1346
-
1347
- await predecessor;
1348
- try {
1349
- return await data.fn();
1350
- } finally {
1351
- releaseLock();
1352
- }
1353
- },
1354
- );
1355
- EnterpriseLicenseService.findOneBy = jest
1356
- .fn()
1357
- .mockResolvedValue(licenseState);
1358
- EnterpriseLicenseService.findOneById = jest
1359
- .fn()
1360
- .mockImplementation(async (): Promise<EnterpriseLicense> => {
1361
- return licenseState;
1362
- });
1363
- EnterpriseLicenseService.updateOneById = jest
1364
- .fn()
1365
- .mockImplementation(
1366
- async (args: { data: Partial<EnterpriseLicense> }): Promise<number> => {
1367
- Object.assign(licenseState, args.data);
1368
- return 1;
1369
- },
1370
- );
1371
- EnterpriseLicenseInstanceService.findOneBy = jest
1372
- .fn()
1373
- .mockResolvedValue(instanceState);
1374
- EnterpriseLicenseInstanceService.updateOneById = jest
1375
- .fn()
1376
- .mockImplementation(
1377
- async (args: {
1378
- data: Partial<EnterpriseLicenseInstance>;
1379
- }): Promise<number> => {
1380
- Object.assign(instanceState, args.data);
1381
- markInstanceUpdateStarted();
1382
- await instanceUpdateGate;
1383
- return 1;
1384
- },
1385
- );
1386
- EnterpriseLicenseInstanceService.findBy = jest
1387
- .fn()
1388
- .mockImplementation(
1389
- async (): Promise<Array<EnterpriseLicenseInstance>> => {
1390
- return [instanceState];
1391
- },
1392
- );
1393
-
1394
- const reportRequest: OneUptimeRequest = {
1395
- body: {
1396
- licenseKey: LICENSE_KEY,
1397
- userCount: 2,
1398
- instanceId: "instance-1",
1399
- userEmailHashes: [hashOf("1"), hashOf("2")],
1400
- },
1401
- } as unknown as OneUptimeRequest;
1402
- const reportPromise: Promise<void> = Promise.resolve(
1403
- mockRouter
1404
- .match("post", REPORT_ROUTE)
1405
- .handlerFunction(reportRequest, mockResponse, nextFunction),
1406
- );
1407
-
1408
- await instanceUpdateStarted;
1409
-
1410
- const snapshotPromise: Promise<void> = callRoute();
1411
-
1412
- releaseInstanceUpdate();
1413
- await Promise.all([reportPromise, snapshotPromise]);
1414
-
1415
- expect(nextFunction).not.toHaveBeenCalled();
1416
- const responseBodies: Array<JSONObject> = (
1417
- Response.sendJsonObjectResponse as unknown as jest.Mock
1418
- ).mock.calls.map((call: Array<unknown>): JSONObject => {
1419
- return call[2] as JSONObject;
1420
- });
1421
- const snapshotBody: JSONObject = responseBodies.find(
1422
- (body: JSONObject): boolean => {
1423
- return Array.isArray(body["activeInstanceIds"]);
1424
- },
1425
- )!;
1426
-
1427
- expect(snapshotBody["currentUserCount"]).toBe(2);
1428
- expect(snapshotBody["activeInstanceIds"]).toEqual([
1429
- instanceState.id!.toString(),
1430
- ]);
1431
- expect(snapshotBody["lastUsageReportedAt"]).toBe(
1432
- calculatedAt.toISOString(),
1433
- );
1434
- });
1435
-
1436
- it("does not disclose whether an unknown license has instance usage", async () => {
1437
- EnterpriseLicenseService.findOneById = jest.fn().mockResolvedValue(null);
1438
-
1439
- await callRoute();
1440
-
1441
- expect(nextFunction).toHaveBeenCalledWith(
1442
- new BadDataException("Enterprise license not found"),
1443
- );
1444
- expect(EnterpriseLicenseInstanceService.findBy).not.toHaveBeenCalled();
1445
- });
1446
- });
1447
-
1448
- describe("EnterpriseLicenseAPI POST /enterprise-license/validate", () => {
1449
- let mockRequest: OneUptimeRequest;
1450
- let mockResponse: OneUptimeResponse;
1451
- let nextFunction: NextFunction;
1452
-
1453
- beforeEach(() => {
1454
- jest.clearAllMocks();
1455
-
1456
- new EnterpriseLicenseAPI();
1457
-
1458
- EnterpriseLicenseService.findOneBy = jest
1459
- .fn()
1460
- .mockResolvedValue(makeLicense());
1461
- EnterpriseLicenseService.findOneById = jest
1462
- .fn()
1463
- .mockResolvedValue(makeLicense());
1464
- EnterpriseLicenseService.runWithUsageAggregationLock = jest
1465
- .fn()
1466
- .mockImplementation(
1467
- async (data: { fn: () => Promise<unknown> }): Promise<unknown> => {
1468
- return await data.fn();
1469
- },
1470
- );
1471
- EnterpriseLicenseInstanceService.findBy = jest.fn().mockResolvedValue([]);
1472
- EnterpriseLicenseInstanceService.findOneBy = jest
1473
- .fn()
1474
- .mockResolvedValue(null);
1475
- EnterpriseLicenseInstanceService.create = jest
1476
- .fn()
1477
- .mockResolvedValue(undefined);
1478
- EnterpriseLicenseInstanceService.updateOneById = jest
1479
- .fn()
1480
- .mockResolvedValue(undefined);
1481
- EnterpriseLicenseInstanceService.countBy = jest
1482
- .fn()
1483
- .mockResolvedValue(new PositiveNumber(0));
1484
-
1485
- mockRequest = {
1486
- body: {
1487
- licenseKey: LICENSE_KEY,
1488
- instanceId: "instance-1",
1489
- host: "oneuptime.acme.internal",
1490
- version: "12.0.19",
1491
- },
1492
- } as unknown as OneUptimeRequest;
1493
-
1494
- mockResponse = {
1495
- send: jest.fn(),
1496
- json: jest.fn(),
1497
- status: jest.fn().mockReturnThis(),
1498
- } as unknown as OneUptimeResponse;
1499
-
1500
- nextFunction = jest.fn();
1501
- });
1502
-
1503
- afterEach(() => {
1504
- jest.restoreAllMocks();
1505
- });
1506
-
1507
- it("still answers with the same terms after the shared refactor", async () => {
1508
- const expiresAt: Date = OneUptimeDate.addRemoveDays(
1509
- OneUptimeDate.getCurrentDate(),
1510
- 90,
1511
- );
1512
-
1513
- EnterpriseLicenseService.findOneBy = jest
1514
- .fn()
1515
- .mockResolvedValue(makeLicense({ expiresAt: expiresAt }));
1516
-
1517
- await mockRouter
1518
- .match("post", VALIDATE_ROUTE)
1519
- .handlerFunction(mockRequest, mockResponse, nextFunction);
1520
-
1521
- expect(nextFunction).not.toHaveBeenCalled();
1522
-
1523
- const body: JSONObject = getResponseBody();
1524
-
1525
- expect(body["companyName"]).toBe("Acme Inc");
1526
- expect(body["licenseKey"]).toBe(LICENSE_KEY);
1527
- expect(body["userLimit"]).toBe(150);
1528
- expect(body["expiresAt"]).toBe(expiresAt.toISOString());
1529
- expect(body["token"]).toBe("signed.jwt.token");
1530
- });
1531
-
1532
- it("registers a new instance without treating validation as a usage heartbeat", async () => {
1533
- await mockRouter
1534
- .match("post", VALIDATE_ROUTE)
1535
- .handlerFunction(mockRequest, mockResponse, nextFunction);
1536
-
1537
- expect(nextFunction).not.toHaveBeenCalled();
1538
- expect(
1539
- EnterpriseLicenseInstanceService.updateOneById,
1540
- ).not.toHaveBeenCalled();
1541
-
1542
- const createArguments: {
1543
- data: EnterpriseLicenseInstance;
1544
- } = (EnterpriseLicenseInstanceService.create as unknown as jest.Mock).mock
1545
- .calls[0]![0] as {
1546
- data: EnterpriseLicenseInstance;
1547
- };
1548
-
1549
- expect(createArguments.data).toEqual(
1550
- expect.objectContaining({
1551
- enterpriseLicenseId: LICENSE_ID,
1552
- instanceId: "instance-1",
1553
- host: "oneuptime.acme.internal",
1554
- oneuptimeVersion: "12.0.19",
1555
- }),
1556
- );
1557
- expect(createArguments.data.lastReportedAt).toBeUndefined();
1558
- expect(createArguments.data.createdAt).toBeUndefined();
1559
- });
1560
-
1561
- it("updates instance metadata without refreshing its activity timestamps", async () => {
1562
- const registeredAt: Date = new Date("2026-08-01T12:00:00.000Z");
1563
- const lastReportedAt: Date = new Date("2026-08-20T12:00:00.000Z");
1564
- const existingInstance: EnterpriseLicenseInstance = makeInstance({
1565
- createdAt: registeredAt,
1566
- lastReportedAt,
1567
- });
1568
-
1569
- EnterpriseLicenseInstanceService.findOneBy = jest
1570
- .fn()
1571
- .mockResolvedValue(existingInstance);
1572
-
1573
- await mockRouter
1574
- .match("post", VALIDATE_ROUTE)
1575
- .handlerFunction(mockRequest, mockResponse, nextFunction);
1576
-
1577
- expect(nextFunction).not.toHaveBeenCalled();
1578
- expect(EnterpriseLicenseInstanceService.create).not.toHaveBeenCalled();
1579
-
1580
- const updateArguments: {
1581
- data: Partial<EnterpriseLicenseInstance>;
1582
- } = (EnterpriseLicenseInstanceService.updateOneById as unknown as jest.Mock)
1583
- .mock.calls[0]![0] as {
1584
- data: Partial<EnterpriseLicenseInstance>;
1585
- };
1586
-
1587
- expect(updateArguments.data).toEqual(
1588
- expect.objectContaining({
1589
- host: "oneuptime.acme.internal",
1590
- oneuptimeVersion: "12.0.19",
1591
- }),
1592
- );
1593
- expect(
1594
- Object.prototype.hasOwnProperty.call(
1595
- updateArguments.data,
1596
- "lastReportedAt",
1597
- ),
1598
- ).toBe(false);
1599
- expect(
1600
- Object.prototype.hasOwnProperty.call(updateArguments.data, "createdAt"),
1601
- ).toBe(false);
1602
- expect(existingInstance.lastReportedAt).toBe(lastReportedAt);
1603
- expect(existingInstance.createdAt).toBe(registeredAt);
1604
- });
1605
-
1606
- it("serializes registration and re-reads usage inside the aggregation lock", async () => {
1607
- const events: Array<string> = [];
1608
- let isInsideUsageLock: boolean = false;
1609
-
1610
- EnterpriseLicenseService.runWithUsageAggregationLock = jest
1611
- .fn()
1612
- .mockImplementation(
1613
- async (data: { fn: () => Promise<unknown> }): Promise<unknown> => {
1614
- events.push("lock");
1615
- isInsideUsageLock = true;
1616
-
1617
- try {
1618
- return await data.fn();
1619
- } finally {
1620
- isInsideUsageLock = false;
1621
- }
1622
- },
1623
- );
1624
- EnterpriseLicenseInstanceService.create = jest
1625
- .fn()
1626
- .mockImplementation(async (): Promise<void> => {
1627
- expect(isInsideUsageLock).toBe(true);
1628
- events.push("register");
1629
- });
1630
- EnterpriseLicenseInstanceService.findBy = jest
1631
- .fn()
1632
- .mockImplementation(
1633
- async (): Promise<Array<EnterpriseLicenseInstance>> => {
1634
- expect(isInsideUsageLock).toBe(true);
1635
- events.push("instances");
1636
- return [];
1637
- },
1638
- );
1639
- /*
1640
- * Relative to now, like every other date in this file: the legacy usage
1641
- * fallback only trusts userCountUpdatedAt while it is inside the
1642
- * InstanceUsageFreshnessInDays window, so a fixed date silently stops
1643
- * being fresh and the echoed count collapses to 0.
1644
- */
1645
- const userCountUpdatedAt: Date = OneUptimeDate.addRemoveDays(
1646
- OneUptimeDate.getCurrentDate(),
1647
- -1,
1648
- );
1649
-
1650
- EnterpriseLicenseService.findOneById = jest
1651
- .fn()
1652
- .mockImplementation(async (): Promise<EnterpriseLicense> => {
1653
- expect(isInsideUsageLock).toBe(true);
1654
- events.push("license");
1655
- return makeLicense({
1656
- currentUserCount: 17,
1657
- userCountUpdatedAt,
1658
- });
1659
- });
1660
-
1661
- await mockRouter
1662
- .match("post", VALIDATE_ROUTE)
1663
- .handlerFunction(mockRequest, mockResponse, nextFunction);
1664
-
1665
- expect(nextFunction).not.toHaveBeenCalled();
1666
- expect(events).toEqual(["lock", "register", "instances", "license"]);
1667
- expect(getResponseBody()).toEqual(
1668
- expect.objectContaining({
1669
- currentUserCount: 17,
1670
- userCountUpdatedAt: userCountUpdatedAt.toISOString(),
1671
- }),
1672
- );
1673
- expect(
1674
- EnterpriseLicenseService.runWithUsageAggregationLock,
1675
- ).toHaveBeenCalledWith({
1676
- licenseId: LICENSE_ID,
1677
- fn: expect.any(Function),
1678
- });
1679
- });
1680
-
1681
- it("returns an active-only aggregate with matching per-instance provenance", async () => {
1682
- const calculatedAt: Date = new Date("2026-09-02T12:00:00.000Z");
1683
- const inactiveInstance: EnterpriseLicenseInstance = makeInstance({
1684
- instanceId: "instance-1",
1685
- userCount: 80,
1686
- lastReportedAt: OneUptimeDate.addRemoveDays(calculatedAt, -7),
1687
- });
1688
- const activeInstance: EnterpriseLicenseInstance = makeInstance({
1689
- instanceId: "instance-2",
1690
- userCount: 100,
1691
- lastReportedAt: OneUptimeDate.addRemoveDays(calculatedAt, -1),
1692
- });
1693
-
1694
- jest.spyOn(OneUptimeDate, "getCurrentDate").mockReturnValue(calculatedAt);
1695
- EnterpriseLicenseInstanceService.findOneBy = jest
1696
- .fn()
1697
- .mockResolvedValue(inactiveInstance);
1698
- EnterpriseLicenseInstanceService.findBy = jest
1699
- .fn()
1700
- .mockResolvedValue([inactiveInstance, activeInstance]);
1701
- EnterpriseLicenseService.findOneById = jest.fn().mockResolvedValue(
1702
- makeLicense({
1703
- currentUserCount: 180,
1704
- userCountUpdatedAt: calculatedAt,
1705
- userCountSource: EnterpriseLicenseUserCountSource.Instance,
1706
- }),
1707
- );
1708
-
1709
- await mockRouter
1710
- .match("post", VALIDATE_ROUTE)
1711
- .handlerFunction(mockRequest, mockResponse, nextFunction);
1712
-
1713
- expect(nextFunction).not.toHaveBeenCalled();
1714
- expect(getResponseBody()).toEqual(
1715
- expect.objectContaining({
1716
- currentUserCount: 100,
1717
- instances: expect.arrayContaining([
1718
- expect.objectContaining({
1719
- instanceId: "instance-1",
1720
- userCount: 80,
1721
- isCountedTowardsUsage: false,
1722
- }),
1723
- expect.objectContaining({
1724
- instanceId: "instance-2",
1725
- userCount: 100,
1726
- isCountedTowardsUsage: true,
1727
- }),
1728
- ]),
1729
- }),
1730
- );
1731
- });
1732
-
1733
- it("keeps rejecting an expired license, unlike the report route", async () => {
1734
- EnterpriseLicenseService.findOneBy = jest.fn().mockResolvedValue(
1735
- makeLicense({
1736
- expiresAt: OneUptimeDate.addRemoveDays(
1737
- OneUptimeDate.getCurrentDate(),
1738
- -1,
1739
- ),
1740
- }),
1741
- );
1742
-
1743
- await mockRouter
1744
- .match("post", VALIDATE_ROUTE)
1745
- .handlerFunction(mockRequest, mockResponse, nextFunction);
1746
-
1747
- expect(nextFunction).toHaveBeenCalledWith(
1748
- new BadDataException("License key has expired"),
1749
- );
1750
- });
1751
- });
1752
-
1753
- describe("the contract between the two halves of the sync", () => {
1754
- let nextFunction: NextFunction;
1755
-
1756
- beforeEach(() => {
1757
- jest.clearAllMocks();
1758
-
1759
- new EnterpriseLicenseAPI();
1760
-
1761
- EnterpriseLicenseService.findOneBy = jest
1762
- .fn()
1763
- .mockResolvedValue(makeLicense());
1764
- EnterpriseLicenseService.updateOneById = jest
1765
- .fn()
1766
- .mockResolvedValue(undefined);
1767
- EnterpriseLicenseService.runWithUsageAggregationLock = jest
1768
- .fn()
1769
- .mockImplementation(
1770
- async (data: { fn: () => Promise<unknown> }): Promise<unknown> => {
1771
- return await data.fn();
1772
- },
1773
- );
1774
- EnterpriseLicenseInstanceService.findBy = jest
1775
- .fn()
1776
- .mockResolvedValue([makeInstance()]);
1777
- EnterpriseLicenseInstanceService.findOneBy = jest
1778
- .fn()
1779
- .mockResolvedValue(null);
1780
- EnterpriseLicenseInstanceService.create = jest
1781
- .fn()
1782
- .mockResolvedValue(undefined);
1783
- EnterpriseLicenseInstanceService.countBy = jest
1784
- .fn()
1785
- .mockResolvedValue(new PositiveNumber(0));
1786
-
1787
- nextFunction = jest.fn();
1788
- });
1789
-
1790
- afterEach(() => {
1791
- jest.restoreAllMocks();
1792
- });
1793
-
1794
- it("lands every license term when the real response body is fed to the real mapper", async () => {
1795
- /*
1796
- * The end of the bug, asserted end to end: the literal object the license
1797
- * server puts on the wire, through the mapper the installation runs. If
1798
- * either side renames a field, this fails and nothing else has to.
1799
- */
1800
- await mockRouter.match("post", REPORT_ROUTE).handlerFunction(
1801
- {
1802
- body: {
1803
- licenseKey: LICENSE_KEY,
1804
- userCount: 2,
1805
- instanceId: "instance-1",
1806
- host: "oneuptime.acme.internal",
1807
- version: "12.0.19",
1808
- userEmailHashes: [hashOf("1"), hashOf("2")],
1809
- masterAdminEmails: ["admin@acme.com"],
1810
- },
1811
- } as unknown as OneUptimeRequest,
1812
- {
1813
- send: jest.fn(),
1814
- json: jest.fn(),
1815
- status: jest.fn().mockReturnThis(),
1816
- } as unknown as OneUptimeResponse,
1817
- nextFunction,
1818
- );
1819
-
1820
- expect(nextFunction).not.toHaveBeenCalled();
1821
-
1822
- const wireBody: JSONObject = getResponseBody();
1823
-
1824
- const result: EnterpriseLicenseSyncResult =
1825
- EnterpriseLicenseSyncUtil.getGlobalConfigUpdateFromLicenseResponse({
1826
- payload: wireBody,
1827
- reportedAt: OneUptimeDate.getCurrentDate(),
1828
- });
1829
-
1830
- expect(result.warnings).toEqual([]);
1831
- expect(result.updateData.enterpriseLicenseUserLimit).toBe(150);
1832
- expect(result.updateData.enterpriseLicenseCurrentUserCount).toBe(2);
1833
- expect(result.updateData.enterpriseCompanyName).toBe("Acme Inc");
1834
- expect(result.updateData.enterpriseLicenseIsEvaluation).toBe(false);
1835
- expect(result.updateData.enterpriseLicenseToken).toBe("signed.jwt.token");
1836
- expect(result.updateData.enterpriseLicenseExpiresAt).toBeInstanceOf(Date);
1837
- expect(result.updateData.enterpriseLicenseInstances).toHaveLength(1);
1838
- expect(
1839
- (
1840
- result.updateData.enterpriseLicenseInstances as
1841
- | Array<EnterpriseLicenseInstanceSummary>
1842
- | undefined
1843
- )?.[0]?.isCountedTowardsUsage,
1844
- ).toBe(true);
1845
-
1846
- // The installation never adopts a key handed to it by a response.
1847
- expect(Object.keys(result.updateData)).not.toContain(
1848
- "enterpriseLicenseKey",
1849
- );
1850
- });
1851
- });