@oneuptime/common 13.0.8 → 14.0.1
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.
- package/Models/AnalyticsModels/AuditLog.ts +23 -15
- package/Models/DatabaseModels/GlobalConfig.ts +24 -0
- package/Models/DatabaseModels/Index.ts +4 -0
- package/Models/DatabaseModels/Monitor.ts +3 -2
- package/Models/DatabaseModels/MonitorTemplate.ts +2 -1
- package/Models/DatabaseModels/ServiceLevelObjectiveFeed.ts +2 -0
- package/Models/DatabaseModels/ServiceLevelObjectiveLabelRule.ts +522 -0
- package/Models/DatabaseModels/ServiceLevelObjectiveMonitorRule.ts +38 -4
- package/Models/DatabaseModels/ServiceLevelObjectiveOwnerRule.ts +603 -0
- package/Scripts/generate-service-worker.js +65 -4
- package/Server/API/BaseAPI.ts +26 -5
- package/Server/API/GlobalConfigAPI.ts +228 -462
- package/Server/API/ProjectOIDC.ts +25 -16
- package/Server/API/ProjectSSO.ts +24 -16
- package/Server/API/StatusPageAPI.ts +100 -42
- package/Server/API/TeamMemberAPI.ts +10 -1
- package/Server/Enterprise/EnterpriseEdition.ts +649 -0
- package/Server/Enterprise/EnterpriseFeature.ts +48 -0
- package/Server/Enterprise/EnterpriseLicenseSnapshot.ts +192 -0
- package/Server/Enterprise/EnterpriseServerModule.ts +233 -0
- package/Server/EnvironmentConfig.ts +147 -2
- package/Server/Infrastructure/Postgres/SchemaMigrations/1793900000000-AddEnterpriseEditionFirstSeenAt.ts +25 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1794100000000-AddSloLabelAndOwnerRules.ts +248 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1794200000000-AddSloMonitorRuleMonitorType.ts +19 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +6 -0
- package/Server/Middleware/UserAuthorization.ts +38 -16
- package/Server/Services/AuditLogService.ts +84 -1079
- package/Server/Services/DatabaseService.ts +10 -0
- package/Server/Services/EnterpriseLicenseService.ts +38 -0
- package/Server/Services/GlobalConfigService.ts +80 -1
- package/Server/Services/Index.ts +4 -0
- package/Server/Services/MonitorProbeService.ts +116 -20
- package/Server/Services/MonitorService.ts +59 -6
- package/Server/Services/MonitorStatusTimelineService.ts +284 -0
- package/Server/Services/MonitorTemplateService.ts +26 -0
- package/Server/Services/ProjectService.ts +284 -0
- package/Server/Services/RumSessionPinService.ts +25 -6
- package/Server/Services/ServiceLevelObjectiveLabelRuleEngineService.ts +385 -0
- package/Server/Services/ServiceLevelObjectiveLabelRuleService.ts +51 -0
- package/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.ts +92 -16
- package/Server/Services/ServiceLevelObjectiveMonitorRuleService.ts +36 -1
- package/Server/Services/ServiceLevelObjectiveOwnerRuleEngineService.ts +521 -0
- package/Server/Services/ServiceLevelObjectiveOwnerRuleService.ts +51 -0
- package/Server/Services/ServiceLevelObjectiveService.ts +28 -0
- package/Server/Services/StatusPagePrivateUserService.ts +7 -3
- package/Server/Services/StatusPageService.ts +78 -1
- package/Server/Services/TeamMemberService.ts +11 -0
- package/Server/Services/TeamService.ts +10 -0
- package/Server/Services/UserService.ts +10 -17
- package/Server/Types/Database/Permissions/CreatePermission.ts +15 -0
- package/Server/Types/Database/Permissions/EditionPermission.ts +208 -18
- package/Server/Types/Database/Permissions/TablePermission.ts +10 -3
- package/Server/Types/Database/Permissions/UpdatePermission.ts +29 -0
- package/Server/Types/Markdown.ts +41 -0
- package/Server/Types/Workflow/Components/Telegram/SendMessageToChat.ts +27 -1
- package/Server/Utils/AI/Toolbox/SloTools.ts +1 -0
- package/Server/Utils/CommunityEditionSsoReport.ts +361 -0
- package/Server/Utils/EditionEnforcement.ts +233 -0
- package/Server/Utils/FrontendEnvironment.ts +41 -2
- package/Server/Utils/InstanceHealth/RedisHealth.ts +12 -277
- package/Server/Utils/Monitor/AffectedResourceList.ts +148 -0
- package/Server/Utils/Monitor/Criteria/CompareCriteria.ts +1 -1
- package/Server/Utils/Monitor/Criteria/EvaluateOverTime.ts +10 -1
- package/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.ts +1 -1
- package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +720 -465
- package/Server/Utils/Monitor/MonitoringIntervalValidator.ts +116 -0
- package/Server/Utils/Monitor/RootCauseList.ts +139 -0
- package/Server/Utils/Rules/RuleRun/RuleRunRegistry.ts +28 -0
- package/Server/Utils/Slo/SloRulePatternValidator.ts +42 -0
- package/Server/Utils/Workspace/Slack/Slack.ts +423 -10
- package/Tests/App/Accounts/LoginPasskey.test.tsx +18 -0
- package/Tests/App/Accounts/LoginSsoEdition.test.tsx +616 -0
- package/Tests/App/AdminDashboard/AdminAuthenticationSsoEdition.test.tsx +217 -0
- package/Tests/App/AdminDashboard/AdminEnterprisePluginPage.test.tsx +357 -0
- package/Tests/App/AdminDashboard/EnterpriseLicensesShell.test.tsx +214 -0
- package/Tests/App/AdminDashboard/GlobalSSOEnterpriseShells.test.tsx +271 -0
- package/Tests/App/AdminDashboard/HealthShells.test.tsx +659 -0
- package/Tests/App/Dashboard/AuditLogsAndComplianceShells.test.tsx +572 -0
- package/Tests/App/Dashboard/EnterpriseFeatureUpgrade.test.tsx +547 -0
- package/Tests/App/Dashboard/EnterprisePluginPage.test.tsx +667 -0
- package/Tests/App/Dashboard/FeedFilterSortIntegration.test.tsx +1632 -0
- package/Tests/App/Dashboard/FeedOptionsLocales.test.ts +250 -0
- package/Tests/App/Dashboard/FeedOrdering.test.ts +279 -9
- package/Tests/App/Dashboard/IdentityEnterpriseShells.test.tsx +319 -0
- package/Tests/App/Dashboard/InvestigationFeedRefresh.test.tsx +60 -1
- package/Tests/App/Dashboard/InvestigationPanel.test.tsx +594 -3
- package/Tests/App/Dashboard/OnboardingSsoEmptyState.test.tsx +185 -0
- package/Tests/App/Dashboard/ProductOrientation.test.tsx +646 -0
- package/Tests/App/Dashboard/ResourceFeed.test.tsx +612 -2
- package/Tests/App/Dashboard/ResourceFeedPageWiring.test.ts +30 -0
- package/Tests/App/Dashboard/ResourceFeedPagesFilterSort.test.tsx +1202 -0
- package/Tests/App/Dashboard/ScheduledMaintenanceFeedRefresh.test.tsx +52 -1
- package/Tests/App/Dashboard/SecurityEventConnectionDiagnostics.test.tsx +45 -9
- package/Tests/App/Dashboard/SecurityEventDetailPanel.test.tsx +399 -0
- package/Tests/App/Dashboard/SecurityEventListRow.test.tsx +214 -0
- package/Tests/App/Dashboard/SecurityEventsEmptyState.test.tsx +1 -83
- package/Tests/App/Dashboard/SecurityEventsNavTabs.test.tsx +248 -0
- package/Tests/App/Dashboard/SecurityEventsViewer.test.tsx +1203 -0
- package/Tests/App/Dashboard/SloFeed.test.tsx +106 -1
- package/Tests/App/Dashboard/SloMonitorRuleCriteria.test.tsx +566 -0
- package/Tests/App/Dashboard/SloNavigationWiring.test.tsx +6 -4
- package/Tests/App/Dashboard/SloNoticeBanner.test.tsx +186 -0
- package/Tests/App/Dashboard/SloOverviewKpiStrip.test.tsx +388 -44
- package/Tests/App/Dashboard/SloOverviewLayout.test.tsx +613 -0
- package/Tests/App/Dashboard/UserEmailCallSites.test.tsx +11 -91
- package/Tests/App/StatusPage/StatusPageSsoEmptyState.test.tsx +319 -0
- package/Tests/Helpers/RefreshAwareApiScan.ts +757 -0
- package/Tests/Helpers/TenantScopedRequestScan.ts +515 -0
- package/Tests/Models/DatabaseModels/ResourceFeedModels.test.ts +0 -8
- package/Tests/Models/DatabaseModels/SloLabelAndOwnerRuleModels.test.ts +404 -0
- package/Tests/Models/DatabaseModels/SloOverhaulModels.test.ts +17 -1
- package/Tests/Models/DatabaseModels/StaticRuleCriteriaInheritance.test.ts +4 -2
- package/Tests/Scripts/GenerateServiceWorker.test.ts +931 -0
- package/Tests/Server/API/BaseAPICreateItemDoesNotMutateModel.test.ts +130 -0
- package/Tests/Server/API/GlobalConfigLicense.test.ts +742 -497
- package/Tests/Server/API/OnCallReadinessAPI.test.ts +35 -909
- package/Tests/Server/API/SsoProviderListingsEdition.test.ts +432 -0
- package/Tests/Server/API/TeamMemberLeaveScimLockEdition.test.ts +252 -0
- package/Tests/Server/Enterprise/EnterpriseEdition.test.ts +824 -0
- package/Tests/Server/Enterprise/EnterpriseFeatureActive.test.ts +643 -0
- package/Tests/Server/Enterprise/EnterpriseLicenseSnapshot.test.ts +294 -0
- package/Tests/Server/Enterprise/EnterpriseModelFeatureMap.test.ts +93 -0
- package/Tests/Server/Enterprise/EnterpriseOnlyModulesLiveInEe.test.ts +582 -0
- package/Tests/Server/Enterprise/EnterpriseServerModule.test.ts +247 -0
- package/Tests/Server/Enterprise/FakeEnterpriseModule.ts +502 -0
- package/Tests/Server/Enterprise/TestBillingFlag.ts +70 -0
- package/Tests/Server/EnvironmentConfigEnterpriseEdition.test.ts +314 -0
- package/Tests/Server/Infrastructure/ContainerBootConfiguration.test.ts +289 -0
- package/Tests/Server/Infrastructure/E2EClickHouseFixtureAccess.test.ts +5 -1
- package/Tests/Server/Infrastructure/Postgres/AddConfigurableRuleCriteriaMigration.test.ts +3 -0
- package/Tests/Server/Infrastructure/Postgres/AddSloLabelAndOwnerRulesMigration.test.ts +407 -0
- package/Tests/Server/Infrastructure/Postgres/AddSloMonitorRuleMonitorTypeMigration.test.ts +171 -0
- package/Tests/Server/Infrastructure/Postgres/SloProductOverhaulMigration.test.ts +88 -4
- package/Tests/Server/Middleware/UserAuthorization.test.ts +37 -2
- package/Tests/Server/Middleware/UserAuthorizationEditionEnforcement.test.ts +516 -0
- package/Tests/Server/Services/AuditLogService.test.ts +460 -990
- package/Tests/Server/Services/CreateAndReadResponsesKeepIds.test.ts +263 -0
- package/Tests/Server/Services/DatabaseServiceRuleCriteriaValidation.test.ts +104 -0
- package/Tests/Server/Services/EnterpriseLicenseServiceKeyGeneration.test.ts +151 -0
- package/Tests/Server/Services/GlobalConfigServiceLicenseColumns.test.ts +357 -0
- package/Tests/Server/Services/MonitorDestinationInfoIPv6.test.ts +101 -0
- package/Tests/Server/Services/MonitorProbeServiceIntervalScheduling.test.ts +333 -0
- package/Tests/Server/Services/MonitorStatusTimelineDailyAggregates.test.ts +303 -0
- package/Tests/Server/Services/ProjectServiceAuditLogLicense.test.ts +701 -0
- package/Tests/Server/Services/ProjectServiceAuditLogSettingsCache.test.ts +31 -35
- package/Tests/Server/Services/RuleRun/LabelRuleEnginesGroupBRun.test.ts +48 -1
- package/Tests/Server/Services/RuleRun/OwnerRuleEnginesGroupBRun.test.ts +30 -0
- package/Tests/Server/Services/ScimPushGroupsTeamLocksEdition.test.ts +431 -0
- package/Tests/Server/Services/ServiceLevelObjectiveLabelAndOwnerRuleEngines.test.ts +1170 -0
- package/Tests/Server/Services/ServiceLevelObjectiveLabelAndOwnerRuleServices.test.ts +172 -0
- package/Tests/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.test.ts +323 -0
- package/Tests/Server/Services/ServiceLevelObjectiveMonitorRuleService.test.ts +115 -0
- package/Tests/Server/Services/ServiceLevelObjectiveMonitorRuleTriggers.test.ts +21 -2
- package/Tests/Server/Services/ServiceLevelObjectiveMonitorTypePostgres.test.ts +480 -0
- package/Tests/Server/Services/ServiceLevelObjectiveService.test.ts +162 -0
- package/Tests/Server/Services/SsoRequirementReadMasking.test.ts +419 -0
- package/Tests/Server/Services/SsoRequirementWriteGuard.test.ts +769 -0
- package/Tests/Server/Services/StaticRuleCriteriaRuntimeCoverage.test.ts +45 -6
- package/Tests/Server/Services/StatusPagePrivateUserInvitationPolicy.test.ts +94 -0
- package/Tests/Server/Services/UserAuthenticationService.test.ts +35 -0
- package/Tests/Server/Services/UserServiceSeatLimit.test.ts +94 -56
- package/Tests/Server/Types/Database/FeedEventTypeFilterQuery.test.ts +745 -0
- package/Tests/Server/Types/Database/Permissions/EditionPermission.test.ts +1823 -0
- package/Tests/Server/Types/Markdown.test.ts +183 -1
- package/Tests/Server/Types/Workflow/Components/ApiComponentSsrf.test.ts +49 -0
- package/Tests/Server/Types/Workflow/Components/TelegramSendMessageToChat.test.ts +331 -0
- package/Tests/Server/Utils/AI/SloTools.test.ts +10 -0
- package/Tests/Server/Utils/CommunityEditionSsoReport.test.ts +565 -0
- package/Tests/Server/Utils/EditionEnforcement.test.ts +303 -0
- package/Tests/Server/Utils/FrontendEnvironment.test.ts +367 -0
- package/Tests/Server/Utils/InstanceHealth/RedisHealth.test.ts +38 -418
- package/Tests/Server/Utils/Monitor/AffectedResourceList.test.ts +518 -0
- package/Tests/Server/Utils/Monitor/Criteria/EvaluateOverTime.test.ts +24 -4
- package/Tests/Server/Utils/Monitor/Criteria/MetricMonitorCriteriaAnomalyUnits.test.ts +1 -1
- package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluator.test.ts +1339 -96
- package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorBreachingSamples.test.ts +1101 -0
- package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorMetricUnits.test.ts +106 -80
- package/Tests/Server/Utils/Monitor/MonitoringIntervalValidator.test.ts +188 -0
- package/Tests/Server/Utils/Monitor/RootCauseList.test.ts +252 -0
- package/Tests/Server/Utils/Rules/RuleRun/RuleRunPermission.test.ts +154 -0
- package/Tests/Server/Utils/SSRFProtection.test.ts +111 -0
- package/Tests/Server/Utils/Slo/SloRulePatternValidator.test.ts +67 -0
- package/Tests/Server/Utils/VM/VMRunnerSsrf.test.ts +213 -0
- package/Tests/Server/Utils/Workspace/Slack/SlackMarkdownSectionSplit.test.ts +1209 -0
- package/Tests/Types/API/HostnameFromAuthority.test.ts +20 -10
- package/Tests/Types/API/HostnameIPv6.test.ts +149 -0
- package/Tests/Types/API/Route.test.ts +56 -0
- package/Tests/Types/API/URLSchemeLikePath.test.ts +304 -0
- package/Tests/Types/Database/CanonicalModelInstance.test.ts +190 -0
- package/Tests/UI/Components/EditionLabelLapseCopy.ts +104 -0
- package/Tests/UI/Components/EditionLabelLicenseManagerWiring.test.tsx +254 -0
- package/Tests/UI/Components/EditionLabelLicenseRefresh.test.tsx +84 -178
- package/Tests/UI/Components/EditionLabelLicenseStatus.test.tsx +1586 -0
- package/Tests/UI/Components/EditionLabelLicenseWritesGuard.test.tsx +157 -0
- package/Tests/UI/Components/FeedOptions.test.ts +1404 -0
- package/Tests/UI/Components/FeedOptionsButton.test.tsx +2603 -0
- package/Tests/UI/Components/Graphs/DayUptimeGraph.test.tsx +49 -2
- package/Tests/UI/Components/Icon.test.tsx +26 -0
- package/Tests/UI/Components/MarkdownConverters.test.ts +684 -0
- package/Tests/UI/Components/ModelListEmptyState.test.tsx +289 -0
- package/Tests/UI/Components/RuleCriteria/RuleCriteriaModelTable.test.tsx +3 -3
- package/Tests/UI/Components/TelemetryViewer/TelemetryViewerEmptyContent.test.tsx +131 -0
- package/Tests/UI/Components/useFeedItemsViewKey.test.tsx +1017 -0
- package/Tests/UI/Components/useFeedOptions.test.tsx +1107 -0
- package/Tests/UI/EsbuildEnterpriseAlias.test.ts +1127 -0
- package/Tests/Utils/HostAddressUtil.test.ts +138 -0
- package/Tests/Utils/LabelRuleImportExport.test.ts +102 -0
- package/Tests/Utils/Monitor/MonitorDestinationUtil.test.ts +313 -0
- package/Tests/Utils/Monitor/MonitoringIntervalUtil.test.ts +304 -0
- package/Tests/Utils/Slo/SloDuration.test.ts +115 -24
- package/Tests/Utils/Slo/SloHealth.test.ts +93 -10
- package/Tests/Utils/Slo/SloMonitorRuleCriteria.test.ts +109 -0
- package/Tests/Utils/Slo/SloOverviewText.test.ts +64 -0
- package/Tests/Utils/StatusPage/UptimeDailyAggregateUtil.test.ts +248 -0
- package/Types/API/Hostname.ts +63 -16
- package/Types/API/Route.ts +19 -2
- package/Types/API/URL.ts +26 -2
- package/Types/Database/AccessControl/ColumnAccessControl.ts +19 -7
- package/Types/Database/AccessControl/ColumnBillingAccessControl.ts +19 -7
- package/Types/Database/AccessControl/OwnerOnlyColumn.ts +15 -6
- package/Types/Database/CanonicalModelInstance.ts +102 -0
- package/Types/Database/TableColumn.ts +20 -6
- package/Types/EnterpriseLicense/EnterpriseLicensePeriods.ts +29 -0
- package/Types/IP/IPv6.ts +6 -1
- package/Types/Monitor/MetricMonitor/MetricCriteriaContext.ts +6 -5
- package/Types/Monitor/MetricMonitor/MetricMonitorResponse.ts +9 -0
- package/Types/Monitor/MonitorStep.ts +6 -1
- package/Types/Monitor/ProxmoxAlertTemplates.ts +1 -1
- package/Types/Permission.ts +90 -0
- package/Types/Rules/RuleCriteriaFieldRegistry.ts +11 -0
- package/Types/Rules/RuleRun.ts +4 -0
- package/Types/StatusPage/UptimeDailyAggregate.ts +110 -0
- package/UI/Components/EditionLabel/EditionLabel.tsx +796 -1195
- package/UI/Components/EditionLabel/LicenseManager.ts +119 -0
- package/UI/Components/Feed/FeedOptions.ts +427 -0
- package/UI/Components/Feed/FeedOptionsButton.tsx +692 -0
- package/UI/Components/Feed/useFeedItems.ts +28 -9
- package/UI/Components/Feed/useFeedOptions.ts +225 -0
- package/UI/Components/Graphs/DayUptimeGraph.tsx +123 -15
- package/UI/Components/Icon/Icon.tsx +1 -1
- package/UI/Components/Markdown.tsx/MarkdownConverters.ts +0 -0
- package/UI/Components/ModelList/ModelList.tsx +11 -1
- package/UI/Components/MonitorGraphs/Uptime.tsx +69 -0
- package/UI/Components/TelemetryViewer/TelemetryViewer.tsx +28 -12
- package/UI/esbuild-enterprise.js +263 -0
- package/Utils/EnterpriseLicense/EnterpriseLicenseUsage.ts +7 -2
- package/Utils/HostAddressUtil.ts +94 -0
- package/Utils/LabelRuleImportExport.ts +27 -9
- package/Utils/Monitor/MetricValueFormatter.ts +2 -2
- package/Utils/Monitor/MonitorDestinationUtil.ts +157 -0
- package/Utils/Monitor/MonitoringIntervalUtil.ts +436 -0
- package/Utils/Rules/MonitorTypeRuleCriteria.ts +31 -0
- package/Utils/Slo/SloDuration.ts +22 -6
- package/Utils/Slo/SloHealth.ts +8 -7
- package/Utils/Slo/SloMonitorRuleCriteria.ts +33 -1
- package/Utils/Slo/SloOverviewText.ts +33 -0
- package/Utils/Slo/SloProjection.ts +4 -3
- package/Utils/StatusPage/UptimeDailyAggregateUtil.ts +192 -0
- package/build/dist/Models/AnalyticsModels/AuditLog.js +23 -15
- package/build/dist/Models/AnalyticsModels/AuditLog.js.map +1 -1
- package/build/dist/Models/DatabaseModels/GlobalConfig.js +25 -0
- package/build/dist/Models/DatabaseModels/GlobalConfig.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Index.js +4 -0
- package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Monitor.js +2 -2
- package/build/dist/Models/DatabaseModels/Monitor.js.map +1 -1
- package/build/dist/Models/DatabaseModels/MonitorTemplate.js +2 -1
- package/build/dist/Models/DatabaseModels/MonitorTemplate.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveFeed.js +2 -0
- package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveFeed.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveLabelRule.js +530 -0
- package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveLabelRule.js.map +1 -0
- package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveMonitorRule.js +39 -4
- package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveMonitorRule.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveOwnerRule.js +612 -0
- package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveOwnerRule.js.map +1 -0
- package/build/dist/Server/API/BaseAPI.js +26 -5
- package/build/dist/Server/API/BaseAPI.js.map +1 -1
- package/build/dist/Server/API/GlobalConfigAPI.js +148 -344
- package/build/dist/Server/API/GlobalConfigAPI.js.map +1 -1
- package/build/dist/Server/API/ProjectOIDC.js +24 -16
- package/build/dist/Server/API/ProjectOIDC.js.map +1 -1
- package/build/dist/Server/API/ProjectSSO.js +24 -16
- package/build/dist/Server/API/ProjectSSO.js.map +1 -1
- package/build/dist/Server/API/StatusPageAPI.js +93 -43
- package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
- package/build/dist/Server/API/TeamMemberAPI.js +8 -1
- package/build/dist/Server/API/TeamMemberAPI.js.map +1 -1
- package/build/dist/Server/Enterprise/EnterpriseEdition.js +466 -0
- package/build/dist/Server/Enterprise/EnterpriseEdition.js.map +1 -0
- package/build/dist/Server/Enterprise/EnterpriseFeature.js +39 -0
- package/build/dist/Server/Enterprise/EnterpriseFeature.js.map +1 -0
- package/build/dist/Server/Enterprise/EnterpriseLicenseSnapshot.js +70 -0
- package/build/dist/Server/Enterprise/EnterpriseLicenseSnapshot.js.map +1 -0
- package/build/dist/Server/Enterprise/EnterpriseServerModule.js +84 -0
- package/build/dist/Server/Enterprise/EnterpriseServerModule.js.map +1 -0
- package/build/dist/Server/EnvironmentConfig.js +84 -2
- package/build/dist/Server/EnvironmentConfig.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1793900000000-AddEnterpriseEditionFirstSeenAt.js +18 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1793900000000-AddEnterpriseEditionFirstSeenAt.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794100000000-AddSloLabelAndOwnerRules.js +93 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794100000000-AddSloLabelAndOwnerRules.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794200000000-AddSloMonitorRuleMonitorType.js +12 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794200000000-AddSloMonitorRuleMonitorType.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +6 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Middleware/UserAuthorization.js +30 -11
- package/build/dist/Server/Middleware/UserAuthorization.js.map +1 -1
- package/build/dist/Server/Services/AuditLogService.js +71 -740
- package/build/dist/Server/Services/AuditLogService.js.map +1 -1
- package/build/dist/Server/Services/DatabaseService.js +7 -0
- package/build/dist/Server/Services/DatabaseService.js.map +1 -1
- package/build/dist/Server/Services/EnterpriseLicenseService.js +34 -0
- package/build/dist/Server/Services/EnterpriseLicenseService.js.map +1 -1
- package/build/dist/Server/Services/GlobalConfigService.js +63 -0
- package/build/dist/Server/Services/GlobalConfigService.js.map +1 -1
- package/build/dist/Server/Services/Index.js +4 -0
- package/build/dist/Server/Services/Index.js.map +1 -1
- package/build/dist/Server/Services/MonitorProbeService.js +90 -17
- package/build/dist/Server/Services/MonitorProbeService.js.map +1 -1
- package/build/dist/Server/Services/MonitorService.js +48 -6
- package/build/dist/Server/Services/MonitorService.js.map +1 -1
- package/build/dist/Server/Services/MonitorStatusTimelineService.js +242 -0
- package/build/dist/Server/Services/MonitorStatusTimelineService.js.map +1 -1
- package/build/dist/Server/Services/MonitorTemplateService.js +16 -0
- package/build/dist/Server/Services/MonitorTemplateService.js.map +1 -1
- package/build/dist/Server/Services/ProjectService.js +214 -0
- package/build/dist/Server/Services/ProjectService.js.map +1 -1
- package/build/dist/Server/Services/RumSessionPinService.js +24 -6
- package/build/dist/Server/Services/RumSessionPinService.js.map +1 -1
- package/build/dist/Server/Services/ServiceLevelObjectiveLabelRuleEngineService.js +295 -0
- package/build/dist/Server/Services/ServiceLevelObjectiveLabelRuleEngineService.js.map +1 -0
- package/build/dist/Server/Services/ServiceLevelObjectiveLabelRuleService.js +56 -0
- package/build/dist/Server/Services/ServiceLevelObjectiveLabelRuleService.js.map +1 -0
- package/build/dist/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.js +59 -14
- package/build/dist/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/ServiceLevelObjectiveMonitorRuleService.js +22 -1
- package/build/dist/Server/Services/ServiceLevelObjectiveMonitorRuleService.js.map +1 -1
- package/build/dist/Server/Services/ServiceLevelObjectiveOwnerRuleEngineService.js +400 -0
- package/build/dist/Server/Services/ServiceLevelObjectiveOwnerRuleEngineService.js.map +1 -0
- package/build/dist/Server/Services/ServiceLevelObjectiveOwnerRuleService.js +56 -0
- package/build/dist/Server/Services/ServiceLevelObjectiveOwnerRuleService.js.map +1 -0
- package/build/dist/Server/Services/ServiceLevelObjectiveService.js +21 -0
- package/build/dist/Server/Services/ServiceLevelObjectiveService.js.map +1 -1
- package/build/dist/Server/Services/StatusPagePrivateUserService.js +7 -3
- package/build/dist/Server/Services/StatusPagePrivateUserService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageService.js +71 -0
- package/build/dist/Server/Services/StatusPageService.js.map +1 -1
- package/build/dist/Server/Services/TeamMemberService.js +10 -0
- package/build/dist/Server/Services/TeamMemberService.js.map +1 -1
- package/build/dist/Server/Services/TeamService.js +9 -0
- package/build/dist/Server/Services/TeamService.js.map +1 -1
- package/build/dist/Server/Services/UserService.js +10 -16
- package/build/dist/Server/Services/UserService.js.map +1 -1
- package/build/dist/Server/Types/Database/Permissions/CreatePermission.js +10 -0
- package/build/dist/Server/Types/Database/Permissions/CreatePermission.js.map +1 -1
- package/build/dist/Server/Types/Database/Permissions/EditionPermission.js +154 -14
- package/build/dist/Server/Types/Database/Permissions/EditionPermission.js.map +1 -1
- package/build/dist/Server/Types/Database/Permissions/TablePermission.js +10 -3
- package/build/dist/Server/Types/Database/Permissions/TablePermission.js.map +1 -1
- package/build/dist/Server/Types/Database/Permissions/UpdatePermission.js +17 -0
- package/build/dist/Server/Types/Database/Permissions/UpdatePermission.js.map +1 -1
- package/build/dist/Server/Types/Markdown.js +31 -0
- package/build/dist/Server/Types/Markdown.js.map +1 -1
- package/build/dist/Server/Types/Workflow/Components/Telegram/SendMessageToChat.js +20 -1
- package/build/dist/Server/Types/Workflow/Components/Telegram/SendMessageToChat.js.map +1 -1
- package/build/dist/Server/Utils/AI/Toolbox/SloTools.js +1 -0
- package/build/dist/Server/Utils/AI/Toolbox/SloTools.js.map +1 -1
- package/build/dist/Server/Utils/CommunityEditionSsoReport.js +223 -0
- package/build/dist/Server/Utils/CommunityEditionSsoReport.js.map +1 -0
- package/build/dist/Server/Utils/EditionEnforcement.js +187 -0
- package/build/dist/Server/Utils/EditionEnforcement.js.map +1 -0
- package/build/dist/Server/Utils/FrontendEnvironment.js +31 -2
- package/build/dist/Server/Utils/FrontendEnvironment.js.map +1 -1
- package/build/dist/Server/Utils/InstanceHealth/RedisHealth.js +6 -193
- package/build/dist/Server/Utils/InstanceHealth/RedisHealth.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/AffectedResourceList.js +67 -0
- package/build/dist/Server/Utils/Monitor/AffectedResourceList.js.map +1 -0
- package/build/dist/Server/Utils/Monitor/Criteria/CompareCriteria.js +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/EvaluateOverTime.js +8 -1
- package/build/dist/Server/Utils/Monitor/Criteria/EvaluateOverTime.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.js +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +585 -367
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitoringIntervalValidator.js +88 -0
- package/build/dist/Server/Utils/Monitor/MonitoringIntervalValidator.js.map +1 -0
- package/build/dist/Server/Utils/Monitor/RootCauseList.js +101 -0
- package/build/dist/Server/Utils/Monitor/RootCauseList.js.map +1 -0
- package/build/dist/Server/Utils/Rules/RuleRun/RuleRunRegistry.js +28 -0
- package/build/dist/Server/Utils/Rules/RuleRun/RuleRunRegistry.js.map +1 -1
- package/build/dist/Server/Utils/Slo/SloRulePatternValidator.js +28 -0
- package/build/dist/Server/Utils/Slo/SloRulePatternValidator.js.map +1 -0
- package/build/dist/Server/Utils/Workspace/Slack/Slack.js +331 -11
- package/build/dist/Server/Utils/Workspace/Slack/Slack.js.map +1 -1
- package/build/dist/Types/API/Hostname.js +59 -11
- package/build/dist/Types/API/Hostname.js.map +1 -1
- package/build/dist/Types/API/Route.js +18 -2
- package/build/dist/Types/API/Route.js.map +1 -1
- package/build/dist/Types/API/URL.js +23 -2
- package/build/dist/Types/API/URL.js.map +1 -1
- package/build/dist/Types/Database/AccessControl/ColumnAccessControl.js +19 -7
- package/build/dist/Types/Database/AccessControl/ColumnAccessControl.js.map +1 -1
- package/build/dist/Types/Database/AccessControl/ColumnBillingAccessControl.js +19 -7
- package/build/dist/Types/Database/AccessControl/ColumnBillingAccessControl.js.map +1 -1
- package/build/dist/Types/Database/AccessControl/OwnerOnlyColumn.js +13 -6
- package/build/dist/Types/Database/AccessControl/OwnerOnlyColumn.js.map +1 -1
- package/build/dist/Types/Database/CanonicalModelInstance.js +88 -0
- package/build/dist/Types/Database/CanonicalModelInstance.js.map +1 -0
- package/build/dist/Types/Database/TableColumn.js +19 -6
- package/build/dist/Types/Database/TableColumn.js.map +1 -1
- package/build/dist/Types/EnterpriseLicense/EnterpriseLicensePeriods.js +28 -0
- package/build/dist/Types/EnterpriseLicense/EnterpriseLicensePeriods.js.map +1 -0
- package/build/dist/Types/IP/IPv6.js +6 -1
- package/build/dist/Types/IP/IPv6.js.map +1 -1
- package/build/dist/Types/Monitor/MonitorStep.js +5 -0
- package/build/dist/Types/Monitor/MonitorStep.js.map +1 -1
- package/build/dist/Types/Monitor/ProxmoxAlertTemplates.js +1 -1
- package/build/dist/Types/Permission.js +84 -0
- package/build/dist/Types/Permission.js.map +1 -1
- package/build/dist/Types/Rules/RuleCriteriaFieldRegistry.js +11 -0
- package/build/dist/Types/Rules/RuleCriteriaFieldRegistry.js.map +1 -1
- package/build/dist/Types/Rules/RuleRun.js +4 -0
- package/build/dist/Types/Rules/RuleRun.js.map +1 -1
- package/build/dist/Types/StatusPage/UptimeDailyAggregate.js +32 -0
- package/build/dist/Types/StatusPage/UptimeDailyAggregate.js.map +1 -0
- package/build/dist/UI/Components/EditionLabel/EditionLabel.js +414 -585
- package/build/dist/UI/Components/EditionLabel/EditionLabel.js.map +1 -1
- package/build/dist/UI/Components/EditionLabel/LicenseManager.js +2 -0
- package/build/dist/UI/Components/EditionLabel/LicenseManager.js.map +1 -0
- package/build/dist/UI/Components/Feed/FeedOptions.js +235 -0
- package/build/dist/UI/Components/Feed/FeedOptions.js.map +1 -0
- package/build/dist/UI/Components/Feed/FeedOptionsButton.js +346 -0
- package/build/dist/UI/Components/Feed/FeedOptionsButton.js.map +1 -0
- package/build/dist/UI/Components/Feed/useFeedItems.js +17 -7
- package/build/dist/UI/Components/Feed/useFeedItems.js.map +1 -1
- package/build/dist/UI/Components/Feed/useFeedOptions.js +130 -0
- package/build/dist/UI/Components/Feed/useFeedOptions.js.map +1 -0
- package/build/dist/UI/Components/Graphs/DayUptimeGraph.js +90 -17
- package/build/dist/UI/Components/Graphs/DayUptimeGraph.js.map +1 -1
- package/build/dist/UI/Components/Icon/Icon.js +1 -1
- package/build/dist/UI/Components/Icon/Icon.js.map +1 -1
- package/build/dist/UI/Components/Markdown.tsx/MarkdownConverters.js +0 -0
- package/build/dist/UI/Components/Markdown.tsx/MarkdownConverters.js.map +1 -1
- package/build/dist/UI/Components/ModelList/ModelList.js +4 -1
- package/build/dist/UI/Components/ModelList/ModelList.js.map +1 -1
- package/build/dist/UI/Components/MonitorGraphs/Uptime.js +42 -1
- package/build/dist/UI/Components/MonitorGraphs/Uptime.js.map +1 -1
- package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js +2 -2
- package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js.map +1 -1
- package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseUsage.js +6 -2
- package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseUsage.js.map +1 -1
- package/build/dist/Utils/HostAddressUtil.js +80 -0
- package/build/dist/Utils/HostAddressUtil.js.map +1 -0
- package/build/dist/Utils/LabelRuleImportExport.js +22 -6
- package/build/dist/Utils/LabelRuleImportExport.js.map +1 -1
- package/build/dist/Utils/Monitor/MetricValueFormatter.js +2 -2
- package/build/dist/Utils/Monitor/MonitorDestinationUtil.js +127 -0
- package/build/dist/Utils/Monitor/MonitorDestinationUtil.js.map +1 -0
- package/build/dist/Utils/Monitor/MonitoringIntervalUtil.js +374 -0
- package/build/dist/Utils/Monitor/MonitoringIntervalUtil.js.map +1 -0
- package/build/dist/Utils/Rules/MonitorTypeRuleCriteria.js +18 -0
- package/build/dist/Utils/Rules/MonitorTypeRuleCriteria.js.map +1 -0
- package/build/dist/Utils/Slo/SloDuration.js +13 -5
- package/build/dist/Utils/Slo/SloDuration.js.map +1 -1
- package/build/dist/Utils/Slo/SloHealth.js +8 -7
- package/build/dist/Utils/Slo/SloHealth.js.map +1 -1
- package/build/dist/Utils/Slo/SloMonitorRuleCriteria.js +16 -1
- package/build/dist/Utils/Slo/SloMonitorRuleCriteria.js.map +1 -1
- package/build/dist/Utils/Slo/SloOverviewText.js +16 -0
- package/build/dist/Utils/Slo/SloOverviewText.js.map +1 -1
- package/build/dist/Utils/Slo/SloProjection.js.map +1 -1
- package/build/dist/Utils/StatusPage/UptimeDailyAggregateUtil.js +148 -0
- package/build/dist/Utils/StatusPage/UptimeDailyAggregateUtil.js.map +1 -0
- package/jest.config.json +3 -1
- package/package.json +1 -1
- package/test-setup.sh +103 -34
- package/tsconfig.json +13 -0
- package/Server/API/EnterpriseLicenseAPI.ts +0 -925
- package/Server/API/TeamComplianceAPI.ts +0 -158
- package/Server/Middleware/SCIMAuthorization.ts +0 -141
- package/Server/Services/TeamComplianceService.ts +0 -733
- package/Server/Utils/EnterpriseLicense/EnterpriseLicenseSeatUtil.ts +0 -186
- package/Server/Utils/InstanceHealth/PostgresHealth.ts +0 -286
- package/Tests/App/AdminDashboard/EnterpriseLicenseInstanceActivity.test.tsx +0 -451
- package/Tests/App/Dashboard/AuditLogsTable.test.tsx +0 -598
- package/Tests/App/Dashboard/SecurityEventsExplorer.test.tsx +0 -761
- package/Tests/App/Dashboard/SecurityEventsSideMenu.test.tsx +0 -247
- package/Tests/App/Dashboard/SecurityEventsTableColumns.test.tsx +0 -382
- package/Tests/App/Dashboard/SecurityEventsVolumeChart.test.tsx +0 -321
- package/Tests/Server/API/EnterpriseLicenseReportUserCount.test.ts +0 -1851
- package/Tests/Server/Middleware/SCIMAuthorization.test.ts +0 -525
- package/Tests/Server/Services/TeamComplianceServiceBehaviour.test.ts +0 -1845
- package/Tests/Server/Utils/EnterpriseLicense/EnterpriseLicenseSeatUtil.test.ts +0 -495
- package/Tests/Server/Utils/InstanceHealth/PostgresHealth.test.ts +0 -337
- package/Tests/Utils/EnterpriseLicenseSeats.test.ts +0 -576
- package/Tests/Utils/EnterpriseLicenseSync.test.ts +0 -568
- package/Utils/EnterpriseLicense/EnterpriseLicenseSeats.ts +0 -287
- package/Utils/EnterpriseLicense/EnterpriseLicenseSync.ts +0 -244
- package/build/dist/Server/API/EnterpriseLicenseAPI.js +0 -646
- package/build/dist/Server/API/EnterpriseLicenseAPI.js.map +0 -1
- package/build/dist/Server/API/TeamComplianceAPI.js +0 -111
- package/build/dist/Server/API/TeamComplianceAPI.js.map +0 -1
- package/build/dist/Server/Middleware/SCIMAuthorization.js +0 -124
- package/build/dist/Server/Middleware/SCIMAuthorization.js.map +0 -1
- package/build/dist/Server/Services/TeamComplianceService.js +0 -537
- package/build/dist/Server/Services/TeamComplianceService.js.map +0 -1
- package/build/dist/Server/Utils/EnterpriseLicense/EnterpriseLicenseSeatUtil.js +0 -145
- package/build/dist/Server/Utils/EnterpriseLicense/EnterpriseLicenseSeatUtil.js.map +0 -1
- package/build/dist/Server/Utils/InstanceHealth/PostgresHealth.js +0 -194
- package/build/dist/Server/Utils/InstanceHealth/PostgresHealth.js.map +0 -1
- package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseSeats.js +0 -168
- package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseSeats.js.map +0 -1
- package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseSync.js +0 -174
- package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseSync.js.map +0 -1
|
@@ -0,0 +1,1586 @@
|
|
|
1
|
+
import EditionLabel, {
|
|
2
|
+
GRACE_ENFORCEMENT_SUMMARY,
|
|
3
|
+
LICENSE_LAPSED_STATE,
|
|
4
|
+
LICENSE_LAPSE_CONSEQUENCES,
|
|
5
|
+
TRIAL_ENFORCEMENT_SUMMARY,
|
|
6
|
+
} from "../../../UI/Components/EditionLabel/EditionLabel";
|
|
7
|
+
import {
|
|
8
|
+
LicenseManagerDialogParts,
|
|
9
|
+
LicenseManagerProps,
|
|
10
|
+
} from "../../../UI/Components/EditionLabel/LicenseManager";
|
|
11
|
+
import HTTPErrorResponse from "../../../Types/API/HTTPErrorResponse";
|
|
12
|
+
import HTTPResponse from "../../../Types/API/HTTPResponse";
|
|
13
|
+
import { JSONObject } from "../../../Types/JSON";
|
|
14
|
+
import {
|
|
15
|
+
ENTERPRISE_LICENSE_GRACE_PERIOD_IN_DAYS,
|
|
16
|
+
ENTERPRISE_LICENSE_TRIAL_PERIOD_IN_DAYS,
|
|
17
|
+
} from "../../../Types/EnterpriseLicense/EnterpriseLicensePeriods";
|
|
18
|
+
import {
|
|
19
|
+
LAPSED_STATE_PHRASES,
|
|
20
|
+
LAPSE_WARNING_PHRASES,
|
|
21
|
+
RETIRED_NOTICE_EXAMPLES,
|
|
22
|
+
lapsedStateProblems,
|
|
23
|
+
lapseWarningProblems,
|
|
24
|
+
} from "./EditionLabelLapseCopy";
|
|
25
|
+
import "@testing-library/jest-dom";
|
|
26
|
+
import {
|
|
27
|
+
act,
|
|
28
|
+
fireEvent,
|
|
29
|
+
render,
|
|
30
|
+
screen,
|
|
31
|
+
waitFor,
|
|
32
|
+
} from "@testing-library/react";
|
|
33
|
+
import React from "react";
|
|
34
|
+
import { afterEach, beforeEach, describe, expect, it } from "@jest/globals";
|
|
35
|
+
|
|
36
|
+
/*
|
|
37
|
+
* The core half of the edition dialog: what EditionLabel shows on its own,
|
|
38
|
+
* with no license manager - the login page, and any build without the
|
|
39
|
+
* Enterprise plugin. Everything that changes the license (activation, offline
|
|
40
|
+
* activation, refresh, the seat and instance cards) is the Enterprise
|
|
41
|
+
* plugin's LicenseManager now; ee/Tests/UI/License runs this suite's original
|
|
42
|
+
* tests, all of them, against EditionLabel with that manager. This one must
|
|
43
|
+
* pass with ee/ deleted.
|
|
44
|
+
*
|
|
45
|
+
* What the edition pill and dialog say about the license, now that the
|
|
46
|
+
* Enterprise license client reports a status rather than a yes/no:
|
|
47
|
+
*
|
|
48
|
+
* valid as before;
|
|
49
|
+
* grace either an expired license's grace period (renew) or an unlicensed
|
|
50
|
+
* installation's trial counted from its first run (add a license) -
|
|
51
|
+
* the two must never be confused;
|
|
52
|
+
* expired, missing, invalid - with the lapse said plainly: single sign-on,
|
|
53
|
+
* SCIM and audit logging are off, "Require SSO" is not enforced
|
|
54
|
+
* (users sign in with their password), configuration is
|
|
55
|
+
* read-only, and everything resumes when a license is added. The
|
|
56
|
+
* trial and grace notices warn about exactly that beforehand
|
|
57
|
+
* (EditionLabelLapseCopy.ts);
|
|
58
|
+
* the Community Edition image running with IS_ENTERPRISE_EDITION set, told to
|
|
59
|
+
* a master admin;
|
|
60
|
+
* and what EditionLabel hands a license manager, and where its parts land.
|
|
61
|
+
*/
|
|
62
|
+
|
|
63
|
+
let isEnterpriseEdition: boolean = true;
|
|
64
|
+
let billingEnabled: boolean = false;
|
|
65
|
+
let isMasterAdmin: boolean = true;
|
|
66
|
+
|
|
67
|
+
jest.mock("../../../UI/Config", () => {
|
|
68
|
+
const actualConfig: Record<string, unknown> = jest.requireActual(
|
|
69
|
+
"../../../UI/Config",
|
|
70
|
+
) as Record<string, unknown>;
|
|
71
|
+
|
|
72
|
+
const mockedConfig: Record<string, unknown> = { ...actualConfig };
|
|
73
|
+
|
|
74
|
+
Object.defineProperty(mockedConfig, "IS_ENTERPRISE_EDITION", {
|
|
75
|
+
get: (): boolean => {
|
|
76
|
+
return isEnterpriseEdition;
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
Object.defineProperty(mockedConfig, "BILLING_ENABLED", {
|
|
81
|
+
get: (): boolean => {
|
|
82
|
+
return billingEnabled;
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
return mockedConfig;
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
jest.mock("../../../UI/Utils/User", () => {
|
|
90
|
+
return {
|
|
91
|
+
__esModule: true,
|
|
92
|
+
default: {
|
|
93
|
+
isMasterAdmin: (): boolean => {
|
|
94
|
+
return isMasterAdmin;
|
|
95
|
+
},
|
|
96
|
+
/*
|
|
97
|
+
* EditionLabel asks whether anybody is signed in before it fetches the
|
|
98
|
+
* license, to add `signedIn=true` (see EditionLabelLicenseRefresh).
|
|
99
|
+
* These are signed-in dashboard screens.
|
|
100
|
+
*/
|
|
101
|
+
isLoggedIn: (): boolean => {
|
|
102
|
+
return true;
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
interface FetchCall {
|
|
109
|
+
method: string;
|
|
110
|
+
url: string;
|
|
111
|
+
data: JSONObject | undefined;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const fetchCalls: Array<FetchCall> = [];
|
|
115
|
+
|
|
116
|
+
type FetchResponder = (
|
|
117
|
+
call: FetchCall,
|
|
118
|
+
) => HTTPResponse<JSONObject> | HTTPErrorResponse;
|
|
119
|
+
|
|
120
|
+
let respond: FetchResponder;
|
|
121
|
+
|
|
122
|
+
jest.mock("../../../UI/Utils/API/API", () => {
|
|
123
|
+
return {
|
|
124
|
+
__esModule: true,
|
|
125
|
+
default: {
|
|
126
|
+
fetch: (options: {
|
|
127
|
+
method: { toString: () => string };
|
|
128
|
+
url: { toString: () => string };
|
|
129
|
+
data?: JSONObject | undefined;
|
|
130
|
+
}): Promise<HTTPResponse<JSONObject> | HTTPErrorResponse> => {
|
|
131
|
+
const call: FetchCall = {
|
|
132
|
+
method: options.method.toString(),
|
|
133
|
+
url: options.url.toString(),
|
|
134
|
+
data: options.data,
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
fetchCalls.push(call);
|
|
138
|
+
|
|
139
|
+
return Promise.resolve(respond(call));
|
|
140
|
+
},
|
|
141
|
+
getFriendlyMessage: (err: unknown): string => {
|
|
142
|
+
if (err instanceof HTTPErrorResponse) {
|
|
143
|
+
return err.message;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return String(err);
|
|
147
|
+
},
|
|
148
|
+
},
|
|
149
|
+
};
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
const DAY_IN_MS: number = 24 * 60 * 60 * 1000;
|
|
153
|
+
const MISMATCH_FLAG: string = "ENTERPRISE_EDITION_REQUESTED_BUT_NOT_LOADED";
|
|
154
|
+
|
|
155
|
+
const inDays: (days: number) => string = (days: number): string => {
|
|
156
|
+
return new Date(Date.now() + days * DAY_IN_MS).toISOString();
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
type PayloadFunction = (overrides?: Record<string, unknown>) => JSONObject;
|
|
160
|
+
|
|
161
|
+
// What GET /global-config/license gives a master admin for a valid license.
|
|
162
|
+
const adminPayload: PayloadFunction = (
|
|
163
|
+
overrides?: Record<string, unknown>,
|
|
164
|
+
): JSONObject => {
|
|
165
|
+
return {
|
|
166
|
+
edition: "enterprise",
|
|
167
|
+
status: "valid",
|
|
168
|
+
verification: "verified",
|
|
169
|
+
graceReason: null,
|
|
170
|
+
graceEndsAt: null,
|
|
171
|
+
licenseValid: true,
|
|
172
|
+
companyName: "Acme Inc",
|
|
173
|
+
expiresAt: inDays(200),
|
|
174
|
+
isEvaluation: false,
|
|
175
|
+
isEvaluationLicense: false,
|
|
176
|
+
message: null,
|
|
177
|
+
licenseKey: "acme-license-key",
|
|
178
|
+
token: "signed.license.token",
|
|
179
|
+
activationMode: "online",
|
|
180
|
+
userLimit: 50,
|
|
181
|
+
currentUserCount: 10,
|
|
182
|
+
userCountUpdatedAt: "2026-01-01T00:00:00.000Z",
|
|
183
|
+
instances: [],
|
|
184
|
+
instanceId: "instance-1",
|
|
185
|
+
currentVersion: "13.0.0",
|
|
186
|
+
latestVersion: "13.0.0",
|
|
187
|
+
latestVersionPublishedAt: null,
|
|
188
|
+
latestVersionCheckedAt: null,
|
|
189
|
+
isUpdateAvailable: false,
|
|
190
|
+
isUpdateCheckDisabled: false,
|
|
191
|
+
isSeatLimitEnforced: true,
|
|
192
|
+
seatsInUse: 10,
|
|
193
|
+
seatsRemaining: 40,
|
|
194
|
+
canAddMoreUsers: true,
|
|
195
|
+
...(overrides || {}),
|
|
196
|
+
};
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
// What anybody else gets.
|
|
200
|
+
const publicPayload: PayloadFunction = (
|
|
201
|
+
overrides?: Record<string, unknown>,
|
|
202
|
+
): JSONObject => {
|
|
203
|
+
return {
|
|
204
|
+
edition: "enterprise",
|
|
205
|
+
status: "valid",
|
|
206
|
+
verification: "verified",
|
|
207
|
+
graceReason: null,
|
|
208
|
+
graceEndsAt: null,
|
|
209
|
+
licenseValid: true,
|
|
210
|
+
companyName: "Acme Inc",
|
|
211
|
+
expiresAt: inDays(200),
|
|
212
|
+
isEvaluation: false,
|
|
213
|
+
isEvaluationLicense: false,
|
|
214
|
+
...(overrides || {}),
|
|
215
|
+
};
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
const respondWith: (payload: JSONObject) => void = (
|
|
219
|
+
payload: JSONObject,
|
|
220
|
+
): void => {
|
|
221
|
+
respond = (): HTTPResponse<JSONObject> => {
|
|
222
|
+
return new HTTPResponse<JSONObject>(200, payload, {});
|
|
223
|
+
};
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
const renderPill: () => Promise<void> = async (): Promise<void> => {
|
|
227
|
+
render(<EditionLabel />);
|
|
228
|
+
|
|
229
|
+
await waitFor(() => {
|
|
230
|
+
expect(fetchCalls.length).toBeGreaterThan(0);
|
|
231
|
+
});
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
const openDialog: () => Promise<void> = async (): Promise<void> => {
|
|
235
|
+
await renderPill();
|
|
236
|
+
fireEvent.click(screen.getByRole("button", { name: /Enterprise Edition/i }));
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
const pill: () => HTMLElement = (): HTMLElement => {
|
|
240
|
+
return screen.getByRole("button", { name: /Edition/ });
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
beforeEach(() => {
|
|
244
|
+
fetchCalls.length = 0;
|
|
245
|
+
isEnterpriseEdition = true;
|
|
246
|
+
billingEnabled = false;
|
|
247
|
+
isMasterAdmin = true;
|
|
248
|
+
delete process.env[MISMATCH_FLAG];
|
|
249
|
+
respondWith(adminPayload());
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
afterEach(() => {
|
|
253
|
+
delete process.env[MISMATCH_FLAG];
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
describe("EditionLabel - a valid license", () => {
|
|
257
|
+
it("reads plainly as the Enterprise Edition", async () => {
|
|
258
|
+
await renderPill();
|
|
259
|
+
|
|
260
|
+
await waitFor(() => {
|
|
261
|
+
expect(pill()).toHaveAccessibleName("Enterprise Edition, View details");
|
|
262
|
+
});
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
it("shows no status notice", async () => {
|
|
266
|
+
await openDialog();
|
|
267
|
+
|
|
268
|
+
expect(await screen.findByText("Licensed to")).toBeInTheDocument();
|
|
269
|
+
expect(
|
|
270
|
+
screen.queryByTestId("enterprise-license-required-notice"),
|
|
271
|
+
).not.toBeInTheDocument();
|
|
272
|
+
expect(
|
|
273
|
+
screen.queryByTestId("enterprise-license-grace-notice"),
|
|
274
|
+
).not.toBeInTheDocument();
|
|
275
|
+
expect(
|
|
276
|
+
screen.queryByTestId("enterprise-license-trial-notice"),
|
|
277
|
+
).not.toBeInTheDocument();
|
|
278
|
+
});
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
describe("EditionLabel - an expired license in its grace period", () => {
|
|
282
|
+
const graceEndsAt: string = inDays(5);
|
|
283
|
+
|
|
284
|
+
beforeEach(() => {
|
|
285
|
+
respondWith(
|
|
286
|
+
adminPayload({
|
|
287
|
+
status: "grace",
|
|
288
|
+
graceReason: "expired",
|
|
289
|
+
graceEndsAt,
|
|
290
|
+
expiresAt: inDays(-25),
|
|
291
|
+
}),
|
|
292
|
+
);
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
it("says the license expired and asks for a renewal on the pill", async () => {
|
|
296
|
+
await renderPill();
|
|
297
|
+
|
|
298
|
+
await waitFor(() => {
|
|
299
|
+
expect(pill()).toHaveAccessibleName(
|
|
300
|
+
"Enterprise Edition (License Expired, Grace Period), Renew license",
|
|
301
|
+
);
|
|
302
|
+
});
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
it("tells when the grace period ends, and what happens then", async () => {
|
|
306
|
+
await openDialog();
|
|
307
|
+
|
|
308
|
+
const notice: HTMLElement = await screen.findByTestId(
|
|
309
|
+
"enterprise-license-grace-notice",
|
|
310
|
+
);
|
|
311
|
+
|
|
312
|
+
expect(notice).toHaveTextContent(
|
|
313
|
+
new Date(graceEndsAt).toLocaleDateString(),
|
|
314
|
+
);
|
|
315
|
+
expect(notice).toHaveTextContent("5 days left");
|
|
316
|
+
expect(notice).toHaveTextContent(
|
|
317
|
+
"Every enterprise feature keeps working until the grace period ends",
|
|
318
|
+
);
|
|
319
|
+
// What stops when it ends: SSO, SCIM and audit logging, not just configuration.
|
|
320
|
+
expect(lapseWarningProblems(notice.textContent)).toEqual([]);
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
/*
|
|
324
|
+
* The seats and the refresh button that sit beside these belong to the
|
|
325
|
+
* license manager (ee/Tests/UI/License/LicenseManagerStatus pins them).
|
|
326
|
+
*/
|
|
327
|
+
it("still counts as licensed: the details and the grace badge stay", async () => {
|
|
328
|
+
await openDialog();
|
|
329
|
+
|
|
330
|
+
expect(await screen.findByText("Licensed to")).toBeInTheDocument();
|
|
331
|
+
expect(screen.getByText("Grace period")).toBeInTheDocument();
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
it("calls it a grace period, never a trial", async () => {
|
|
335
|
+
await openDialog();
|
|
336
|
+
|
|
337
|
+
const notice: HTMLElement = await screen.findByTestId(
|
|
338
|
+
"enterprise-license-grace-notice",
|
|
339
|
+
);
|
|
340
|
+
|
|
341
|
+
expect(notice).toHaveTextContent(
|
|
342
|
+
"Without a valid license (after the 30-day grace period), single sign-on (SAML and OIDC) stops",
|
|
343
|
+
);
|
|
344
|
+
expect(notice).not.toHaveTextContent(/trial/i);
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
it("does not call an expired license a trial", async () => {
|
|
348
|
+
await openDialog();
|
|
349
|
+
|
|
350
|
+
await screen.findByTestId("enterprise-license-grace-notice");
|
|
351
|
+
|
|
352
|
+
expect(screen.queryByText(/trial/i)).not.toBeInTheDocument();
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
it("asks somebody who is not a master admin to find one", async () => {
|
|
356
|
+
isMasterAdmin = false;
|
|
357
|
+
respondWith(
|
|
358
|
+
publicPayload({
|
|
359
|
+
status: "grace",
|
|
360
|
+
graceReason: "expired",
|
|
361
|
+
graceEndsAt,
|
|
362
|
+
expiresAt: inDays(-25),
|
|
363
|
+
}),
|
|
364
|
+
);
|
|
365
|
+
|
|
366
|
+
await openDialog();
|
|
367
|
+
|
|
368
|
+
expect(
|
|
369
|
+
await screen.findByText(
|
|
370
|
+
"Ask a master admin of this installation to renew the license.",
|
|
371
|
+
),
|
|
372
|
+
).toBeInTheDocument();
|
|
373
|
+
});
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
describe("EditionLabel - an unlicensed installation's trial", () => {
|
|
377
|
+
const graceEndsAt: string = inDays(10);
|
|
378
|
+
|
|
379
|
+
beforeEach(() => {
|
|
380
|
+
respondWith(
|
|
381
|
+
adminPayload({
|
|
382
|
+
status: "grace",
|
|
383
|
+
graceReason: "unlicensed",
|
|
384
|
+
verification: "none",
|
|
385
|
+
graceEndsAt,
|
|
386
|
+
licenseValid: true,
|
|
387
|
+
companyName: null,
|
|
388
|
+
expiresAt: null,
|
|
389
|
+
licenseKey: null,
|
|
390
|
+
token: null,
|
|
391
|
+
activationMode: null,
|
|
392
|
+
userLimit: null,
|
|
393
|
+
isSeatLimitEnforced: false,
|
|
394
|
+
seatsInUse: null,
|
|
395
|
+
seatsRemaining: null,
|
|
396
|
+
}),
|
|
397
|
+
);
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
it("shows the days left on the pill", async () => {
|
|
401
|
+
await renderPill();
|
|
402
|
+
|
|
403
|
+
await waitFor(() => {
|
|
404
|
+
expect(pill()).toHaveAccessibleName(
|
|
405
|
+
"Enterprise Edition (Trial, 10 days left), Add license",
|
|
406
|
+
);
|
|
407
|
+
});
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
it("explains the trial and what happens after it", async () => {
|
|
411
|
+
await openDialog();
|
|
412
|
+
|
|
413
|
+
const notice: HTMLElement = await screen.findByTestId(
|
|
414
|
+
"enterprise-license-trial-notice",
|
|
415
|
+
);
|
|
416
|
+
|
|
417
|
+
expect(notice).toHaveTextContent("Enterprise Edition trial: 10 days left");
|
|
418
|
+
expect(notice).toHaveTextContent("No Enterprise license is installed");
|
|
419
|
+
expect(notice).toHaveTextContent(
|
|
420
|
+
new Date(graceEndsAt).toLocaleDateString(),
|
|
421
|
+
);
|
|
422
|
+
// What stops when it ends: SSO, SCIM and audit logging, not just configuration.
|
|
423
|
+
expect(lapseWarningProblems(notice.textContent)).toEqual([]);
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
/*
|
|
427
|
+
* The first 14 days of an unlicensed install are its trial; only a license
|
|
428
|
+
* that lapsed has a grace period. The notice used to call both "the 14-day
|
|
429
|
+
* grace period".
|
|
430
|
+
*/
|
|
431
|
+
it("calls the first 14 days a trial, not a grace period", async () => {
|
|
432
|
+
await openDialog();
|
|
433
|
+
|
|
434
|
+
const notice: HTMLElement = await screen.findByTestId(
|
|
435
|
+
"enterprise-license-trial-notice",
|
|
436
|
+
);
|
|
437
|
+
|
|
438
|
+
expect(notice).toHaveTextContent(
|
|
439
|
+
"Without a valid license (after the 14-day trial), single sign-on (SAML and OIDC) stops",
|
|
440
|
+
);
|
|
441
|
+
expect(notice).not.toHaveTextContent(/grace/i);
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
it("shows no empty license details for a license that does not exist", async () => {
|
|
445
|
+
await openDialog();
|
|
446
|
+
|
|
447
|
+
await screen.findByTestId("enterprise-license-trial-notice");
|
|
448
|
+
|
|
449
|
+
expect(screen.queryByText("Licensed to")).not.toBeInTheDocument();
|
|
450
|
+
expect(screen.queryByText("Licensed seats")).not.toBeInTheDocument();
|
|
451
|
+
expect(screen.queryByText(/renew/i)).not.toBeInTheDocument();
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
it("says one day, not one days", async () => {
|
|
455
|
+
respondWith(
|
|
456
|
+
adminPayload({
|
|
457
|
+
status: "grace",
|
|
458
|
+
graceReason: "unlicensed",
|
|
459
|
+
verification: "none",
|
|
460
|
+
graceEndsAt: new Date(Date.now() + DAY_IN_MS / 2).toISOString(),
|
|
461
|
+
}),
|
|
462
|
+
);
|
|
463
|
+
|
|
464
|
+
await renderPill();
|
|
465
|
+
|
|
466
|
+
await waitFor(() => {
|
|
467
|
+
expect(pill()).toHaveAccessibleName(
|
|
468
|
+
"Enterprise Edition (Trial, 1 day left), Add license",
|
|
469
|
+
);
|
|
470
|
+
});
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
it("points somebody who is not a master admin at one", async () => {
|
|
474
|
+
isMasterAdmin = false;
|
|
475
|
+
respondWith(
|
|
476
|
+
publicPayload({
|
|
477
|
+
status: "grace",
|
|
478
|
+
graceReason: "unlicensed",
|
|
479
|
+
verification: "none",
|
|
480
|
+
graceEndsAt,
|
|
481
|
+
companyName: null,
|
|
482
|
+
expiresAt: null,
|
|
483
|
+
}),
|
|
484
|
+
);
|
|
485
|
+
|
|
486
|
+
await openDialog();
|
|
487
|
+
|
|
488
|
+
expect(
|
|
489
|
+
await screen.findByText(
|
|
490
|
+
"Ask a master admin of this installation to add a license.",
|
|
491
|
+
),
|
|
492
|
+
).toBeInTheDocument();
|
|
493
|
+
expect(
|
|
494
|
+
screen.queryByPlaceholderText("Enter your enterprise license key"),
|
|
495
|
+
).not.toBeInTheDocument();
|
|
496
|
+
});
|
|
497
|
+
});
|
|
498
|
+
|
|
499
|
+
describe("EditionLabel - no usable license", () => {
|
|
500
|
+
it.each([
|
|
501
|
+
[
|
|
502
|
+
"expired",
|
|
503
|
+
"Enterprise Edition (License Expired), Renew license",
|
|
504
|
+
"License expired",
|
|
505
|
+
],
|
|
506
|
+
[
|
|
507
|
+
"missing",
|
|
508
|
+
"Enterprise Edition (License Required), Validate license",
|
|
509
|
+
"No valid license",
|
|
510
|
+
],
|
|
511
|
+
[
|
|
512
|
+
"invalid",
|
|
513
|
+
"Enterprise Edition (License Invalid), Fix license",
|
|
514
|
+
"License not valid",
|
|
515
|
+
],
|
|
516
|
+
])(
|
|
517
|
+
"a %s license reads as such on the pill and in the dialog",
|
|
518
|
+
async (status: string, pillName: string, noticeTitle: string) => {
|
|
519
|
+
respondWith(
|
|
520
|
+
adminPayload({
|
|
521
|
+
status,
|
|
522
|
+
licenseValid: false,
|
|
523
|
+
verification: status === "missing" ? "none" : "verified",
|
|
524
|
+
}),
|
|
525
|
+
);
|
|
526
|
+
|
|
527
|
+
await renderPill();
|
|
528
|
+
|
|
529
|
+
await waitFor(() => {
|
|
530
|
+
expect(pill()).toHaveAccessibleName(pillName);
|
|
531
|
+
});
|
|
532
|
+
|
|
533
|
+
fireEvent.click(pill());
|
|
534
|
+
|
|
535
|
+
const notice: HTMLElement = await screen.findByTestId(
|
|
536
|
+
"enterprise-license-required-notice",
|
|
537
|
+
);
|
|
538
|
+
|
|
539
|
+
expect(notice).toHaveTextContent(noticeTitle);
|
|
540
|
+
/*
|
|
541
|
+
* The lapse, said plainly: single sign-on, SCIM and audit logging are
|
|
542
|
+
* off, "Require SSO" is not enforced, configuration is read-only, it
|
|
543
|
+
* all resumes with a license, and core monitoring is not touched.
|
|
544
|
+
*/
|
|
545
|
+
expect(lapsedStateProblems(notice.textContent)).toEqual([]);
|
|
546
|
+
},
|
|
547
|
+
);
|
|
548
|
+
|
|
549
|
+
it("shows a master admin why the license is not valid", async () => {
|
|
550
|
+
respondWith(
|
|
551
|
+
adminPayload({
|
|
552
|
+
status: "invalid",
|
|
553
|
+
licenseValid: false,
|
|
554
|
+
message: "The license is bound to a different OneUptime instance.",
|
|
555
|
+
}),
|
|
556
|
+
);
|
|
557
|
+
|
|
558
|
+
await openDialog();
|
|
559
|
+
|
|
560
|
+
expect(
|
|
561
|
+
await screen.findByTestId("enterprise-license-required-notice"),
|
|
562
|
+
).toHaveTextContent(
|
|
563
|
+
"The license is bound to a different OneUptime instance.",
|
|
564
|
+
);
|
|
565
|
+
});
|
|
566
|
+
|
|
567
|
+
/*
|
|
568
|
+
* The old copy promised that validating a key would "turn these on
|
|
569
|
+
* immediately" - the list is services (support, indemnification) as much as
|
|
570
|
+
* features, so it still must not. It then said "Nothing you already
|
|
571
|
+
* configured stops working without one", which stopped being true when
|
|
572
|
+
* single sign-on, SCIM and audit logging began to stop with the license.
|
|
573
|
+
*/
|
|
574
|
+
it("says what a license keeps running, and no longer that nothing stops without one", async () => {
|
|
575
|
+
respondWith(adminPayload({ status: "missing", licenseValid: false }));
|
|
576
|
+
|
|
577
|
+
await openDialog();
|
|
578
|
+
|
|
579
|
+
await screen.findByText("What your license unlocks");
|
|
580
|
+
|
|
581
|
+
expect(
|
|
582
|
+
screen.queryByText(/turn these on immediately/),
|
|
583
|
+
).not.toBeInTheDocument();
|
|
584
|
+
// Without a manager nobody here can add the license, so it names who can.
|
|
585
|
+
expect(
|
|
586
|
+
screen.getByText(
|
|
587
|
+
/A master admin can add the license to keep single sign-on, SCIM provisioning and audit logging running/,
|
|
588
|
+
),
|
|
589
|
+
).toBeInTheDocument();
|
|
590
|
+
expect(
|
|
591
|
+
screen.queryByText(/Nothing you already configured stops working/),
|
|
592
|
+
).not.toBeInTheDocument();
|
|
593
|
+
});
|
|
594
|
+
|
|
595
|
+
it("asks for a license to turn single sign-on, SCIM and audit logging back on", async () => {
|
|
596
|
+
respondWith(adminPayload({ status: "expired", licenseValid: false }));
|
|
597
|
+
|
|
598
|
+
await openDialog();
|
|
599
|
+
|
|
600
|
+
expect(
|
|
601
|
+
await screen.findByText(
|
|
602
|
+
"Add a valid license to turn single sign-on, SCIM and audit logging back on and make enterprise configuration editable.",
|
|
603
|
+
),
|
|
604
|
+
).toBeInTheDocument();
|
|
605
|
+
});
|
|
606
|
+
});
|
|
607
|
+
|
|
608
|
+
describe("EditionLabel - an unverified legacy license", () => {
|
|
609
|
+
/*
|
|
610
|
+
* The notice tells a master admin that refreshing the license fixes it,
|
|
611
|
+
* which takes the license manager: with it, a master admin is told
|
|
612
|
+
* (ee/Tests/UI/License/LicenseManagerStatus); without it, nobody is.
|
|
613
|
+
*/
|
|
614
|
+
it("is not raised where the license cannot be refreshed", async () => {
|
|
615
|
+
respondWith(adminPayload({ verification: "unverified" }));
|
|
616
|
+
|
|
617
|
+
await openDialog();
|
|
618
|
+
|
|
619
|
+
await screen.findByText("Licensed to");
|
|
620
|
+
|
|
621
|
+
expect(
|
|
622
|
+
screen.queryByTestId("enterprise-license-unverified-notice"),
|
|
623
|
+
).not.toBeInTheDocument();
|
|
624
|
+
});
|
|
625
|
+
|
|
626
|
+
it("does not trouble anybody else with it", async () => {
|
|
627
|
+
isMasterAdmin = false;
|
|
628
|
+
respondWith(publicPayload({ verification: "unverified" }));
|
|
629
|
+
|
|
630
|
+
await openDialog();
|
|
631
|
+
|
|
632
|
+
await screen.findByText("Licensed to");
|
|
633
|
+
|
|
634
|
+
expect(
|
|
635
|
+
screen.queryByTestId("enterprise-license-unverified-notice"),
|
|
636
|
+
).not.toBeInTheDocument();
|
|
637
|
+
});
|
|
638
|
+
|
|
639
|
+
it("still reads as a valid license", async () => {
|
|
640
|
+
respondWith(adminPayload({ verification: "unverified" }));
|
|
641
|
+
|
|
642
|
+
await renderPill();
|
|
643
|
+
|
|
644
|
+
await waitFor(() => {
|
|
645
|
+
expect(pill()).toHaveAccessibleName("Enterprise Edition, View details");
|
|
646
|
+
});
|
|
647
|
+
});
|
|
648
|
+
});
|
|
649
|
+
|
|
650
|
+
describe("EditionLabel - the Community Edition", () => {
|
|
651
|
+
beforeEach(() => {
|
|
652
|
+
isEnterpriseEdition = false;
|
|
653
|
+
respondWith(
|
|
654
|
+
adminPayload({
|
|
655
|
+
edition: "community",
|
|
656
|
+
status: null,
|
|
657
|
+
verification: null,
|
|
658
|
+
licenseValid: false,
|
|
659
|
+
licenseKey: null,
|
|
660
|
+
token: null,
|
|
661
|
+
}),
|
|
662
|
+
);
|
|
663
|
+
});
|
|
664
|
+
|
|
665
|
+
it("does not ask about a license on page load", async () => {
|
|
666
|
+
render(<EditionLabel />);
|
|
667
|
+
|
|
668
|
+
expect(
|
|
669
|
+
screen.getByRole("button", {
|
|
670
|
+
name: "Community Edition, Learn more",
|
|
671
|
+
}),
|
|
672
|
+
).toBeInTheDocument();
|
|
673
|
+
expect(fetchCalls).toHaveLength(0);
|
|
674
|
+
});
|
|
675
|
+
|
|
676
|
+
it("keeps the comparison and the version card", async () => {
|
|
677
|
+
render(<EditionLabel />);
|
|
678
|
+
fireEvent.click(screen.getByRole("button", { name: /Community Edition/ }));
|
|
679
|
+
|
|
680
|
+
expect(
|
|
681
|
+
await screen.findByText(/You are running the Community Edition/),
|
|
682
|
+
).toBeInTheDocument();
|
|
683
|
+
expect(await screen.findByText("This installation")).toBeInTheDocument();
|
|
684
|
+
expect(screen.getByText("v13.0.0")).toBeInTheDocument();
|
|
685
|
+
});
|
|
686
|
+
|
|
687
|
+
/*
|
|
688
|
+
* A leftover IS_ENTERPRISE_EDITION=true on the Community image: the server
|
|
689
|
+
* tells the frontend through env.js. Only a master admin can switch the
|
|
690
|
+
* image, so only a master admin is told.
|
|
691
|
+
*/
|
|
692
|
+
it("tells a master admin to switch to the enterprise image", async () => {
|
|
693
|
+
process.env[MISMATCH_FLAG] = "true";
|
|
694
|
+
|
|
695
|
+
render(<EditionLabel />);
|
|
696
|
+
|
|
697
|
+
const button: HTMLElement = screen.getByRole("button", {
|
|
698
|
+
name: "Community Edition, Action needed",
|
|
699
|
+
});
|
|
700
|
+
|
|
701
|
+
fireEvent.click(button);
|
|
702
|
+
|
|
703
|
+
expect(
|
|
704
|
+
await screen.findByTestId("enterprise-edition-image-mismatch"),
|
|
705
|
+
).toHaveTextContent(
|
|
706
|
+
"IS_ENTERPRISE_EDITION is set but this is the Community Edition image — switch to the enterprise image",
|
|
707
|
+
);
|
|
708
|
+
});
|
|
709
|
+
|
|
710
|
+
it("does not tell anybody else", async () => {
|
|
711
|
+
process.env[MISMATCH_FLAG] = "true";
|
|
712
|
+
isMasterAdmin = false;
|
|
713
|
+
|
|
714
|
+
render(<EditionLabel />);
|
|
715
|
+
fireEvent.click(
|
|
716
|
+
screen.getByRole("button", { name: "Community Edition, Learn more" }),
|
|
717
|
+
);
|
|
718
|
+
|
|
719
|
+
await screen.findByText(/You are running the Community Edition/);
|
|
720
|
+
|
|
721
|
+
expect(
|
|
722
|
+
screen.queryByTestId("enterprise-edition-image-mismatch"),
|
|
723
|
+
).not.toBeInTheDocument();
|
|
724
|
+
});
|
|
725
|
+
|
|
726
|
+
it("says nothing about the image when it was not asked for", async () => {
|
|
727
|
+
render(<EditionLabel />);
|
|
728
|
+
fireEvent.click(
|
|
729
|
+
screen.getByRole("button", { name: "Community Edition, Learn more" }),
|
|
730
|
+
);
|
|
731
|
+
|
|
732
|
+
await screen.findByText(/You are running the Community Edition/);
|
|
733
|
+
|
|
734
|
+
expect(
|
|
735
|
+
screen.queryByTestId("enterprise-edition-image-mismatch"),
|
|
736
|
+
).not.toBeInTheDocument();
|
|
737
|
+
});
|
|
738
|
+
});
|
|
739
|
+
|
|
740
|
+
describe("EditionLabel - oneuptime.com", () => {
|
|
741
|
+
it("renders nothing and asks nothing with billing enabled", async () => {
|
|
742
|
+
billingEnabled = true;
|
|
743
|
+
|
|
744
|
+
const { container } = render(<EditionLabel />);
|
|
745
|
+
|
|
746
|
+
await act(async () => {
|
|
747
|
+
await Promise.resolve();
|
|
748
|
+
});
|
|
749
|
+
|
|
750
|
+
expect(container).toBeEmptyDOMElement();
|
|
751
|
+
expect(fetchCalls).toHaveLength(0);
|
|
752
|
+
});
|
|
753
|
+
|
|
754
|
+
/*
|
|
755
|
+
* Every hook runs whatever the edition: the early return for billing sits
|
|
756
|
+
* after them, so a re-render that changes the answer cannot change the
|
|
757
|
+
* number of hooks React sees.
|
|
758
|
+
*/
|
|
759
|
+
it("keeps React's hook order when the billing answer changes between renders", async () => {
|
|
760
|
+
billingEnabled = true;
|
|
761
|
+
|
|
762
|
+
const { rerender, container } = render(<EditionLabel />);
|
|
763
|
+
|
|
764
|
+
billingEnabled = false;
|
|
765
|
+
rerender(<EditionLabel />);
|
|
766
|
+
|
|
767
|
+
await waitFor(() => {
|
|
768
|
+
expect(container).not.toBeEmptyDOMElement();
|
|
769
|
+
});
|
|
770
|
+
});
|
|
771
|
+
});
|
|
772
|
+
|
|
773
|
+
const LICENSE_KEY_PLACEHOLDER: string = "Enter your enterprise license key";
|
|
774
|
+
|
|
775
|
+
const postCalls: () => Array<FetchCall> = (): Array<FetchCall> => {
|
|
776
|
+
return fetchCalls.filter((call: FetchCall): boolean => {
|
|
777
|
+
return call.method !== "GET";
|
|
778
|
+
});
|
|
779
|
+
};
|
|
780
|
+
|
|
781
|
+
// Nothing that changes the license is on screen.
|
|
782
|
+
const expectNoLicenseControls: () => void = (): void => {
|
|
783
|
+
expect(
|
|
784
|
+
screen.queryByPlaceholderText(LICENSE_KEY_PLACEHOLDER),
|
|
785
|
+
).not.toBeInTheDocument();
|
|
786
|
+
expect(
|
|
787
|
+
screen.queryByTestId("enterprise-license-token-input"),
|
|
788
|
+
).not.toBeInTheDocument();
|
|
789
|
+
expect(
|
|
790
|
+
screen.queryByTestId("switch-license-activation-mode"),
|
|
791
|
+
).not.toBeInTheDocument();
|
|
792
|
+
expect(
|
|
793
|
+
screen.queryByTestId("refresh-enterprise-license"),
|
|
794
|
+
).not.toBeInTheDocument();
|
|
795
|
+
expect(screen.queryByText("Change license key")).not.toBeInTheDocument();
|
|
796
|
+
expect(screen.queryByText("Replace license")).not.toBeInTheDocument();
|
|
797
|
+
expect(
|
|
798
|
+
screen.queryByRole("button", { name: /Validate License|Activate License/ }),
|
|
799
|
+
).not.toBeInTheDocument();
|
|
800
|
+
expect(screen.queryByText("Licensed seats")).not.toBeInTheDocument();
|
|
801
|
+
expect(
|
|
802
|
+
screen.queryByText("Instances on this license"),
|
|
803
|
+
).not.toBeInTheDocument();
|
|
804
|
+
};
|
|
805
|
+
|
|
806
|
+
/*
|
|
807
|
+
* The login page, and any Dashboard or Admin Dashboard built without the
|
|
808
|
+
* Enterprise plugin: EditionLabel on its own reads the license and never
|
|
809
|
+
* writes it, and tells nobody - not even a master admin - to manage it from
|
|
810
|
+
* a dialog that cannot.
|
|
811
|
+
*/
|
|
812
|
+
describe("EditionLabel - without a license manager", () => {
|
|
813
|
+
it.each([
|
|
814
|
+
["no license", { status: "missing", licenseValid: false }],
|
|
815
|
+
[
|
|
816
|
+
"the trial",
|
|
817
|
+
{
|
|
818
|
+
status: "grace",
|
|
819
|
+
graceReason: "unlicensed",
|
|
820
|
+
graceEndsAt: inDays(10),
|
|
821
|
+
licenseKey: null,
|
|
822
|
+
},
|
|
823
|
+
],
|
|
824
|
+
[
|
|
825
|
+
"an expired license in its grace period",
|
|
826
|
+
{ status: "grace", graceReason: "expired", graceEndsAt: inDays(5) },
|
|
827
|
+
],
|
|
828
|
+
["a valid license", {}],
|
|
829
|
+
])(
|
|
830
|
+
"offers no license controls and writes nothing for %s",
|
|
831
|
+
async (_name: string, overrides: Record<string, unknown>) => {
|
|
832
|
+
respondWith(adminPayload(overrides));
|
|
833
|
+
|
|
834
|
+
await openDialog();
|
|
835
|
+
|
|
836
|
+
await waitFor(() => {
|
|
837
|
+
expect(screen.getByText("This installation")).toBeInTheDocument();
|
|
838
|
+
});
|
|
839
|
+
|
|
840
|
+
expectNoLicenseControls();
|
|
841
|
+
expect(postCalls()).toEqual([]);
|
|
842
|
+
},
|
|
843
|
+
);
|
|
844
|
+
|
|
845
|
+
it("points even a master admin at somebody else during the trial", async () => {
|
|
846
|
+
respondWith(
|
|
847
|
+
adminPayload({
|
|
848
|
+
status: "grace",
|
|
849
|
+
graceReason: "unlicensed",
|
|
850
|
+
/*
|
|
851
|
+
* No license at all, which is what the server sends for this trial:
|
|
852
|
+
* no token, and verification "none" because there was nothing to
|
|
853
|
+
* judge. An install that IS holding a license reaches the same trial
|
|
854
|
+
* (a token with no expiry recorded) and is told something different.
|
|
855
|
+
*/
|
|
856
|
+
verification: "none",
|
|
857
|
+
token: null,
|
|
858
|
+
licenseKey: null,
|
|
859
|
+
expiresAt: null,
|
|
860
|
+
graceEndsAt: inDays(10),
|
|
861
|
+
}),
|
|
862
|
+
);
|
|
863
|
+
|
|
864
|
+
await openDialog();
|
|
865
|
+
|
|
866
|
+
expect(
|
|
867
|
+
await screen.findByText(
|
|
868
|
+
"Ask a master admin of this installation to add a license.",
|
|
869
|
+
),
|
|
870
|
+
).toBeInTheDocument();
|
|
871
|
+
expect(screen.queryByText(/below/)).not.toBeInTheDocument();
|
|
872
|
+
});
|
|
873
|
+
|
|
874
|
+
it("asks for a renewal rather than a refresh it cannot offer", async () => {
|
|
875
|
+
respondWith(
|
|
876
|
+
adminPayload({
|
|
877
|
+
status: "grace",
|
|
878
|
+
graceReason: "expired",
|
|
879
|
+
graceEndsAt: inDays(5),
|
|
880
|
+
expiresAt: inDays(-25),
|
|
881
|
+
}),
|
|
882
|
+
);
|
|
883
|
+
|
|
884
|
+
await openDialog();
|
|
885
|
+
|
|
886
|
+
expect(
|
|
887
|
+
await screen.findByText(
|
|
888
|
+
"Ask a master admin of this installation to renew the license.",
|
|
889
|
+
),
|
|
890
|
+
).toBeInTheDocument();
|
|
891
|
+
expect(screen.queryByText(/refresh it here/)).not.toBeInTheDocument();
|
|
892
|
+
});
|
|
893
|
+
|
|
894
|
+
it("explains who can activate a missing license", async () => {
|
|
895
|
+
respondWith(adminPayload({ status: "missing", licenseValid: false }));
|
|
896
|
+
|
|
897
|
+
await openDialog();
|
|
898
|
+
|
|
899
|
+
expect(
|
|
900
|
+
await screen.findByText("A master admin has to activate this license"),
|
|
901
|
+
).toBeInTheDocument();
|
|
902
|
+
expect(
|
|
903
|
+
screen.getByText(
|
|
904
|
+
/A master admin can add the license to keep single sign-on, SCIM provisioning and audit logging running and enterprise configuration editable/,
|
|
905
|
+
),
|
|
906
|
+
).toBeInTheDocument();
|
|
907
|
+
});
|
|
908
|
+
});
|
|
909
|
+
|
|
910
|
+
describe("EditionLabel - the Community dialog", () => {
|
|
911
|
+
beforeEach(() => {
|
|
912
|
+
isEnterpriseEdition = false;
|
|
913
|
+
respondWith(
|
|
914
|
+
adminPayload({
|
|
915
|
+
edition: "community",
|
|
916
|
+
status: null,
|
|
917
|
+
verification: null,
|
|
918
|
+
licenseValid: false,
|
|
919
|
+
licenseKey: null,
|
|
920
|
+
token: null,
|
|
921
|
+
}),
|
|
922
|
+
);
|
|
923
|
+
});
|
|
924
|
+
|
|
925
|
+
it("has no license input, refresh or offline controls, and makes no POST", async () => {
|
|
926
|
+
const originalOpen: typeof window.open = window.open;
|
|
927
|
+
const opened: Array<string> = [];
|
|
928
|
+
|
|
929
|
+
window.open = ((url?: string | URL): Window | null => {
|
|
930
|
+
opened.push(String(url));
|
|
931
|
+
return null;
|
|
932
|
+
}) as typeof window.open;
|
|
933
|
+
|
|
934
|
+
try {
|
|
935
|
+
render(<EditionLabel />);
|
|
936
|
+
fireEvent.click(
|
|
937
|
+
screen.getByRole("button", { name: "Community Edition, Learn more" }),
|
|
938
|
+
);
|
|
939
|
+
|
|
940
|
+
expect(
|
|
941
|
+
await screen.findByText(/You are running the Community Edition/),
|
|
942
|
+
).toBeInTheDocument();
|
|
943
|
+
await screen.findByText("This installation");
|
|
944
|
+
|
|
945
|
+
expectNoLicenseControls();
|
|
946
|
+
|
|
947
|
+
// The one primary action is the sales conversation, off-site.
|
|
948
|
+
fireEvent.click(screen.getByRole("button", { name: "Talk to Sales" }));
|
|
949
|
+
|
|
950
|
+
expect(opened).toEqual(["https://oneuptime.com/enterprise/demo"]);
|
|
951
|
+
expect(fetchCalls.length).toBeGreaterThan(0);
|
|
952
|
+
expect(postCalls()).toEqual([]);
|
|
953
|
+
} finally {
|
|
954
|
+
window.open = originalOpen;
|
|
955
|
+
}
|
|
956
|
+
});
|
|
957
|
+
});
|
|
958
|
+
|
|
959
|
+
/*
|
|
960
|
+
* The contract with a license manager, proved with a stand-in: what
|
|
961
|
+
* EditionLabel hands it, where the parts it returns land, and when it is
|
|
962
|
+
* rendered at all. The real manager (ee/AdminDashboard/License) is tested in
|
|
963
|
+
* ee/Tests/UI/License.
|
|
964
|
+
*/
|
|
965
|
+
describe("EditionLabel - with a license manager", () => {
|
|
966
|
+
const managerProps: Array<LicenseManagerProps> = [];
|
|
967
|
+
let parts: LicenseManagerDialogParts = {};
|
|
968
|
+
let submitted: number = 0;
|
|
969
|
+
|
|
970
|
+
const FakeLicenseManager: (
|
|
971
|
+
props: LicenseManagerProps,
|
|
972
|
+
) => React.ReactElement = (
|
|
973
|
+
props: LicenseManagerProps,
|
|
974
|
+
): React.ReactElement => {
|
|
975
|
+
managerProps.push(props);
|
|
976
|
+
|
|
977
|
+
return props.renderDialog(parts);
|
|
978
|
+
};
|
|
979
|
+
|
|
980
|
+
const latestProps: () => LicenseManagerProps = (): LicenseManagerProps => {
|
|
981
|
+
const props: LicenseManagerProps | undefined =
|
|
982
|
+
managerProps[managerProps.length - 1];
|
|
983
|
+
|
|
984
|
+
expect(props).toBeDefined();
|
|
985
|
+
|
|
986
|
+
return props as LicenseManagerProps;
|
|
987
|
+
};
|
|
988
|
+
|
|
989
|
+
const renderWithManager: () => Promise<void> = async (): Promise<void> => {
|
|
990
|
+
render(<EditionLabel licenseManager={FakeLicenseManager} />);
|
|
991
|
+
|
|
992
|
+
await waitFor(() => {
|
|
993
|
+
expect(fetchCalls.length).toBeGreaterThan(0);
|
|
994
|
+
});
|
|
995
|
+
};
|
|
996
|
+
|
|
997
|
+
// Opens the dialog and waits for the license GET to fill its body in.
|
|
998
|
+
const openWithManager: () => Promise<void> = async (): Promise<void> => {
|
|
999
|
+
await renderWithManager();
|
|
1000
|
+
fireEvent.click(pill());
|
|
1001
|
+
await screen.findByText("This installation");
|
|
1002
|
+
};
|
|
1003
|
+
|
|
1004
|
+
const isBefore: (first: HTMLElement, second: HTMLElement) => boolean = (
|
|
1005
|
+
first: HTMLElement,
|
|
1006
|
+
second: HTMLElement,
|
|
1007
|
+
): boolean => {
|
|
1008
|
+
return Boolean(
|
|
1009
|
+
first.compareDocumentPosition(second) & Node.DOCUMENT_POSITION_FOLLOWING,
|
|
1010
|
+
);
|
|
1011
|
+
};
|
|
1012
|
+
|
|
1013
|
+
beforeEach(() => {
|
|
1014
|
+
managerProps.length = 0;
|
|
1015
|
+
submitted = 0;
|
|
1016
|
+
parts = {
|
|
1017
|
+
messages: <div data-testid="fake-messages" />,
|
|
1018
|
+
usage: <div data-testid="fake-usage" />,
|
|
1019
|
+
activation: <div data-testid="fake-activation" />,
|
|
1020
|
+
};
|
|
1021
|
+
});
|
|
1022
|
+
|
|
1023
|
+
it("puts each part where the dialog has always had it", async () => {
|
|
1024
|
+
await openWithManager();
|
|
1025
|
+
|
|
1026
|
+
const messages: HTMLElement = await screen.findByTestId("fake-messages");
|
|
1027
|
+
const details: HTMLElement = screen.getByText("Licensed to");
|
|
1028
|
+
const version: HTMLElement = screen.getByText("This installation");
|
|
1029
|
+
const usage: HTMLElement = screen.getByTestId("fake-usage");
|
|
1030
|
+
const activation: HTMLElement = screen.getByTestId("fake-activation");
|
|
1031
|
+
|
|
1032
|
+
expect(isBefore(messages, details)).toBe(true);
|
|
1033
|
+
expect(isBefore(details, version)).toBe(true);
|
|
1034
|
+
expect(isBefore(version, usage)).toBe(true);
|
|
1035
|
+
expect(isBefore(usage, activation)).toBe(true);
|
|
1036
|
+
});
|
|
1037
|
+
|
|
1038
|
+
it("keeps the status notices between the messages and the activation input, and the feature list last", async () => {
|
|
1039
|
+
isMasterAdmin = false;
|
|
1040
|
+
respondWith(publicPayload({ status: "missing", licenseValid: false }));
|
|
1041
|
+
|
|
1042
|
+
await renderWithManager();
|
|
1043
|
+
fireEvent.click(pill());
|
|
1044
|
+
await screen.findByText("What your license unlocks");
|
|
1045
|
+
|
|
1046
|
+
const messages: HTMLElement = await screen.findByTestId("fake-messages");
|
|
1047
|
+
const required: HTMLElement = screen.getByTestId(
|
|
1048
|
+
"enterprise-license-required-notice",
|
|
1049
|
+
);
|
|
1050
|
+
const askAnAdmin: HTMLElement = screen.getByText(
|
|
1051
|
+
"A master admin has to activate this license",
|
|
1052
|
+
);
|
|
1053
|
+
const activation: HTMLElement = screen.getByTestId("fake-activation");
|
|
1054
|
+
const features: HTMLElement = screen.getByText("What your license unlocks");
|
|
1055
|
+
|
|
1056
|
+
expect(isBefore(messages, required)).toBe(true);
|
|
1057
|
+
expect(isBefore(required, askAnAdmin)).toBe(true);
|
|
1058
|
+
expect(isBefore(askAnAdmin, activation)).toBe(true);
|
|
1059
|
+
expect(isBefore(activation, features)).toBe(true);
|
|
1060
|
+
});
|
|
1061
|
+
|
|
1062
|
+
it("gives the manager the dialog's primary button and its footer", async () => {
|
|
1063
|
+
parts = {
|
|
1064
|
+
...parts,
|
|
1065
|
+
submitButtonText: "Fake Submit",
|
|
1066
|
+
onSubmit: (): void => {
|
|
1067
|
+
submitted += 1;
|
|
1068
|
+
},
|
|
1069
|
+
leftFooterElement: <button type="button">Fake footer action</button>,
|
|
1070
|
+
};
|
|
1071
|
+
|
|
1072
|
+
await openWithManager();
|
|
1073
|
+
|
|
1074
|
+
fireEvent.click(await screen.findByRole("button", { name: "Fake Submit" }));
|
|
1075
|
+
|
|
1076
|
+
expect(submitted).toBe(1);
|
|
1077
|
+
expect(
|
|
1078
|
+
screen.getByRole("button", { name: "Fake footer action" }),
|
|
1079
|
+
).toBeInTheDocument();
|
|
1080
|
+
});
|
|
1081
|
+
|
|
1082
|
+
it("disables the primary button while the manager says so", async () => {
|
|
1083
|
+
parts = {
|
|
1084
|
+
...parts,
|
|
1085
|
+
submitButtonText: "Fake Submit",
|
|
1086
|
+
onSubmit: (): void => {
|
|
1087
|
+
submitted += 1;
|
|
1088
|
+
},
|
|
1089
|
+
disableSubmitButton: true,
|
|
1090
|
+
};
|
|
1091
|
+
|
|
1092
|
+
await openWithManager();
|
|
1093
|
+
|
|
1094
|
+
expect(
|
|
1095
|
+
await screen.findByRole("button", { name: "Fake Submit" }),
|
|
1096
|
+
).toBeDisabled();
|
|
1097
|
+
});
|
|
1098
|
+
|
|
1099
|
+
it("hands the manager what the license GET said", async () => {
|
|
1100
|
+
const body: JSONObject = adminPayload();
|
|
1101
|
+
respondWith(body);
|
|
1102
|
+
|
|
1103
|
+
await openWithManager();
|
|
1104
|
+
|
|
1105
|
+
await screen.findByText("Licensed to");
|
|
1106
|
+
|
|
1107
|
+
const license: LicenseManagerProps["license"] = latestProps().license;
|
|
1108
|
+
|
|
1109
|
+
expect(license.payload).toEqual(body);
|
|
1110
|
+
expect(license).toEqual(
|
|
1111
|
+
expect.objectContaining({
|
|
1112
|
+
isLoading: false,
|
|
1113
|
+
loadError: "",
|
|
1114
|
+
canManageLicense: true,
|
|
1115
|
+
licenseValid: true,
|
|
1116
|
+
isUnlicensedTrial: false,
|
|
1117
|
+
showLicenseDetails: true,
|
|
1118
|
+
companyName: "Acme Inc",
|
|
1119
|
+
licenseKey: "acme-license-key",
|
|
1120
|
+
userLimit: 50,
|
|
1121
|
+
currentUserCount: 10,
|
|
1122
|
+
effectiveUserCount: 10,
|
|
1123
|
+
isSeatLimitEnforced: true,
|
|
1124
|
+
canAddMoreUsers: true,
|
|
1125
|
+
seatTone: "healthy",
|
|
1126
|
+
seatUsageDisplayPercent: 20,
|
|
1127
|
+
currentVersion: "13.0.0",
|
|
1128
|
+
latestVersion: "13.0.0",
|
|
1129
|
+
hasComparableVersion: true,
|
|
1130
|
+
}),
|
|
1131
|
+
);
|
|
1132
|
+
});
|
|
1133
|
+
|
|
1134
|
+
it("tells the manager that somebody who is not a master admin cannot manage the license", async () => {
|
|
1135
|
+
isMasterAdmin = false;
|
|
1136
|
+
respondWith(publicPayload());
|
|
1137
|
+
|
|
1138
|
+
await renderWithManager();
|
|
1139
|
+
fireEvent.click(pill());
|
|
1140
|
+
|
|
1141
|
+
await screen.findByText("Licensed to");
|
|
1142
|
+
|
|
1143
|
+
expect(latestProps().license.canManageLicense).toBe(false);
|
|
1144
|
+
});
|
|
1145
|
+
|
|
1146
|
+
it("tells the manager about the trial", async () => {
|
|
1147
|
+
respondWith(
|
|
1148
|
+
adminPayload({
|
|
1149
|
+
status: "grace",
|
|
1150
|
+
graceReason: "unlicensed",
|
|
1151
|
+
// No license at all: no token, and nothing to verify.
|
|
1152
|
+
verification: "none",
|
|
1153
|
+
token: null,
|
|
1154
|
+
licenseKey: null,
|
|
1155
|
+
expiresAt: null,
|
|
1156
|
+
graceEndsAt: inDays(10),
|
|
1157
|
+
}),
|
|
1158
|
+
);
|
|
1159
|
+
|
|
1160
|
+
await renderWithManager();
|
|
1161
|
+
fireEvent.click(pill());
|
|
1162
|
+
|
|
1163
|
+
await screen.findByTestId("enterprise-license-trial-notice");
|
|
1164
|
+
|
|
1165
|
+
expect(latestProps().license.isUnlicensedTrial).toBe(true);
|
|
1166
|
+
expect(latestProps().license.showLicenseDetails).toBe(false);
|
|
1167
|
+
// With a manager to add it with, a master admin is pointed at it.
|
|
1168
|
+
expect(
|
|
1169
|
+
screen.getByText(
|
|
1170
|
+
"Add a license below before the trial ends to keep single sign-on, SCIM and audit logging running and enterprise configuration editable.",
|
|
1171
|
+
),
|
|
1172
|
+
).toBeInTheDocument();
|
|
1173
|
+
});
|
|
1174
|
+
|
|
1175
|
+
it("hands over no payload, and the reason, when the license could not be read", async () => {
|
|
1176
|
+
respond = (): HTTPErrorResponse => {
|
|
1177
|
+
return new HTTPErrorResponse(
|
|
1178
|
+
500,
|
|
1179
|
+
{ message: "The license service is down." },
|
|
1180
|
+
{},
|
|
1181
|
+
);
|
|
1182
|
+
};
|
|
1183
|
+
|
|
1184
|
+
await renderWithManager();
|
|
1185
|
+
fireEvent.click(pill());
|
|
1186
|
+
|
|
1187
|
+
expect(
|
|
1188
|
+
await screen.findByText("Unable to load license details"),
|
|
1189
|
+
).toBeInTheDocument();
|
|
1190
|
+
expect(latestProps().license.payload).toBeNull();
|
|
1191
|
+
expect(latestProps().license.loadError).toBe(
|
|
1192
|
+
"The license service is down.",
|
|
1193
|
+
);
|
|
1194
|
+
});
|
|
1195
|
+
|
|
1196
|
+
it("stays mounted while the dialog is closed, and is told when it opens and closes", async () => {
|
|
1197
|
+
await renderWithManager();
|
|
1198
|
+
|
|
1199
|
+
expect(latestProps().isDialogOpen).toBe(false);
|
|
1200
|
+
expect(screen.queryByTestId("fake-messages")).not.toBeInTheDocument();
|
|
1201
|
+
|
|
1202
|
+
fireEvent.click(pill());
|
|
1203
|
+
|
|
1204
|
+
expect(await screen.findByTestId("fake-messages")).toBeInTheDocument();
|
|
1205
|
+
expect(latestProps().isDialogOpen).toBe(true);
|
|
1206
|
+
|
|
1207
|
+
fireEvent.click(screen.getByTestId("modal-footer-close-button"));
|
|
1208
|
+
|
|
1209
|
+
expect(screen.queryByTestId("fake-messages")).not.toBeInTheDocument();
|
|
1210
|
+
expect(latestProps().isDialogOpen).toBe(false);
|
|
1211
|
+
});
|
|
1212
|
+
|
|
1213
|
+
it("re-reads the license when the manager asks, with a fresh payload", async () => {
|
|
1214
|
+
await openWithManager();
|
|
1215
|
+
await screen.findByText("Licensed to");
|
|
1216
|
+
|
|
1217
|
+
const before: number = fetchCalls.length;
|
|
1218
|
+
const payloadBefore: LicenseManagerProps["license"]["payload"] =
|
|
1219
|
+
latestProps().license.payload;
|
|
1220
|
+
|
|
1221
|
+
await act(async () => {
|
|
1222
|
+
await latestProps().reloadLicense();
|
|
1223
|
+
});
|
|
1224
|
+
|
|
1225
|
+
expect(fetchCalls.length).toBe(before + 1);
|
|
1226
|
+
expect(fetchCalls[fetchCalls.length - 1]?.method).toBe("GET");
|
|
1227
|
+
expect(latestProps().license.payload).not.toBe(payloadBefore);
|
|
1228
|
+
expect(latestProps().license.payload).toEqual(payloadBefore);
|
|
1229
|
+
});
|
|
1230
|
+
|
|
1231
|
+
it("moves the pill when the manager applies the seat fields of a license write", async () => {
|
|
1232
|
+
await renderWithManager();
|
|
1233
|
+
|
|
1234
|
+
await waitFor(() => {
|
|
1235
|
+
expect(pill()).toHaveAccessibleName("Enterprise Edition, View details");
|
|
1236
|
+
});
|
|
1237
|
+
|
|
1238
|
+
act(() => {
|
|
1239
|
+
latestProps().applySeatEnforcement({
|
|
1240
|
+
isSeatLimitEnforced: true,
|
|
1241
|
+
seatsInUse: 50,
|
|
1242
|
+
canAddMoreUsers: false,
|
|
1243
|
+
});
|
|
1244
|
+
});
|
|
1245
|
+
|
|
1246
|
+
expect(pill()).toHaveAccessibleName(
|
|
1247
|
+
"Enterprise Edition, User limit exceeded",
|
|
1248
|
+
);
|
|
1249
|
+
expect(latestProps().license.seatTone).toBe("breached");
|
|
1250
|
+
expect(latestProps().license.effectiveUserCount).toBe(50);
|
|
1251
|
+
});
|
|
1252
|
+
|
|
1253
|
+
it("is never rendered on the Community Edition", async () => {
|
|
1254
|
+
isEnterpriseEdition = false;
|
|
1255
|
+
|
|
1256
|
+
render(<EditionLabel licenseManager={FakeLicenseManager} />);
|
|
1257
|
+
fireEvent.click(
|
|
1258
|
+
screen.getByRole("button", { name: "Community Edition, Learn more" }),
|
|
1259
|
+
);
|
|
1260
|
+
|
|
1261
|
+
expect(
|
|
1262
|
+
await screen.findByText(/You are running the Community Edition/),
|
|
1263
|
+
).toBeInTheDocument();
|
|
1264
|
+
expect(managerProps).toHaveLength(0);
|
|
1265
|
+
expect(screen.queryByTestId("fake-messages")).not.toBeInTheDocument();
|
|
1266
|
+
});
|
|
1267
|
+
|
|
1268
|
+
it("is never rendered with billing enabled", async () => {
|
|
1269
|
+
billingEnabled = true;
|
|
1270
|
+
|
|
1271
|
+
const { container } = render(
|
|
1272
|
+
<EditionLabel licenseManager={FakeLicenseManager} />,
|
|
1273
|
+
);
|
|
1274
|
+
|
|
1275
|
+
await act(async () => {
|
|
1276
|
+
await Promise.resolve();
|
|
1277
|
+
});
|
|
1278
|
+
|
|
1279
|
+
expect(container).toBeEmptyDOMElement();
|
|
1280
|
+
expect(managerProps).toHaveLength(0);
|
|
1281
|
+
expect(fetchCalls).toHaveLength(0);
|
|
1282
|
+
});
|
|
1283
|
+
});
|
|
1284
|
+
|
|
1285
|
+
/*
|
|
1286
|
+
* The lapse checks the notices above rely on (EditionLabelLapseCopy.ts), and
|
|
1287
|
+
* the sentences EditionLabel builds its notices from. Each check has to fail
|
|
1288
|
+
* on the copy that promised SSO, SCIM and audit logging never stop, and on
|
|
1289
|
+
* copy that drops any single consequence - otherwise a passing notice test
|
|
1290
|
+
* proves nothing.
|
|
1291
|
+
*/
|
|
1292
|
+
describe("EditionLabel - the lapse copy and its checks", () => {
|
|
1293
|
+
it.each(RETIRED_NOTICE_EXAMPLES)(
|
|
1294
|
+
"rejects the retired soft-enforcement copy: %s",
|
|
1295
|
+
(retired: string) => {
|
|
1296
|
+
expect(lapseWarningProblems(retired)).toContain("retired: never stop");
|
|
1297
|
+
expect(lapsedStateProblems(retired)).toContain("retired: never stop");
|
|
1298
|
+
expect(lapseWarningProblems(retired)).toContain(
|
|
1299
|
+
"missing: single sign-on (SAML and OIDC) stops",
|
|
1300
|
+
);
|
|
1301
|
+
expect(lapsedStateProblems(retired)).toContain(
|
|
1302
|
+
"missing: Single sign-on (SAML and OIDC) and SCIM provisioning are off",
|
|
1303
|
+
);
|
|
1304
|
+
},
|
|
1305
|
+
);
|
|
1306
|
+
|
|
1307
|
+
it("accepts the sentences EditionLabel ships", () => {
|
|
1308
|
+
expect(lapseWarningProblems(TRIAL_ENFORCEMENT_SUMMARY)).toEqual([]);
|
|
1309
|
+
expect(lapseWarningProblems(GRACE_ENFORCEMENT_SUMMARY)).toEqual([]);
|
|
1310
|
+
expect(lapsedStateProblems(LICENSE_LAPSED_STATE)).toEqual([]);
|
|
1311
|
+
});
|
|
1312
|
+
|
|
1313
|
+
it.each(
|
|
1314
|
+
LAPSE_WARNING_PHRASES.map((phrase: string) => {
|
|
1315
|
+
return [phrase];
|
|
1316
|
+
}),
|
|
1317
|
+
)("reports a warning that leaves out: %s", (phrase: string) => {
|
|
1318
|
+
const withoutPhrase: string = LICENSE_LAPSE_CONSEQUENCES.replace(
|
|
1319
|
+
phrase,
|
|
1320
|
+
"",
|
|
1321
|
+
);
|
|
1322
|
+
|
|
1323
|
+
expect(withoutPhrase).not.toBe(LICENSE_LAPSE_CONSEQUENCES);
|
|
1324
|
+
expect(lapseWarningProblems(withoutPhrase)).toEqual([`missing: ${phrase}`]);
|
|
1325
|
+
});
|
|
1326
|
+
|
|
1327
|
+
it.each(
|
|
1328
|
+
LAPSED_STATE_PHRASES.map((phrase: string) => {
|
|
1329
|
+
return [phrase];
|
|
1330
|
+
}),
|
|
1331
|
+
)("reports a lapsed notice that leaves out: %s", (phrase: string) => {
|
|
1332
|
+
const withoutPhrase: string = LICENSE_LAPSED_STATE.replace(phrase, "");
|
|
1333
|
+
|
|
1334
|
+
expect(withoutPhrase).not.toBe(LICENSE_LAPSED_STATE);
|
|
1335
|
+
expect(lapsedStateProblems(withoutPhrase)).toEqual([`missing: ${phrase}`]);
|
|
1336
|
+
});
|
|
1337
|
+
|
|
1338
|
+
it("reports shipped copy that also promises SSO keeps running", () => {
|
|
1339
|
+
expect(
|
|
1340
|
+
lapseWarningProblems(
|
|
1341
|
+
`${TRIAL_ENFORCEMENT_SUMMARY} Everything you already configured keeps working.`,
|
|
1342
|
+
),
|
|
1343
|
+
).toEqual(["retired: already configured keeps working"]);
|
|
1344
|
+
expect(
|
|
1345
|
+
lapsedStateProblems(
|
|
1346
|
+
`${LICENSE_LAPSED_STATE} SSO, SCIM and audit logging never stop.`,
|
|
1347
|
+
),
|
|
1348
|
+
).toEqual(["retired: never stop"]);
|
|
1349
|
+
});
|
|
1350
|
+
|
|
1351
|
+
it("warns without calling the trial a grace period, or the grace period a trial", () => {
|
|
1352
|
+
expect(LICENSE_LAPSE_CONSEQUENCES).not.toMatch(/grace|trial/i);
|
|
1353
|
+
expect(LICENSE_LAPSED_STATE).not.toMatch(/grace|trial/i);
|
|
1354
|
+
expect(TRIAL_ENFORCEMENT_SUMMARY).not.toMatch(/grace/i);
|
|
1355
|
+
expect(GRACE_ENFORCEMENT_SUMMARY).not.toMatch(/trial/i);
|
|
1356
|
+
});
|
|
1357
|
+
|
|
1358
|
+
/*
|
|
1359
|
+
* The trial is 14 days and the grace period after a license expires is 30.
|
|
1360
|
+
* Each summary states its own length, derived from the constant the license
|
|
1361
|
+
* classifier uses, so the two can never drift apart.
|
|
1362
|
+
*/
|
|
1363
|
+
it("states a 14-day trial and a 30-day grace period, from the constants", () => {
|
|
1364
|
+
expect(ENTERPRISE_LICENSE_TRIAL_PERIOD_IN_DAYS).toBe(14);
|
|
1365
|
+
expect(ENTERPRISE_LICENSE_GRACE_PERIOD_IN_DAYS).toBe(30);
|
|
1366
|
+
expect(TRIAL_ENFORCEMENT_SUMMARY).toContain(
|
|
1367
|
+
`Without a valid license (after the ${ENTERPRISE_LICENSE_TRIAL_PERIOD_IN_DAYS}-day trial),`,
|
|
1368
|
+
);
|
|
1369
|
+
expect(GRACE_ENFORCEMENT_SUMMARY).toContain(
|
|
1370
|
+
`Without a valid license (after the ${ENTERPRISE_LICENSE_GRACE_PERIOD_IN_DAYS}-day grace period),`,
|
|
1371
|
+
);
|
|
1372
|
+
expect(GRACE_ENFORCEMENT_SUMMARY).not.toContain("14-day");
|
|
1373
|
+
});
|
|
1374
|
+
});
|
|
1375
|
+
|
|
1376
|
+
/*
|
|
1377
|
+
* A license IS installed, and the expiry beside it never was.
|
|
1378
|
+
*
|
|
1379
|
+
* The license client stopped calling that "invalid" - it is the product's own
|
|
1380
|
+
* bookkeeping failing, not an entitlement ending - and falls back to the
|
|
1381
|
+
* unlicensed trial instead. Two things followed for this dialog, and both were
|
|
1382
|
+
* wrong until these cases existed:
|
|
1383
|
+
*
|
|
1384
|
+
* - the explanation ("no expiry is recorded, re-activate it or let the daily
|
|
1385
|
+
* sync fetch it") was rendered in exactly one place, inside the "invalid"
|
|
1386
|
+
* branch, which this state deliberately is not. It reached nobody.
|
|
1387
|
+
* - the copy it fell into instead is the trial notice, which opens "No
|
|
1388
|
+
* Enterprise license is installed." - said to a customer who is holding
|
|
1389
|
+
* one.
|
|
1390
|
+
*
|
|
1391
|
+
* The server sends the token to a master admin and the verification to
|
|
1392
|
+
* everybody, so both audiences can tell the two apart.
|
|
1393
|
+
*/
|
|
1394
|
+
describe("EditionLabel - a license whose expiry was never recorded", () => {
|
|
1395
|
+
const NO_EXPIRY_MESSAGE: string =
|
|
1396
|
+
"A OneUptime Enterprise license is installed, but no expiry is recorded for it, so this installation cannot tell whether it is still current. " +
|
|
1397
|
+
"A master admin can re-activate the license from the edition label in the Admin Dashboard, or leave the daily license sync to fetch its expiry from oneuptime.com.";
|
|
1398
|
+
|
|
1399
|
+
// Inside the trial the classifier reports grace/unlicensed, with a token.
|
|
1400
|
+
const inTrial: PayloadFunction = (
|
|
1401
|
+
overrides?: Record<string, unknown>,
|
|
1402
|
+
): JSONObject => {
|
|
1403
|
+
return adminPayload({
|
|
1404
|
+
status: "grace",
|
|
1405
|
+
graceReason: "unlicensed",
|
|
1406
|
+
verification: "unverified",
|
|
1407
|
+
graceEndsAt: inDays(10),
|
|
1408
|
+
licenseValid: true,
|
|
1409
|
+
expiresAt: null,
|
|
1410
|
+
message: NO_EXPIRY_MESSAGE,
|
|
1411
|
+
userLimit: null,
|
|
1412
|
+
isSeatLimitEnforced: false,
|
|
1413
|
+
seatsInUse: null,
|
|
1414
|
+
seatsRemaining: null,
|
|
1415
|
+
...(overrides || {}),
|
|
1416
|
+
});
|
|
1417
|
+
};
|
|
1418
|
+
|
|
1419
|
+
it("does not tell a customer holding a license that none is installed", async () => {
|
|
1420
|
+
respondWith(inTrial());
|
|
1421
|
+
|
|
1422
|
+
await openDialog();
|
|
1423
|
+
|
|
1424
|
+
const notice: HTMLElement = await screen.findByTestId(
|
|
1425
|
+
"enterprise-license-trial-notice",
|
|
1426
|
+
);
|
|
1427
|
+
|
|
1428
|
+
expect(notice).not.toHaveTextContent("No Enterprise license is installed");
|
|
1429
|
+
expect(notice).toHaveTextContent(
|
|
1430
|
+
"A license is installed, but this installation cannot tell whether it is still current",
|
|
1431
|
+
);
|
|
1432
|
+
});
|
|
1433
|
+
|
|
1434
|
+
it("shows the explanation the license client sent, inside the trial", async () => {
|
|
1435
|
+
respondWith(inTrial());
|
|
1436
|
+
|
|
1437
|
+
await openDialog();
|
|
1438
|
+
|
|
1439
|
+
expect(
|
|
1440
|
+
await screen.findByTestId("enterprise-license-status-message"),
|
|
1441
|
+
).toHaveTextContent("no expiry is recorded for it");
|
|
1442
|
+
expect(
|
|
1443
|
+
screen.getByTestId("enterprise-license-status-message"),
|
|
1444
|
+
).toHaveTextContent("daily license sync");
|
|
1445
|
+
});
|
|
1446
|
+
|
|
1447
|
+
/*
|
|
1448
|
+
* And the remedy names the license that is there. This dialog is rendered
|
|
1449
|
+
* with no license manager, so nobody here can act on it and the copy is the
|
|
1450
|
+
* "ask a master admin" one - re-activate the license, not add a first.
|
|
1451
|
+
*/
|
|
1452
|
+
it("asks for the installed license to be re-activated, not for a new one", async () => {
|
|
1453
|
+
respondWith(inTrial());
|
|
1454
|
+
|
|
1455
|
+
await openDialog();
|
|
1456
|
+
|
|
1457
|
+
const notice: HTMLElement = await screen.findByTestId(
|
|
1458
|
+
"enterprise-license-trial-notice",
|
|
1459
|
+
);
|
|
1460
|
+
|
|
1461
|
+
expect(notice).toHaveTextContent(
|
|
1462
|
+
"Ask a master admin of this installation to re-activate the license",
|
|
1463
|
+
);
|
|
1464
|
+
expect(notice).not.toHaveTextContent("to add a license");
|
|
1465
|
+
});
|
|
1466
|
+
|
|
1467
|
+
/*
|
|
1468
|
+
* Past the trial the classifier reports "missing", which used to render the
|
|
1469
|
+
* bare "This installation has no valid Enterprise license." and nothing
|
|
1470
|
+
* else - the one state where an administrator most needs to be told what is
|
|
1471
|
+
* actually wrong.
|
|
1472
|
+
*/
|
|
1473
|
+
it("shows the explanation past the trial, where the status alone says nothing", async () => {
|
|
1474
|
+
respondWith(
|
|
1475
|
+
adminPayload({
|
|
1476
|
+
status: "missing",
|
|
1477
|
+
verification: "unverified",
|
|
1478
|
+
licenseValid: false,
|
|
1479
|
+
expiresAt: null,
|
|
1480
|
+
graceEndsAt: inDays(-1),
|
|
1481
|
+
message: NO_EXPIRY_MESSAGE,
|
|
1482
|
+
}),
|
|
1483
|
+
);
|
|
1484
|
+
|
|
1485
|
+
await openDialog();
|
|
1486
|
+
|
|
1487
|
+
const notice: HTMLElement = await screen.findByTestId(
|
|
1488
|
+
"enterprise-license-required-notice",
|
|
1489
|
+
);
|
|
1490
|
+
|
|
1491
|
+
expect(notice).toHaveTextContent("no expiry is recorded for it");
|
|
1492
|
+
expect(notice).toHaveTextContent("re-activate the license");
|
|
1493
|
+
expect(notice).not.toHaveTextContent(
|
|
1494
|
+
"This installation has no valid Enterprise license",
|
|
1495
|
+
);
|
|
1496
|
+
});
|
|
1497
|
+
|
|
1498
|
+
/*
|
|
1499
|
+
* Somebody who is not a master admin is sent no token and no message, but is
|
|
1500
|
+
* sent the verification - which is "none" only when there was no token to
|
|
1501
|
+
* judge. So they are not told that no license is installed either.
|
|
1502
|
+
*/
|
|
1503
|
+
it("tells a non-master-admin the same thing, from the verification alone", async () => {
|
|
1504
|
+
isMasterAdmin = false;
|
|
1505
|
+
respondWith(
|
|
1506
|
+
publicPayload({
|
|
1507
|
+
status: "grace",
|
|
1508
|
+
graceReason: "unlicensed",
|
|
1509
|
+
verification: "unverified",
|
|
1510
|
+
graceEndsAt: inDays(10),
|
|
1511
|
+
expiresAt: null,
|
|
1512
|
+
}),
|
|
1513
|
+
);
|
|
1514
|
+
|
|
1515
|
+
await openDialog();
|
|
1516
|
+
|
|
1517
|
+
const notice: HTMLElement = await screen.findByTestId(
|
|
1518
|
+
"enterprise-license-trial-notice",
|
|
1519
|
+
);
|
|
1520
|
+
|
|
1521
|
+
expect(notice).not.toHaveTextContent("No Enterprise license is installed");
|
|
1522
|
+
expect(notice).toHaveTextContent(
|
|
1523
|
+
"Ask a master admin of this installation to re-activate the license",
|
|
1524
|
+
);
|
|
1525
|
+
// No message is sent to them, so none is rendered.
|
|
1526
|
+
expect(
|
|
1527
|
+
screen.queryByTestId("enterprise-license-status-message"),
|
|
1528
|
+
).not.toBeInTheDocument();
|
|
1529
|
+
});
|
|
1530
|
+
|
|
1531
|
+
/*
|
|
1532
|
+
* The genuinely unlicensed installation, unchanged. The whole point of the
|
|
1533
|
+
* qualification is that it applies only when a license IS there: verification
|
|
1534
|
+
* "none" and no token.
|
|
1535
|
+
*/
|
|
1536
|
+
it("leaves the copy for an install with no license at all exactly as it was", async () => {
|
|
1537
|
+
respondWith(
|
|
1538
|
+
adminPayload({
|
|
1539
|
+
status: "grace",
|
|
1540
|
+
graceReason: "unlicensed",
|
|
1541
|
+
verification: "none",
|
|
1542
|
+
graceEndsAt: inDays(10),
|
|
1543
|
+
licenseValid: true,
|
|
1544
|
+
token: null,
|
|
1545
|
+
licenseKey: null,
|
|
1546
|
+
expiresAt: null,
|
|
1547
|
+
companyName: null,
|
|
1548
|
+
}),
|
|
1549
|
+
);
|
|
1550
|
+
|
|
1551
|
+
await openDialog();
|
|
1552
|
+
|
|
1553
|
+
const notice: HTMLElement = await screen.findByTestId(
|
|
1554
|
+
"enterprise-license-trial-notice",
|
|
1555
|
+
);
|
|
1556
|
+
|
|
1557
|
+
expect(notice).toHaveTextContent("No Enterprise license is installed");
|
|
1558
|
+
expect(notice).toHaveTextContent(
|
|
1559
|
+
"Ask a master admin of this installation to add a license",
|
|
1560
|
+
);
|
|
1561
|
+
expect(notice).not.toHaveTextContent("A license is installed");
|
|
1562
|
+
});
|
|
1563
|
+
|
|
1564
|
+
/*
|
|
1565
|
+
* An "invalid" license still shows its message exactly once: it is both the
|
|
1566
|
+
* reason line and the message, and a master admin must not read the same
|
|
1567
|
+
* sentence twice.
|
|
1568
|
+
*/
|
|
1569
|
+
it("does not repeat the message for an invalid license", async () => {
|
|
1570
|
+
respondWith(
|
|
1571
|
+
adminPayload({
|
|
1572
|
+
status: "invalid",
|
|
1573
|
+
licenseValid: false,
|
|
1574
|
+
message: "The license is bound to a different OneUptime instance.",
|
|
1575
|
+
}),
|
|
1576
|
+
);
|
|
1577
|
+
|
|
1578
|
+
await openDialog();
|
|
1579
|
+
|
|
1580
|
+
await screen.findByTestId("enterprise-license-required-notice");
|
|
1581
|
+
|
|
1582
|
+
expect(
|
|
1583
|
+
screen.getAllByText(/bound to a different OneUptime instance/),
|
|
1584
|
+
).toHaveLength(1);
|
|
1585
|
+
});
|
|
1586
|
+
});
|