@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
|
@@ -1,1124 +1,594 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
3
|
-
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, test } from "@jest/globals";
|
|
2
|
+
import AuditLogServiceInstance, {
|
|
3
|
+
AUDIT_LOG_CREATE_REFUSED_MESSAGE,
|
|
4
|
+
AuditLogService,
|
|
5
|
+
} from "../../../Server/Services/AuditLogService";
|
|
6
|
+
import AnalyticsDatabaseService from "../../../Server/Services/AnalyticsDatabaseService";
|
|
7
|
+
import ModelPermission from "../../../Server/Types/AnalyticsDatabase/ModelPermission";
|
|
8
|
+
import EnterpriseEdition from "../../../Server/Enterprise/EnterpriseEdition";
|
|
9
|
+
import logger from "../../../Server/Utils/Logger";
|
|
4
10
|
import AuditLog from "../../../Models/AnalyticsModels/AuditLog";
|
|
5
|
-
import BaseModel from "../../../Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel";
|
|
6
|
-
import AlertSeverity from "../../../Models/DatabaseModels/AlertSeverity";
|
|
7
|
-
import Label from "../../../Models/DatabaseModels/Label";
|
|
8
11
|
import Monitor from "../../../Models/DatabaseModels/Monitor";
|
|
9
|
-
import
|
|
10
|
-
import ServiceLevelObjective from "../../../Models/DatabaseModels/ServiceLevelObjective";
|
|
11
|
-
import ServiceLevelObjectiveBurnRateRule from "../../../Models/DatabaseModels/ServiceLevelObjectiveBurnRateRule";
|
|
12
|
-
import ServiceLevelObjectiveMonitorRule from "../../../Models/DatabaseModels/ServiceLevelObjectiveMonitorRule";
|
|
13
|
-
import ServiceLevelObjectiveOwnerTeam from "../../../Models/DatabaseModels/ServiceLevelObjectiveOwnerTeam";
|
|
14
|
-
import ServiceLevelObjectiveOwnerUser from "../../../Models/DatabaseModels/ServiceLevelObjectiveOwnerUser";
|
|
15
|
-
import Team from "../../../Models/DatabaseModels/Team";
|
|
16
|
-
import User from "../../../Models/DatabaseModels/User";
|
|
17
|
-
import AuditLogAction from "../../../Types/AuditLog/AuditLogAction";
|
|
12
|
+
import AnalyticsTableColumn from "../../../Types/AnalyticsDatabase/TableColumn";
|
|
18
13
|
import DatabaseCommonInteractionProps from "../../../Types/BaseDatabase/DatabaseCommonInteractionProps";
|
|
19
|
-
import
|
|
20
|
-
import Email from "../../../Types/Email";
|
|
21
|
-
import { JSONObject } from "../../../Types/JSON";
|
|
22
|
-
import Name from "../../../Types/Name";
|
|
14
|
+
import NotAuthorizedException from "../../../Types/Exception/NotAuthorizedException";
|
|
23
15
|
import ObjectID from "../../../Types/ObjectID";
|
|
16
|
+
import Permission, {
|
|
17
|
+
UserTenantAccessPermission,
|
|
18
|
+
} from "../../../Types/Permission";
|
|
24
19
|
import UserType from "../../../Types/UserType";
|
|
25
|
-
import
|
|
20
|
+
import FakeEnterpriseModule, {
|
|
21
|
+
createAuditLogRecorderSpy,
|
|
22
|
+
installFakeEnterpriseModule,
|
|
23
|
+
MockedAuditLogRecorder,
|
|
24
|
+
uninstallEnterpriseModule,
|
|
25
|
+
} from "../Enterprise/FakeEnterpriseModule";
|
|
26
|
+
import { setTestBillingEnabled } from "../Enterprise/TestBillingFlag";
|
|
26
27
|
|
|
27
28
|
/*
|
|
28
|
-
* AuditLogService
|
|
29
|
-
* says. Every failure mode here is silent - record* swallows its errors and
|
|
30
|
-
* an empty audit page looks exactly like "nobody changed anything" - so each
|
|
31
|
-
* rule is pinned against the entry that actually reaches the insert:
|
|
29
|
+
* Core's AuditLogService after the Community / Enterprise split.
|
|
32
30
|
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
* - readability: relations are recorded as { _id, name }, nameless owner
|
|
39
|
-
* rows are named after their user or team, and names are only ever read
|
|
40
|
-
* from inside the audited project.
|
|
31
|
+
* Audit-log RECORDING is an Enterprise Edition feature: which changes are
|
|
32
|
+
* recorded, the diff, redaction, names and the settings cache live in ee/
|
|
33
|
+
* (ee/Server/AuditLog/AuditLogRecorder.ts, pinned by
|
|
34
|
+
* ee/Tests/Server/AuditLog/AuditLogRecorder.test.ts). What stays here, and is
|
|
35
|
+
* pinned here, is the seam every audited write goes through:
|
|
41
36
|
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
37
|
+
* - Community Edition: no recorder, every call is a no-op and nothing is
|
|
38
|
+
* written - there is no recording code left in core to write it;
|
|
39
|
+
* - Enterprise Edition: each call reaches the recorder with exactly the
|
|
40
|
+
* arguments DatabaseService passed, looked up on every call (the module
|
|
41
|
+
* registers after core loaded);
|
|
42
|
+
* - never a failed write because of its audit entry, whatever the recorder
|
|
43
|
+
* does.
|
|
44
|
+
*
|
|
45
|
+
* Billing and the edition are pinned in every test (CI's config.env sets
|
|
46
|
+
* BILLING_ENABLED=true). This suite must pass with ee/ deleted: it uses only
|
|
47
|
+
* the fake enterprise module.
|
|
46
48
|
*/
|
|
47
|
-
|
|
48
|
-
const findProjectMock: jest.Mock = jest.fn();
|
|
49
|
-
const findUserMock: jest.Mock = jest.fn();
|
|
50
|
-
|
|
51
|
-
jest.mock("../../../Server/Services/ProjectService", () => {
|
|
52
|
-
return {
|
|
53
|
-
__esModule: true,
|
|
54
|
-
default: {
|
|
55
|
-
findOneById: (...args: Array<unknown>): unknown => {
|
|
56
|
-
return findProjectMock(...args);
|
|
57
|
-
},
|
|
58
|
-
},
|
|
59
|
-
};
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
jest.mock("../../../Server/Services/UserService", () => {
|
|
63
|
-
return {
|
|
64
|
-
__esModule: true,
|
|
65
|
-
default: {
|
|
66
|
-
findOneById: (...args: Array<unknown>): unknown => {
|
|
67
|
-
return findUserMock(...args);
|
|
68
|
-
},
|
|
69
|
-
},
|
|
70
|
-
};
|
|
71
|
-
});
|
|
72
|
-
|
|
73
49
|
jest.mock("../../../Server/EnvironmentConfig", () => {
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
return
|
|
50
|
+
const billingFlag: typeof import("../Enterprise/TestBillingFlag") =
|
|
51
|
+
jest.requireActual(
|
|
52
|
+
"../Enterprise/TestBillingFlag",
|
|
53
|
+
) as typeof import("../Enterprise/TestBillingFlag");
|
|
54
|
+
|
|
55
|
+
return billingFlag.withLiveBillingFlag(
|
|
56
|
+
jest.requireActual("../../../Server/EnvironmentConfig") as Record<
|
|
57
|
+
string,
|
|
58
|
+
unknown
|
|
59
|
+
>,
|
|
60
|
+
);
|
|
80
61
|
});
|
|
81
62
|
|
|
82
|
-
type EnvironmentFlags = {
|
|
83
|
-
IsEnterpriseEdition: boolean;
|
|
84
|
-
IsBillingEnabled: boolean;
|
|
85
|
-
};
|
|
86
|
-
|
|
87
|
-
const environment: EnvironmentFlags = jest.requireMock(
|
|
88
|
-
"../../../Server/EnvironmentConfig",
|
|
89
|
-
) as EnvironmentFlags;
|
|
90
|
-
|
|
91
63
|
const PROJECT_ID: ObjectID = new ObjectID(
|
|
92
64
|
"11111111-1111-4111-8111-111111111111",
|
|
93
65
|
);
|
|
94
|
-
const USER_ID: ObjectID = new ObjectID("22222222-2222-4222-8222-222222222222");
|
|
95
|
-
const SLO_ID: ObjectID = new ObjectID("33333333-3333-4333-8333-333333333333");
|
|
96
|
-
const RULE_ID: ObjectID = new ObjectID("44444444-4444-4444-8444-444444444444");
|
|
97
|
-
const OWNER_ROW_ID: ObjectID = new ObjectID(
|
|
98
|
-
"55555555-5555-4555-8555-555555555555",
|
|
99
|
-
);
|
|
100
|
-
const OWNER_USER_ID: ObjectID = new ObjectID(
|
|
101
|
-
"66666666-6666-4666-8666-666666666666",
|
|
102
|
-
);
|
|
103
|
-
const TEAM_ID: ObjectID = new ObjectID("77777777-7777-4777-8777-777777777777");
|
|
104
66
|
const MONITOR_ID: ObjectID = new ObjectID(
|
|
105
67
|
"88888888-8888-4888-8888-888888888888",
|
|
106
68
|
);
|
|
107
69
|
|
|
108
|
-
const
|
|
109
|
-
|
|
110
|
-
const LABEL_C: string = "aaaaaaaa-0000-4000-8000-00000000000c";
|
|
111
|
-
|
|
112
|
-
const SEVERITY_1: string = "bbbbbbbb-0000-4000-8000-000000000001";
|
|
113
|
-
const SEVERITY_2: string = "bbbbbbbb-0000-4000-8000-000000000002";
|
|
114
|
-
|
|
115
|
-
const SLO_RESOURCE_TYPE: string = "Service Level Objective";
|
|
116
|
-
|
|
117
|
-
const USER_PROPS: DatabaseCommonInteractionProps = {
|
|
118
|
-
userId: USER_ID,
|
|
70
|
+
const PROPS: DatabaseCommonInteractionProps = {
|
|
71
|
+
userId: new ObjectID("22222222-2222-4222-8222-222222222222"),
|
|
119
72
|
userType: UserType.User,
|
|
120
73
|
tenantId: PROJECT_ID,
|
|
121
74
|
};
|
|
122
75
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
query: JSONObject;
|
|
129
|
-
select: JSONObject;
|
|
130
|
-
props: JSONObject;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
interface Harness {
|
|
134
|
-
service: AuditLogService;
|
|
135
|
-
inserted: Array<AuditLog>;
|
|
136
|
-
findProject: jest.Mock;
|
|
137
|
-
findUser: jest.Mock;
|
|
138
|
-
insert: jest.SpyInstance;
|
|
139
|
-
relatedLookups: Array<RelatedLookup>;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
let project: Project;
|
|
143
|
-
let usersById: Map<string, User>;
|
|
144
|
-
let relatedRows: Map<string, Array<BaseModel>>;
|
|
145
|
-
let failRelatedLookups: boolean;
|
|
146
|
-
let harness: Harness;
|
|
147
|
-
|
|
148
|
-
function makeProject(settings: {
|
|
149
|
-
enableAuditLogs?: boolean;
|
|
150
|
-
storeSystemEventsInAuditLogs?: boolean;
|
|
151
|
-
planName?: PlanType;
|
|
152
|
-
}): Project {
|
|
153
|
-
const item: Project = new Project();
|
|
154
|
-
item._id = PROJECT_ID.toString();
|
|
155
|
-
if (settings.enableAuditLogs !== undefined) {
|
|
156
|
-
item.enableAuditLogs = settings.enableAuditLogs;
|
|
157
|
-
}
|
|
158
|
-
if (settings.storeSystemEventsInAuditLogs !== undefined) {
|
|
159
|
-
item.storeSystemEventsInAuditLogs = settings.storeSystemEventsInAuditLogs;
|
|
160
|
-
}
|
|
161
|
-
item.auditLogsRetentionInDays = 30;
|
|
162
|
-
if (settings.planName) {
|
|
163
|
-
item.planName = settings.planName;
|
|
164
|
-
}
|
|
165
|
-
return item;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
function makeUser(id: ObjectID, name: string | null, email: string): User {
|
|
169
|
-
const user: User = new User();
|
|
170
|
-
user._id = id.toString();
|
|
171
|
-
if (name) {
|
|
172
|
-
user.name = new Name(name);
|
|
173
|
-
}
|
|
174
|
-
user.email = new Email(email);
|
|
175
|
-
return user;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
function createHarness(): Harness {
|
|
179
|
-
const service: AuditLogService = new AuditLogService();
|
|
180
|
-
const inserted: Array<AuditLog> = [];
|
|
181
|
-
|
|
182
|
-
const insert: jest.SpyInstance = jest
|
|
183
|
-
.spyOn(service, "create")
|
|
184
|
-
.mockImplementation(((createBy: { data: AuditLog }) => {
|
|
185
|
-
inserted.push(createBy.data);
|
|
186
|
-
return Promise.resolve(createBy.data);
|
|
187
|
-
}) as never);
|
|
76
|
+
type CreateData = {
|
|
77
|
+
model: Monitor;
|
|
78
|
+
createdItem: Monitor;
|
|
79
|
+
props: DatabaseCommonInteractionProps;
|
|
80
|
+
};
|
|
188
81
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
82
|
+
type UpdateData = {
|
|
83
|
+
model: Monitor;
|
|
84
|
+
before: Monitor;
|
|
85
|
+
updatedFields: { name: string };
|
|
86
|
+
itemId: ObjectID;
|
|
87
|
+
props: DatabaseCommonInteractionProps;
|
|
88
|
+
};
|
|
194
89
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
const relatedLookups: Array<RelatedLookup> = [];
|
|
202
|
-
|
|
203
|
-
jest.spyOn(DatabaseService.prototype, "findBy").mockImplementation(function (
|
|
204
|
-
this: DatabaseService<BaseModel>,
|
|
205
|
-
findBy: FindBy<BaseModel>,
|
|
206
|
-
): Promise<Array<BaseModel>> {
|
|
207
|
-
const tableName: string = this.getModel().tableName!;
|
|
208
|
-
|
|
209
|
-
relatedLookups.push({
|
|
210
|
-
tableName,
|
|
211
|
-
query: findBy.query as unknown as JSONObject,
|
|
212
|
-
select: findBy.select as unknown as JSONObject,
|
|
213
|
-
props: findBy.props as unknown as JSONObject,
|
|
214
|
-
});
|
|
90
|
+
type DeleteData = {
|
|
91
|
+
model: Monitor;
|
|
92
|
+
deletedItem: Monitor;
|
|
93
|
+
itemId: ObjectID;
|
|
94
|
+
props: DatabaseCommonInteractionProps;
|
|
95
|
+
};
|
|
215
96
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
97
|
+
const makeMonitor: () => Monitor = (): Monitor => {
|
|
98
|
+
const monitor: Monitor = new Monitor();
|
|
99
|
+
monitor._id = MONITOR_ID.toString();
|
|
100
|
+
monitor.projectId = PROJECT_ID;
|
|
101
|
+
monitor.name = "Checkout API";
|
|
102
|
+
return monitor;
|
|
103
|
+
};
|
|
219
104
|
|
|
220
|
-
|
|
221
|
-
|
|
105
|
+
const createData: () => CreateData = (): CreateData => {
|
|
106
|
+
return { model: new Monitor(), createdItem: makeMonitor(), props: PROPS };
|
|
107
|
+
};
|
|
222
108
|
|
|
223
|
-
|
|
224
|
-
|
|
109
|
+
const updateData: () => UpdateData = (): UpdateData => {
|
|
110
|
+
return {
|
|
111
|
+
model: new Monitor(),
|
|
112
|
+
before: makeMonitor(),
|
|
113
|
+
updatedFields: { name: "Checkout API (primary)" },
|
|
114
|
+
itemId: MONITOR_ID,
|
|
115
|
+
props: PROPS,
|
|
116
|
+
};
|
|
117
|
+
};
|
|
225
118
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
119
|
+
const deleteData: () => DeleteData = (): DeleteData => {
|
|
120
|
+
return {
|
|
121
|
+
model: new Monitor(),
|
|
122
|
+
deletedItem: makeMonitor(),
|
|
123
|
+
itemId: MONITOR_ID,
|
|
124
|
+
props: PROPS,
|
|
231
125
|
};
|
|
126
|
+
};
|
|
232
127
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
.sort();
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
function label(id: string, name?: string): Label {
|
|
242
|
-
const item: Label = new Label();
|
|
243
|
-
item._id = id;
|
|
244
|
-
if (name) {
|
|
245
|
-
item.name = name;
|
|
246
|
-
}
|
|
247
|
-
return item;
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
function makeSlo(): ServiceLevelObjective {
|
|
251
|
-
const slo: ServiceLevelObjective = new ServiceLevelObjective();
|
|
252
|
-
slo._id = SLO_ID.toString();
|
|
253
|
-
slo.projectId = PROJECT_ID;
|
|
254
|
-
slo.name = "Checkout availability";
|
|
255
|
-
slo.targetPercentage = 99.9;
|
|
256
|
-
return slo;
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
function makeBurnRateRule(): ServiceLevelObjectiveBurnRateRule {
|
|
260
|
-
const rule: ServiceLevelObjectiveBurnRateRule =
|
|
261
|
-
new ServiceLevelObjectiveBurnRateRule();
|
|
262
|
-
rule._id = RULE_ID.toString();
|
|
263
|
-
rule.projectId = PROJECT_ID;
|
|
264
|
-
rule.serviceLevelObjectiveId = SLO_ID;
|
|
265
|
-
rule.name = "Fast burn";
|
|
266
|
-
rule.burnRateThreshold = 14.4;
|
|
267
|
-
return rule;
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
function makeMonitorRule(): ServiceLevelObjectiveMonitorRule {
|
|
271
|
-
const rule: ServiceLevelObjectiveMonitorRule =
|
|
272
|
-
new ServiceLevelObjectiveMonitorRule();
|
|
273
|
-
rule._id = RULE_ID.toString();
|
|
274
|
-
rule.projectId = PROJECT_ID;
|
|
275
|
-
rule.serviceLevelObjectiveId = SLO_ID;
|
|
276
|
-
rule.name = "Production APIs";
|
|
277
|
-
return rule;
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
function makeOwnerUser(): ServiceLevelObjectiveOwnerUser {
|
|
281
|
-
const owner: ServiceLevelObjectiveOwnerUser =
|
|
282
|
-
new ServiceLevelObjectiveOwnerUser();
|
|
283
|
-
owner._id = OWNER_ROW_ID.toString();
|
|
284
|
-
owner.projectId = PROJECT_ID;
|
|
285
|
-
owner.serviceLevelObjectiveId = SLO_ID;
|
|
286
|
-
owner.userId = OWNER_USER_ID;
|
|
287
|
-
owner.isOwnerNotified = false;
|
|
288
|
-
return owner;
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
function makeOwnerTeam(): ServiceLevelObjectiveOwnerTeam {
|
|
292
|
-
const owner: ServiceLevelObjectiveOwnerTeam =
|
|
293
|
-
new ServiceLevelObjectiveOwnerTeam();
|
|
294
|
-
owner._id = OWNER_ROW_ID.toString();
|
|
295
|
-
owner.projectId = PROJECT_ID;
|
|
296
|
-
owner.serviceLevelObjectiveId = SLO_ID;
|
|
297
|
-
owner.teamId = TEAM_ID;
|
|
298
|
-
owner.isOwnerNotified = false;
|
|
299
|
-
return owner;
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
function onlyEntry(): AuditLog {
|
|
303
|
-
expect(harness.inserted).toHaveLength(1);
|
|
304
|
-
return harness.inserted[0]!;
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
function changesOf(entry: AuditLog): Array<JSONObject> {
|
|
308
|
-
return (entry.changes || []) as Array<JSONObject>;
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
function fieldsOf(entry: AuditLog): Array<string> {
|
|
312
|
-
return changesOf(entry).map((change: JSONObject) => {
|
|
313
|
-
return String(change["field"]);
|
|
314
|
-
});
|
|
315
|
-
}
|
|
128
|
+
// The four entry points, each driven the way its caller drives it.
|
|
129
|
+
type EntryPoint = {
|
|
130
|
+
name: "recordCreate" | "recordUpdate" | "recordDelete";
|
|
131
|
+
call: (service: AuditLogService) => Promise<void>;
|
|
132
|
+
};
|
|
316
133
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
134
|
+
const RECORD_ENTRY_POINTS: Array<EntryPoint> = [
|
|
135
|
+
{
|
|
136
|
+
name: "recordCreate",
|
|
137
|
+
call: (service: AuditLogService): Promise<void> => {
|
|
138
|
+
return service.recordCreate(createData());
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
name: "recordUpdate",
|
|
143
|
+
call: (service: AuditLogService): Promise<void> => {
|
|
144
|
+
return service.recordUpdate(updateData());
|
|
145
|
+
},
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
name: "recordDelete",
|
|
149
|
+
call: (service: AuditLogService): Promise<void> => {
|
|
150
|
+
return service.recordDelete(deleteData());
|
|
151
|
+
},
|
|
152
|
+
},
|
|
153
|
+
];
|
|
154
|
+
|
|
155
|
+
let insertSpy: jest.SpyInstance;
|
|
156
|
+
let warnSpy: jest.SpyInstance;
|
|
322
157
|
|
|
323
158
|
beforeEach(() => {
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
159
|
+
setTestBillingEnabled(false);
|
|
160
|
+
uninstallEnterpriseModule();
|
|
161
|
+
|
|
162
|
+
// Nothing here may ever reach ClickHouse: a write would be a bug.
|
|
163
|
+
insertSpy = jest
|
|
164
|
+
.spyOn(AuditLogServiceInstance, "create")
|
|
165
|
+
.mockImplementation(((createBy: { data: AuditLog }) => {
|
|
166
|
+
return Promise.resolve(createBy.data);
|
|
167
|
+
}) as never);
|
|
168
|
+
|
|
169
|
+
warnSpy = jest.spyOn(logger, "warn").mockImplementation((): void => {
|
|
170
|
+
return undefined;
|
|
171
|
+
});
|
|
333
172
|
});
|
|
334
173
|
|
|
335
174
|
afterEach(() => {
|
|
175
|
+
uninstallEnterpriseModule();
|
|
176
|
+
setTestBillingEnabled(false);
|
|
336
177
|
jest.restoreAllMocks();
|
|
337
178
|
});
|
|
338
179
|
|
|
339
|
-
describe("
|
|
340
|
-
test(
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
props: USER_PROPS,
|
|
345
|
-
});
|
|
180
|
+
describe("on the Community Edition (no enterprise module)", () => {
|
|
181
|
+
test.each([false, true])(
|
|
182
|
+
"with billing=%s every record call resolves and writes nothing",
|
|
183
|
+
async (billing: boolean) => {
|
|
184
|
+
setTestBillingEnabled(billing);
|
|
346
185
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
expect(entry.projectId?.toString()).toBe(PROJECT_ID.toString());
|
|
350
|
-
expect(entry.action).toBe(AuditLogAction.Create);
|
|
351
|
-
expect(entry.resourceType).toBe(SLO_RESOURCE_TYPE);
|
|
352
|
-
expect(entry.resourceId?.toString()).toBe(SLO_ID.toString());
|
|
353
|
-
expect(entry.resourceName).toBe("Checkout availability");
|
|
354
|
-
expect(entry.userId?.toString()).toBe(USER_ID.toString());
|
|
355
|
-
expect(entry.userName).toBe("Ada Lovelace");
|
|
356
|
-
expect(entry.userEmail).toBe("ada@example.com");
|
|
357
|
-
expect(entry.userType).toBe(UserType.User);
|
|
358
|
-
expect(entry.retentionDate).toBeInstanceOf(Date);
|
|
359
|
-
expect(changeFor(entry, "targetPercentage")).toEqual({
|
|
360
|
-
field: "targetPercentage",
|
|
361
|
-
newValue: 99.9,
|
|
362
|
-
});
|
|
363
|
-
});
|
|
186
|
+
expect(EnterpriseEdition.isLoaded()).toBe(false);
|
|
364
187
|
|
|
365
|
-
|
|
366
|
-
|
|
188
|
+
for (const entryPoint of RECORD_ENTRY_POINTS) {
|
|
189
|
+
await expect(
|
|
190
|
+
entryPoint.call(AuditLogServiceInstance),
|
|
191
|
+
).resolves.toBeUndefined();
|
|
192
|
+
}
|
|
367
193
|
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
});
|
|
194
|
+
expect(insertSpy).not.toHaveBeenCalled();
|
|
195
|
+
expect(warnSpy).not.toHaveBeenCalled();
|
|
196
|
+
},
|
|
197
|
+
);
|
|
373
198
|
|
|
374
|
-
|
|
199
|
+
test("invalidating a project's settings is a harmless no-op", () => {
|
|
200
|
+
expect(() => {
|
|
201
|
+
AuditLogServiceInstance.invalidateProjectSettings(PROJECT_ID);
|
|
202
|
+
}).not.toThrow();
|
|
375
203
|
});
|
|
376
204
|
|
|
377
|
-
test("
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
205
|
+
test("a fresh instance behaves the same as the shared one", async () => {
|
|
206
|
+
const service: AuditLogService = new AuditLogService();
|
|
207
|
+
const freshInsert: jest.SpyInstance = jest
|
|
208
|
+
.spyOn(service, "create")
|
|
209
|
+
.mockImplementation(((createBy: { data: AuditLog }) => {
|
|
210
|
+
return Promise.resolve(createBy.data);
|
|
211
|
+
}) as never);
|
|
381
212
|
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
props: USER_PROPS,
|
|
386
|
-
});
|
|
213
|
+
for (const entryPoint of RECORD_ENTRY_POINTS) {
|
|
214
|
+
await entryPoint.call(service);
|
|
215
|
+
}
|
|
387
216
|
|
|
388
|
-
expect(
|
|
217
|
+
expect(freshInsert).not.toHaveBeenCalled();
|
|
389
218
|
});
|
|
219
|
+
});
|
|
390
220
|
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
model: new ServiceLevelObjective(),
|
|
394
|
-
createdItem: makeSlo(),
|
|
395
|
-
props: SYSTEM_PROPS,
|
|
396
|
-
});
|
|
397
|
-
|
|
398
|
-
expect(harness.inserted).toHaveLength(0);
|
|
399
|
-
|
|
400
|
-
project = makeProject({
|
|
401
|
-
enableAuditLogs: true,
|
|
402
|
-
storeSystemEventsInAuditLogs: true,
|
|
403
|
-
});
|
|
404
|
-
harness.service.invalidateProjectSettings(PROJECT_ID);
|
|
405
|
-
|
|
406
|
-
await harness.service.recordCreate({
|
|
407
|
-
model: new ServiceLevelObjective(),
|
|
408
|
-
createdItem: makeSlo(),
|
|
409
|
-
props: SYSTEM_PROPS,
|
|
410
|
-
});
|
|
221
|
+
describe("with the Enterprise Edition loaded", () => {
|
|
222
|
+
let recorder: MockedAuditLogRecorder;
|
|
411
223
|
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
224
|
+
beforeEach(() => {
|
|
225
|
+
recorder = createAuditLogRecorderSpy();
|
|
226
|
+
installFakeEnterpriseModule({ auditLogRecorder: recorder });
|
|
415
227
|
});
|
|
416
228
|
|
|
417
|
-
test.each([
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
planName: undefined,
|
|
423
|
-
recorded: true,
|
|
424
|
-
},
|
|
425
|
-
{
|
|
426
|
-
build: "cloud on the Enterprise plan",
|
|
427
|
-
isEnterpriseEdition: false,
|
|
428
|
-
isBillingEnabled: true,
|
|
429
|
-
planName: PlanType.Enterprise,
|
|
430
|
-
recorded: true,
|
|
431
|
-
},
|
|
432
|
-
{
|
|
433
|
-
build: "cloud on the Growth plan",
|
|
434
|
-
isEnterpriseEdition: false,
|
|
435
|
-
isBillingEnabled: true,
|
|
436
|
-
planName: PlanType.Growth,
|
|
437
|
-
recorded: false,
|
|
438
|
-
},
|
|
439
|
-
{
|
|
440
|
-
build: "the free self-hosted build",
|
|
441
|
-
isEnterpriseEdition: false,
|
|
442
|
-
isBillingEnabled: false,
|
|
443
|
-
planName: PlanType.Enterprise,
|
|
444
|
-
recorded: false,
|
|
445
|
-
},
|
|
446
|
-
])(
|
|
447
|
-
"on $build an entry is recorded: $recorded",
|
|
448
|
-
async (data: {
|
|
449
|
-
isEnterpriseEdition: boolean;
|
|
450
|
-
isBillingEnabled: boolean;
|
|
451
|
-
planName: PlanType | undefined;
|
|
452
|
-
recorded: boolean;
|
|
453
|
-
}) => {
|
|
454
|
-
environment.IsEnterpriseEdition = data.isEnterpriseEdition;
|
|
455
|
-
environment.IsBillingEnabled = data.isBillingEnabled;
|
|
456
|
-
project = makeProject({
|
|
457
|
-
enableAuditLogs: true,
|
|
458
|
-
...(data.planName ? { planName: data.planName } : {}),
|
|
459
|
-
});
|
|
229
|
+
test.each([false, true])(
|
|
230
|
+
"with billing=%s recordCreate hands the recorder the very arguments it was given",
|
|
231
|
+
async (billing: boolean) => {
|
|
232
|
+
setTestBillingEnabled(billing);
|
|
233
|
+
const data: CreateData = createData();
|
|
460
234
|
|
|
461
|
-
await
|
|
462
|
-
model: new ServiceLevelObjective(),
|
|
463
|
-
createdItem: makeSlo(),
|
|
464
|
-
props: USER_PROPS,
|
|
465
|
-
});
|
|
235
|
+
await AuditLogServiceInstance.recordCreate(data);
|
|
466
236
|
|
|
467
|
-
expect(
|
|
237
|
+
expect(recorder.recordCreate).toHaveBeenCalledTimes(1);
|
|
238
|
+
expect(recorder.recordCreate.mock.calls[0]![0]).toBe(data);
|
|
239
|
+
expect(recorder.recordUpdate).not.toHaveBeenCalled();
|
|
240
|
+
expect(recorder.recordDelete).not.toHaveBeenCalled();
|
|
468
241
|
},
|
|
469
242
|
);
|
|
470
243
|
|
|
471
|
-
test("
|
|
472
|
-
const
|
|
473
|
-
(slo as unknown as Record<string, unknown>)["projectId"] = undefined;
|
|
474
|
-
|
|
475
|
-
await harness.service.recordCreate({
|
|
476
|
-
model: new ServiceLevelObjective(),
|
|
477
|
-
createdItem: slo,
|
|
478
|
-
props: { userId: USER_ID, userType: UserType.User },
|
|
479
|
-
});
|
|
480
|
-
|
|
481
|
-
expect(harness.inserted).toHaveLength(0);
|
|
482
|
-
expect(harness.findProject).not.toHaveBeenCalled();
|
|
483
|
-
});
|
|
484
|
-
|
|
485
|
-
test("the project's settings are read once per cache period, and again after invalidation", async () => {
|
|
486
|
-
for (let i: number = 0; i < 3; i++) {
|
|
487
|
-
await harness.service.recordCreate({
|
|
488
|
-
model: new ServiceLevelObjective(),
|
|
489
|
-
createdItem: makeSlo(),
|
|
490
|
-
props: USER_PROPS,
|
|
491
|
-
});
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
expect(harness.findProject).toHaveBeenCalledTimes(1);
|
|
495
|
-
|
|
496
|
-
harness.service.invalidateProjectSettings(PROJECT_ID);
|
|
244
|
+
test("recordUpdate hands the recorder the very arguments it was given", async () => {
|
|
245
|
+
const data: UpdateData = updateData();
|
|
497
246
|
|
|
498
|
-
await
|
|
499
|
-
model: new ServiceLevelObjective(),
|
|
500
|
-
createdItem: makeSlo(),
|
|
501
|
-
props: USER_PROPS,
|
|
502
|
-
});
|
|
247
|
+
await AuditLogServiceInstance.recordUpdate(data);
|
|
503
248
|
|
|
504
|
-
expect(
|
|
249
|
+
expect(recorder.recordUpdate).toHaveBeenCalledTimes(1);
|
|
250
|
+
expect(recorder.recordUpdate.mock.calls[0]![0]).toBe(data);
|
|
251
|
+
expect(recorder.recordCreate).not.toHaveBeenCalled();
|
|
505
252
|
});
|
|
506
253
|
|
|
507
|
-
test("
|
|
508
|
-
|
|
509
|
-
return Promise.reject(new Error("clickhouse unavailable"));
|
|
510
|
-
}) as never);
|
|
254
|
+
test("recordDelete hands the recorder the very arguments it was given", async () => {
|
|
255
|
+
const data: DeleteData = deleteData();
|
|
511
256
|
|
|
512
|
-
await
|
|
513
|
-
harness.service.recordCreate({
|
|
514
|
-
model: new ServiceLevelObjective(),
|
|
515
|
-
createdItem: makeSlo(),
|
|
516
|
-
props: USER_PROPS,
|
|
517
|
-
}),
|
|
518
|
-
).resolves.toBeUndefined();
|
|
519
|
-
});
|
|
520
|
-
});
|
|
257
|
+
await AuditLogServiceInstance.recordDelete(data);
|
|
521
258
|
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
expect(
|
|
525
|
-
expect(
|
|
526
|
-
new ServiceLevelObjectiveBurnRateRule().enableAuditLogOn?.rootResource
|
|
527
|
-
?.resourceType,
|
|
528
|
-
).toBe(SLO_RESOURCE_TYPE);
|
|
259
|
+
expect(recorder.recordDelete).toHaveBeenCalledTimes(1);
|
|
260
|
+
expect(recorder.recordDelete.mock.calls[0]![0]).toBe(data);
|
|
261
|
+
expect(recorder.recordCreate).not.toHaveBeenCalled();
|
|
529
262
|
});
|
|
530
|
-
});
|
|
531
263
|
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
await harness.service.recordUpdate({
|
|
535
|
-
model: new ServiceLevelObjective(),
|
|
536
|
-
before: makeSlo(),
|
|
537
|
-
updatedFields: { targetPercentage: 99.9 },
|
|
538
|
-
itemId: SLO_ID,
|
|
539
|
-
props: USER_PROPS,
|
|
540
|
-
});
|
|
264
|
+
test("invalidateProjectSettings reaches the recorder's cache", () => {
|
|
265
|
+
AuditLogServiceInstance.invalidateProjectSettings(PROJECT_ID);
|
|
541
266
|
|
|
542
|
-
expect(
|
|
543
|
-
expect(
|
|
267
|
+
expect(recorder.invalidateProjectSettings).toHaveBeenCalledTimes(1);
|
|
268
|
+
expect(recorder.invalidateProjectSettings).toHaveBeenCalledWith(PROJECT_ID);
|
|
544
269
|
});
|
|
545
270
|
|
|
546
|
-
test("
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
});
|
|
551
|
-
|
|
552
|
-
const before: ServiceLevelObjective = makeSlo();
|
|
553
|
-
before.currentSliPercentage = 99.95;
|
|
554
|
-
before.errorBudgetRemainingPercentage = 60;
|
|
555
|
-
before.currentBurnRate = 0.4;
|
|
556
|
-
before.lastEvaluatedAt = new Date("2026-09-15T10:00:00.000Z");
|
|
557
|
-
|
|
558
|
-
await harness.service.recordUpdate({
|
|
559
|
-
model: new ServiceLevelObjective(),
|
|
560
|
-
before,
|
|
561
|
-
updatedFields: {
|
|
562
|
-
currentSliPercentage: 99.91,
|
|
563
|
-
errorBudgetRemainingPercentage: 55,
|
|
564
|
-
errorBudgetRemainingSeconds: 1200,
|
|
565
|
-
errorBudgetTotalSeconds: 2592,
|
|
566
|
-
currentBurnRate: 1.3,
|
|
567
|
-
sloStatus: "AtRisk",
|
|
568
|
-
statusChangeNotificationSentAt: new Date("2026-09-15T10:05:00.000Z"),
|
|
569
|
-
lastEvaluatedAt: new Date("2026-09-15T10:05:00.000Z"),
|
|
570
|
-
nextEvaluationAt: new Date("2026-09-15T10:10:00.000Z"),
|
|
571
|
-
lastAccumulatedBucketEndAt: new Date("2026-09-15T10:05:00.000Z"),
|
|
572
|
-
autoAddedMonitors: [{ _id: MONITOR_ID.toString() }],
|
|
573
|
-
},
|
|
574
|
-
itemId: SLO_ID,
|
|
575
|
-
props: SYSTEM_PROPS,
|
|
576
|
-
});
|
|
271
|
+
test("core itself never writes: an entry is the recorder's to write", async () => {
|
|
272
|
+
for (const entryPoint of RECORD_ENTRY_POINTS) {
|
|
273
|
+
await entryPoint.call(AuditLogServiceInstance);
|
|
274
|
+
}
|
|
577
275
|
|
|
578
|
-
expect(
|
|
579
|
-
expect(harness.findProject).not.toHaveBeenCalled();
|
|
276
|
+
expect(insertSpy).not.toHaveBeenCalled();
|
|
580
277
|
});
|
|
581
278
|
|
|
582
|
-
test("
|
|
583
|
-
|
|
584
|
-
before.currentSliPercentage = 99.95;
|
|
585
|
-
|
|
586
|
-
await harness.service.recordUpdate({
|
|
587
|
-
model: new ServiceLevelObjective(),
|
|
588
|
-
before,
|
|
589
|
-
updatedFields: {
|
|
590
|
-
targetPercentage: 99.5,
|
|
591
|
-
currentSliPercentage: 99.1,
|
|
592
|
-
lastEvaluatedAt: new Date("2026-09-15T10:05:00.000Z"),
|
|
593
|
-
},
|
|
594
|
-
itemId: SLO_ID,
|
|
595
|
-
props: USER_PROPS,
|
|
596
|
-
});
|
|
597
|
-
|
|
598
|
-
expect(changesOf(onlyEntry())).toEqual([
|
|
599
|
-
{ field: "targetPercentage", oldValue: 99.9, newValue: 99.5 },
|
|
600
|
-
]);
|
|
601
|
-
});
|
|
279
|
+
test("the call waits for the recorder, so the entry is written before the write returns", async () => {
|
|
280
|
+
let finished: boolean = false;
|
|
602
281
|
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
before: makeBurnRateRule(),
|
|
607
|
-
updatedFields: {
|
|
608
|
-
lastAlertCreatedAt: new Date("2026-09-15T10:05:00.000Z"),
|
|
609
|
-
lastAlertResolvedAt: new Date("2026-09-15T10:06:00.000Z"),
|
|
610
|
-
lastIncidentCreatedAt: new Date("2026-09-15T10:07:00.000Z"),
|
|
611
|
-
lastIncidentResolvedAt: new Date("2026-09-15T10:08:00.000Z"),
|
|
612
|
-
},
|
|
613
|
-
itemId: RULE_ID,
|
|
614
|
-
props: USER_PROPS,
|
|
282
|
+
recorder.recordCreate.mockImplementation(async (): Promise<void> => {
|
|
283
|
+
await Promise.resolve();
|
|
284
|
+
finished = true;
|
|
615
285
|
});
|
|
616
286
|
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
await harness.service.recordUpdate({
|
|
620
|
-
model: new ServiceLevelObjectiveBurnRateRule(),
|
|
621
|
-
before: makeBurnRateRule(),
|
|
622
|
-
updatedFields: { burnRateThreshold: 6 },
|
|
623
|
-
itemId: RULE_ID,
|
|
624
|
-
props: USER_PROPS,
|
|
625
|
-
});
|
|
287
|
+
await AuditLogServiceInstance.recordCreate(createData());
|
|
626
288
|
|
|
627
|
-
expect(
|
|
289
|
+
expect(finished).toBe(true);
|
|
628
290
|
});
|
|
629
291
|
|
|
630
|
-
test(
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
await harness.service.recordUpdate({
|
|
637
|
-
model: new ServiceLevelObjectiveOwnerUser(),
|
|
638
|
-
before: makeOwnerUser(),
|
|
639
|
-
updatedFields: { isOwnerNotified: true },
|
|
640
|
-
itemId: OWNER_ROW_ID,
|
|
641
|
-
props: SYSTEM_PROPS,
|
|
642
|
-
});
|
|
292
|
+
test.each(RECORD_ENTRY_POINTS)(
|
|
293
|
+
"$name never fails the audited write when the recorder rejects",
|
|
294
|
+
async (entryPoint: EntryPoint) => {
|
|
295
|
+
recorder[entryPoint.name].mockImplementation(async (): Promise<void> => {
|
|
296
|
+
throw new Error("clickhouse unavailable");
|
|
297
|
+
});
|
|
643
298
|
|
|
644
|
-
|
|
645
|
-
|
|
299
|
+
await expect(
|
|
300
|
+
entryPoint.call(AuditLogServiceInstance),
|
|
301
|
+
).resolves.toBeUndefined();
|
|
302
|
+
expect(warnSpy).toHaveBeenCalled();
|
|
303
|
+
},
|
|
304
|
+
);
|
|
646
305
|
|
|
647
|
-
test("
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
slo.currentSliPercentage = 99.2;
|
|
651
|
-
slo.nextEvaluationAt = new Date("2026-09-15T10:10:00.000Z");
|
|
652
|
-
|
|
653
|
-
await harness.service.recordDelete({
|
|
654
|
-
model: new ServiceLevelObjective(),
|
|
655
|
-
deletedItem: slo,
|
|
656
|
-
itemId: SLO_ID,
|
|
657
|
-
props: USER_PROPS,
|
|
306
|
+
test("invalidateProjectSettings never throws when the recorder does", () => {
|
|
307
|
+
recorder.invalidateProjectSettings.mockImplementation((): void => {
|
|
308
|
+
throw new Error("cache unavailable");
|
|
658
309
|
});
|
|
659
310
|
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
expect(
|
|
664
|
-
expect.arrayContaining(["name", "targetPercentage", "projectId"]),
|
|
665
|
-
);
|
|
666
|
-
expect(fieldsOf(entry)).not.toContain("currentSliPercentage");
|
|
667
|
-
expect(fieldsOf(entry)).not.toContain("nextEvaluationAt");
|
|
668
|
-
expect(fieldsOf(entry)).not.toContain("_id");
|
|
669
|
-
expect(fieldsOf(entry)).not.toContain("slug");
|
|
670
|
-
expect(changeFor(entry, "name")).toEqual({
|
|
671
|
-
field: "name",
|
|
672
|
-
oldValue: "Checkout availability",
|
|
673
|
-
});
|
|
311
|
+
expect(() => {
|
|
312
|
+
AuditLogServiceInstance.invalidateProjectSettings(PROJECT_ID);
|
|
313
|
+
}).not.toThrow();
|
|
314
|
+
expect(warnSpy).toHaveBeenCalled();
|
|
674
315
|
});
|
|
675
316
|
});
|
|
676
317
|
|
|
677
|
-
describe("
|
|
678
|
-
test("
|
|
679
|
-
await
|
|
680
|
-
model: new ServiceLevelObjective(),
|
|
681
|
-
createdItem: makeSlo(),
|
|
682
|
-
props: USER_PROPS,
|
|
683
|
-
});
|
|
318
|
+
describe("the recorder is looked up on every call", () => {
|
|
319
|
+
test("installing, then removing, the enterprise module takes effect at once", async () => {
|
|
320
|
+
await AuditLogServiceInstance.recordCreate(createData());
|
|
684
321
|
|
|
685
|
-
const
|
|
686
|
-
|
|
687
|
-
expect(entry.rootResourceId?.toString()).toBe(SLO_ID.toString());
|
|
688
|
-
});
|
|
322
|
+
const recorder: MockedAuditLogRecorder = createAuditLogRecorderSpy();
|
|
323
|
+
installFakeEnterpriseModule({ auditLogRecorder: recorder });
|
|
689
324
|
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
before._id = MONITOR_ID.toString();
|
|
693
|
-
before.projectId = PROJECT_ID;
|
|
694
|
-
before.name = "Checkout API";
|
|
695
|
-
|
|
696
|
-
await harness.service.recordUpdate({
|
|
697
|
-
model: new Monitor(),
|
|
698
|
-
before,
|
|
699
|
-
updatedFields: { name: "Checkout API (primary)" },
|
|
700
|
-
itemId: MONITOR_ID,
|
|
701
|
-
props: USER_PROPS,
|
|
702
|
-
});
|
|
325
|
+
await AuditLogServiceInstance.recordCreate(createData());
|
|
326
|
+
expect(recorder.recordCreate).toHaveBeenCalledTimes(1);
|
|
703
327
|
|
|
704
|
-
|
|
705
|
-
expect(entry.rootResourceType).toBe("Monitor");
|
|
706
|
-
expect(entry.rootResourceId?.toString()).toBe(MONITOR_ID.toString());
|
|
707
|
-
});
|
|
328
|
+
uninstallEnterpriseModule();
|
|
708
329
|
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
model: (): BaseModel => {
|
|
713
|
-
return new ServiceLevelObjectiveBurnRateRule();
|
|
714
|
-
},
|
|
715
|
-
item: (): BaseModel => {
|
|
716
|
-
return makeBurnRateRule();
|
|
717
|
-
},
|
|
718
|
-
resourceType: "SLO Burn Rate Rule",
|
|
719
|
-
resourceId: RULE_ID,
|
|
720
|
-
},
|
|
721
|
-
{
|
|
722
|
-
name: "monitor rule",
|
|
723
|
-
model: (): BaseModel => {
|
|
724
|
-
return new ServiceLevelObjectiveMonitorRule();
|
|
725
|
-
},
|
|
726
|
-
item: (): BaseModel => {
|
|
727
|
-
return makeMonitorRule();
|
|
728
|
-
},
|
|
729
|
-
resourceType: "SLO Monitor Rule",
|
|
730
|
-
resourceId: RULE_ID,
|
|
731
|
-
},
|
|
732
|
-
{
|
|
733
|
-
name: "owner user",
|
|
734
|
-
model: (): BaseModel => {
|
|
735
|
-
return new ServiceLevelObjectiveOwnerUser();
|
|
736
|
-
},
|
|
737
|
-
item: (): BaseModel => {
|
|
738
|
-
return makeOwnerUser();
|
|
739
|
-
},
|
|
740
|
-
resourceType: "Service Level Objective User Owner",
|
|
741
|
-
resourceId: OWNER_ROW_ID,
|
|
742
|
-
},
|
|
743
|
-
{
|
|
744
|
-
name: "owner team",
|
|
745
|
-
model: (): BaseModel => {
|
|
746
|
-
return new ServiceLevelObjectiveOwnerTeam();
|
|
747
|
-
},
|
|
748
|
-
item: (): BaseModel => {
|
|
749
|
-
return makeOwnerTeam();
|
|
750
|
-
},
|
|
751
|
-
resourceType: "Service Level Objective Team Owner",
|
|
752
|
-
resourceId: OWNER_ROW_ID,
|
|
753
|
-
},
|
|
754
|
-
])(
|
|
755
|
-
"an SLO $name keeps its own identity and rolls up to its SLO, on create and delete",
|
|
756
|
-
async (data: {
|
|
757
|
-
model: () => BaseModel;
|
|
758
|
-
item: () => BaseModel;
|
|
759
|
-
resourceType: string;
|
|
760
|
-
resourceId: ObjectID;
|
|
761
|
-
}) => {
|
|
762
|
-
await harness.service.recordCreate({
|
|
763
|
-
model: data.model(),
|
|
764
|
-
createdItem: data.item(),
|
|
765
|
-
props: USER_PROPS,
|
|
766
|
-
});
|
|
767
|
-
|
|
768
|
-
await harness.service.recordDelete({
|
|
769
|
-
model: data.model(),
|
|
770
|
-
deletedItem: data.item(),
|
|
771
|
-
itemId: data.resourceId,
|
|
772
|
-
props: USER_PROPS,
|
|
773
|
-
});
|
|
774
|
-
|
|
775
|
-
expect(harness.inserted).toHaveLength(2);
|
|
776
|
-
|
|
777
|
-
for (const entry of harness.inserted) {
|
|
778
|
-
expect(entry.resourceType).toBe(data.resourceType);
|
|
779
|
-
expect(entry.resourceId?.toString()).toBe(data.resourceId.toString());
|
|
780
|
-
expect(entry.rootResourceType).toBe(SLO_RESOURCE_TYPE);
|
|
781
|
-
expect(entry.rootResourceId?.toString()).toBe(SLO_ID.toString());
|
|
782
|
-
}
|
|
783
|
-
},
|
|
784
|
-
);
|
|
785
|
-
|
|
786
|
-
test("a child's update rolls up through the parent id on its before-row", async () => {
|
|
787
|
-
await harness.service.recordUpdate({
|
|
788
|
-
model: new ServiceLevelObjectiveBurnRateRule(),
|
|
789
|
-
before: makeBurnRateRule(),
|
|
790
|
-
updatedFields: { name: "Fast burn (1h)" },
|
|
791
|
-
itemId: RULE_ID,
|
|
792
|
-
props: USER_PROPS,
|
|
793
|
-
});
|
|
794
|
-
|
|
795
|
-
const entry: AuditLog = onlyEntry();
|
|
796
|
-
expect(entry.resourceName).toBe("Fast burn");
|
|
797
|
-
expect(entry.rootResourceType).toBe(SLO_RESOURCE_TYPE);
|
|
798
|
-
expect(entry.rootResourceId?.toString()).toBe(SLO_ID.toString());
|
|
330
|
+
await AuditLogServiceInstance.recordCreate(createData());
|
|
331
|
+
expect(recorder.recordCreate).toHaveBeenCalledTimes(1);
|
|
332
|
+
expect(insertSpy).not.toHaveBeenCalled();
|
|
799
333
|
});
|
|
800
334
|
|
|
801
|
-
test("
|
|
802
|
-
|
|
803
|
-
(rule as unknown as Record<string, unknown>)["serviceLevelObjectiveId"] =
|
|
804
|
-
undefined;
|
|
335
|
+
test("an enterprise module that has no recorder records nothing", async () => {
|
|
336
|
+
installFakeEnterpriseModule({ auditLogRecorder: null });
|
|
805
337
|
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
}
|
|
338
|
+
for (const entryPoint of RECORD_ENTRY_POINTS) {
|
|
339
|
+
await expect(
|
|
340
|
+
entryPoint.call(AuditLogServiceInstance),
|
|
341
|
+
).resolves.toBeUndefined();
|
|
342
|
+
}
|
|
811
343
|
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
expect(
|
|
344
|
+
expect(() => {
|
|
345
|
+
AuditLogServiceInstance.invalidateProjectSettings(PROJECT_ID);
|
|
346
|
+
}).not.toThrow();
|
|
347
|
+
expect(insertSpy).not.toHaveBeenCalled();
|
|
816
348
|
});
|
|
817
|
-
});
|
|
818
|
-
|
|
819
|
-
describe("relation values are recorded as named references", () => {
|
|
820
|
-
test("a label swap records old names from the before-row and new ones borrowed or looked up in the project", async () => {
|
|
821
|
-
relatedRows.set("Label", [label(LABEL_C, "Staging")]);
|
|
822
|
-
|
|
823
|
-
const before: ServiceLevelObjective = makeSlo();
|
|
824
|
-
before.labels = [label(LABEL_A, "Production"), label(LABEL_B, "Payments")];
|
|
825
|
-
|
|
826
|
-
await harness.service.recordUpdate({
|
|
827
|
-
model: new ServiceLevelObjective(),
|
|
828
|
-
before,
|
|
829
|
-
updatedFields: {
|
|
830
|
-
labels: [label(LABEL_C), label(LABEL_A)],
|
|
831
|
-
} as unknown as JSONObject,
|
|
832
|
-
itemId: SLO_ID,
|
|
833
|
-
props: USER_PROPS,
|
|
834
|
-
});
|
|
835
|
-
|
|
836
|
-
expect(changeFor(onlyEntry(), "labels")).toEqual({
|
|
837
|
-
field: "labels",
|
|
838
|
-
oldValue: [
|
|
839
|
-
{ _id: LABEL_A, name: "Production" },
|
|
840
|
-
{ _id: LABEL_B, name: "Payments" },
|
|
841
|
-
],
|
|
842
|
-
newValue: [
|
|
843
|
-
{ _id: LABEL_C, name: "Staging" },
|
|
844
|
-
{ _id: LABEL_A, name: "Production" },
|
|
845
|
-
],
|
|
846
|
-
});
|
|
847
349
|
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
const lookup: RelatedLookup = harness.relatedLookups[0]!;
|
|
851
|
-
expect(lookup.tableName).toBe("Label");
|
|
852
|
-
expect(idsInLookup(lookup)).toEqual([LABEL_C]);
|
|
853
|
-
expect(lookup.query["projectId"]).toEqual(PROJECT_ID);
|
|
854
|
-
expect(lookup.select).toEqual({ _id: true, name: true });
|
|
855
|
-
expect(lookup.props).toEqual({ isRoot: true, ignoreHooks: true });
|
|
856
|
-
});
|
|
350
|
+
test("an enterprise module that fails to hand over its recorder records nothing, and the write still succeeds", async () => {
|
|
351
|
+
const fake: FakeEnterpriseModule = installFakeEnterpriseModule();
|
|
857
352
|
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
await harness.service.recordUpdate({
|
|
861
|
-
model: new ServiceLevelObjective(),
|
|
862
|
-
before: Object.assign(makeSlo(), { labels: [] }),
|
|
863
|
-
updatedFields: { labels: [LABEL_C] } as unknown as JSONObject,
|
|
864
|
-
itemId: SLO_ID,
|
|
865
|
-
props: USER_PROPS,
|
|
353
|
+
jest.spyOn(fake, "getAuditLogRecorder").mockImplementation(() => {
|
|
354
|
+
throw new Error("recorder unavailable");
|
|
866
355
|
});
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
field: "labels",
|
|
870
|
-
oldValue: [],
|
|
871
|
-
newValue: [{ _id: LABEL_C }],
|
|
356
|
+
jest.spyOn(logger, "error").mockImplementation((): void => {
|
|
357
|
+
return undefined;
|
|
872
358
|
});
|
|
873
|
-
});
|
|
874
359
|
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
model: new ServiceLevelObjective(),
|
|
881
|
-
before,
|
|
882
|
-
updatedFields: {
|
|
883
|
-
labels: [{ _id: LABEL_B }, LABEL_A],
|
|
884
|
-
} as unknown as JSONObject,
|
|
885
|
-
itemId: SLO_ID,
|
|
886
|
-
props: USER_PROPS,
|
|
887
|
-
});
|
|
360
|
+
for (const entryPoint of RECORD_ENTRY_POINTS) {
|
|
361
|
+
await expect(
|
|
362
|
+
entryPoint.call(AuditLogServiceInstance),
|
|
363
|
+
).resolves.toBeUndefined();
|
|
364
|
+
}
|
|
888
365
|
|
|
889
|
-
expect(
|
|
366
|
+
expect(insertSpy).not.toHaveBeenCalled();
|
|
890
367
|
});
|
|
368
|
+
});
|
|
891
369
|
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
await harness.service.recordUpdate({
|
|
899
|
-
model: new ServiceLevelObjective(),
|
|
900
|
-
before,
|
|
901
|
-
updatedFields: {
|
|
902
|
-
labels: [label(LABEL_C), label(LABEL_A)],
|
|
903
|
-
} as unknown as JSONObject,
|
|
904
|
-
itemId: SLO_ID,
|
|
905
|
-
props: USER_PROPS,
|
|
906
|
-
});
|
|
907
|
-
|
|
908
|
-
expect(changeFor(onlyEntry(), "labels")?.["newValue"]).toEqual([
|
|
909
|
-
{ _id: LABEL_C },
|
|
910
|
-
{ _id: LABEL_A, name: "Production" },
|
|
911
|
-
]);
|
|
370
|
+
describe("what core's AuditLogService still is", () => {
|
|
371
|
+
test("the audit log's analytics service, so reads, retention and migrations stay core", () => {
|
|
372
|
+
expect(AuditLogServiceInstance).toBeInstanceOf(AuditLogService);
|
|
373
|
+
expect(typeof AuditLogServiceInstance.create).toBe("function");
|
|
374
|
+
expect(typeof AuditLogServiceInstance.findBy).toBe("function");
|
|
912
375
|
});
|
|
913
376
|
|
|
914
|
-
test("
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
makeUser(OWNER_USER_ID, null, "grace@example.com"),
|
|
377
|
+
test("it carries none of the recording logic, which is Enterprise code", () => {
|
|
378
|
+
const methods: Array<string> = Object.getOwnPropertyNames(
|
|
379
|
+
AuditLogService.prototype,
|
|
918
380
|
);
|
|
919
381
|
|
|
920
|
-
const
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
itemId: RULE_ID,
|
|
933
|
-
props: USER_PROPS,
|
|
934
|
-
});
|
|
382
|
+
for (const recordingHelper of [
|
|
383
|
+
"insert",
|
|
384
|
+
"isEligible",
|
|
385
|
+
"getProjectSettings",
|
|
386
|
+
"buildSnapshotChanges",
|
|
387
|
+
"buildUpdateDiff",
|
|
388
|
+
"getRedactedFields",
|
|
389
|
+
"addRelationNames",
|
|
390
|
+
"resolveActor",
|
|
391
|
+
]) {
|
|
392
|
+
expect(methods).not.toContain(recordingHelper);
|
|
393
|
+
}
|
|
935
394
|
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
395
|
+
for (const entryPoint of [
|
|
396
|
+
"recordCreate",
|
|
397
|
+
"recordUpdate",
|
|
398
|
+
"recordDelete",
|
|
399
|
+
"invalidateProjectSettings",
|
|
400
|
+
]) {
|
|
401
|
+
expect(methods).toContain(entryPoint);
|
|
402
|
+
}
|
|
941
403
|
});
|
|
404
|
+
});
|
|
942
405
|
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
await harness.service.recordUpdate({
|
|
960
|
-
model: new ServiceLevelObjectiveBurnRateRule(),
|
|
961
|
-
before,
|
|
962
|
-
updatedFields: { alertSeverity: next } as unknown as JSONObject,
|
|
963
|
-
itemId: RULE_ID,
|
|
964
|
-
props: USER_PROPS,
|
|
965
|
-
});
|
|
406
|
+
/*
|
|
407
|
+
* The audit trail cannot be forged. Entries are written only by the
|
|
408
|
+
* Enterprise recorder, as root. The model grants create to nobody (a project
|
|
409
|
+
* owner or admin used to be able to POST /audit-log), and AuditLogService
|
|
410
|
+
* refuses every create that is not root, master admins' included.
|
|
411
|
+
*/
|
|
412
|
+
const auditEntry: () => AuditLog = (): AuditLog => {
|
|
413
|
+
const entry: AuditLog = new AuditLog();
|
|
414
|
+
entry.projectId = PROJECT_ID;
|
|
415
|
+
entry.resourceType = "Monitor";
|
|
416
|
+
entry.resourceId = MONITOR_ID;
|
|
417
|
+
entry.action = "Delete";
|
|
418
|
+
entry.changes = [];
|
|
419
|
+
return entry;
|
|
420
|
+
};
|
|
966
421
|
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
422
|
+
const projectMemberProps: (
|
|
423
|
+
permissions: Array<Permission>,
|
|
424
|
+
) => DatabaseCommonInteractionProps = (
|
|
425
|
+
permissions: Array<Permission>,
|
|
426
|
+
): DatabaseCommonInteractionProps => {
|
|
427
|
+
return {
|
|
428
|
+
userId: PROPS.userId,
|
|
429
|
+
userType: UserType.User,
|
|
430
|
+
tenantId: PROJECT_ID,
|
|
431
|
+
userTenantAccessPermission: {
|
|
432
|
+
[PROJECT_ID.toString()]: {
|
|
433
|
+
projectId: PROJECT_ID,
|
|
434
|
+
_type: "UserTenantAccessPermission",
|
|
435
|
+
permissions: permissions.map((permission: Permission) => {
|
|
436
|
+
return {
|
|
437
|
+
_type: "UserPermission",
|
|
438
|
+
permission: permission,
|
|
439
|
+
labelIds: [],
|
|
440
|
+
isBlockPermission: false,
|
|
441
|
+
};
|
|
442
|
+
}),
|
|
443
|
+
} as UserTenantAccessPermission,
|
|
444
|
+
},
|
|
445
|
+
};
|
|
446
|
+
};
|
|
973
447
|
|
|
974
|
-
|
|
975
|
-
|
|
448
|
+
const NON_ROOT_CALLERS: Array<[string, DatabaseCommonInteractionProps]> = [
|
|
449
|
+
["a project owner", projectMemberProps([Permission.ProjectOwner])],
|
|
450
|
+
["a project admin", projectMemberProps([Permission.ProjectAdmin])],
|
|
451
|
+
[
|
|
452
|
+
"a master admin",
|
|
453
|
+
{
|
|
454
|
+
userId: PROPS.userId,
|
|
455
|
+
userType: UserType.MasterAdmin,
|
|
456
|
+
isMasterAdmin: true,
|
|
457
|
+
},
|
|
458
|
+
],
|
|
459
|
+
[
|
|
460
|
+
"an API key",
|
|
461
|
+
{
|
|
462
|
+
userType: UserType.API,
|
|
463
|
+
tenantId: PROJECT_ID,
|
|
464
|
+
userTenantAccessPermission: projectMemberProps([Permission.ProjectOwner])
|
|
465
|
+
.userTenantAccessPermission!,
|
|
466
|
+
},
|
|
467
|
+
],
|
|
468
|
+
["a caller with no identity", {}],
|
|
469
|
+
[
|
|
470
|
+
"a project admin skipping hooks",
|
|
471
|
+
{
|
|
472
|
+
...projectMemberProps([Permission.ProjectAdmin]),
|
|
473
|
+
ignoreHooks: true,
|
|
474
|
+
},
|
|
475
|
+
],
|
|
476
|
+
];
|
|
477
|
+
|
|
478
|
+
describe("nobody but root creates audit log entries", () => {
|
|
479
|
+
let analyticsCreateMany: jest.SpyInstance;
|
|
480
|
+
|
|
481
|
+
beforeEach(() => {
|
|
482
|
+
// The real create path here; the ClickHouse insert underneath is stubbed.
|
|
483
|
+
insertSpy.mockRestore();
|
|
484
|
+
analyticsCreateMany = jest
|
|
485
|
+
.spyOn(AnalyticsDatabaseService.prototype, "createMany")
|
|
486
|
+
.mockImplementation((async (createBy: { items: Array<AuditLog> }) => {
|
|
487
|
+
return createBy.items;
|
|
488
|
+
}) as never);
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
test.each(NON_ROOT_CALLERS)(
|
|
492
|
+
"%s cannot create one",
|
|
493
|
+
async (_caller: string, props: DatabaseCommonInteractionProps) => {
|
|
494
|
+
await expect(
|
|
495
|
+
AuditLogServiceInstance.create({ data: auditEntry(), props }),
|
|
496
|
+
).rejects.toThrow(
|
|
497
|
+
new NotAuthorizedException(AUDIT_LOG_CREATE_REFUSED_MESSAGE),
|
|
498
|
+
);
|
|
499
|
+
await expect(
|
|
500
|
+
AuditLogServiceInstance.createMany({ items: [auditEntry()], props }),
|
|
501
|
+
).rejects.toThrow(NotAuthorizedException);
|
|
502
|
+
|
|
503
|
+
expect(analyticsCreateMany).not.toHaveBeenCalled();
|
|
504
|
+
},
|
|
505
|
+
);
|
|
976
506
|
|
|
977
|
-
|
|
978
|
-
|
|
507
|
+
test("a root create - the recorder's - goes through", async () => {
|
|
508
|
+
const entry: AuditLog = auditEntry();
|
|
979
509
|
|
|
980
|
-
await
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
props: USER_PROPS,
|
|
984
|
-
});
|
|
510
|
+
await expect(
|
|
511
|
+
AuditLogServiceInstance.create({ data: entry, props: { isRoot: true } }),
|
|
512
|
+
).resolves.toBe(entry);
|
|
985
513
|
|
|
986
|
-
expect(
|
|
987
|
-
|
|
988
|
-
|
|
514
|
+
expect(analyticsCreateMany).toHaveBeenCalledTimes(1);
|
|
515
|
+
expect(analyticsCreateMany.mock.calls[0]![0]).toEqual({
|
|
516
|
+
items: [entry],
|
|
517
|
+
props: { isRoot: true },
|
|
989
518
|
});
|
|
990
519
|
});
|
|
991
520
|
|
|
992
|
-
test("
|
|
993
|
-
|
|
994
|
-
{
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
},
|
|
998
|
-
);
|
|
999
|
-
|
|
1000
|
-
await harness.service.recordUpdate({
|
|
1001
|
-
model: new ServiceLevelObjective(),
|
|
1002
|
-
before: Object.assign(makeSlo(), { labels: [] }),
|
|
1003
|
-
updatedFields: { labels: manyIds } as unknown as JSONObject,
|
|
1004
|
-
itemId: SLO_ID,
|
|
1005
|
-
props: USER_PROPS,
|
|
1006
|
-
});
|
|
1007
|
-
|
|
1008
|
-
const lookedUp: number = harness.relatedLookups.reduce(
|
|
1009
|
-
(total: number, lookup: RelatedLookup) => {
|
|
1010
|
-
return total + idsInLookup(lookup).length;
|
|
1011
|
-
},
|
|
1012
|
-
0,
|
|
1013
|
-
);
|
|
1014
|
-
|
|
1015
|
-
expect(lookedUp).toBe(100);
|
|
1016
|
-
expect(
|
|
1017
|
-
(changeFor(onlyEntry(), "labels")?.["newValue"] as Array<JSONObject>)
|
|
1018
|
-
.length,
|
|
1019
|
-
).toBe(150);
|
|
521
|
+
test("a root create that skips hooks goes through too", async () => {
|
|
522
|
+
await expect(
|
|
523
|
+
AuditLogServiceInstance.createMany({
|
|
524
|
+
items: [auditEntry(), auditEntry()],
|
|
525
|
+
props: { isRoot: true, ignoreHooks: true },
|
|
526
|
+
}),
|
|
527
|
+
).resolves.toHaveLength(2);
|
|
1020
528
|
});
|
|
1021
529
|
|
|
1022
|
-
test("a
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
updatedFields: {
|
|
1030
|
-
metricQueryConfig: { _id: "query-1", query: "b", name: "Errors" },
|
|
1031
|
-
},
|
|
1032
|
-
itemId: SLO_ID,
|
|
1033
|
-
props: USER_PROPS,
|
|
1034
|
-
});
|
|
1035
|
-
|
|
1036
|
-
expect(changeFor(onlyEntry(), "metricQueryConfig")).toEqual({
|
|
1037
|
-
field: "metricQueryConfig",
|
|
1038
|
-
oldValue: { _id: "query-1", query: "a" },
|
|
1039
|
-
newValue: { _id: "query-1", query: "b", name: "Errors" },
|
|
1040
|
-
});
|
|
1041
|
-
expect(harness.relatedLookups).toHaveLength(0);
|
|
530
|
+
test("a fresh instance refuses the same way", async () => {
|
|
531
|
+
await expect(
|
|
532
|
+
new AuditLogService().create({
|
|
533
|
+
data: auditEntry(),
|
|
534
|
+
props: projectMemberProps([Permission.ProjectAdmin]),
|
|
535
|
+
}),
|
|
536
|
+
).rejects.toThrow(NotAuthorizedException);
|
|
1042
537
|
});
|
|
1043
538
|
});
|
|
1044
539
|
|
|
1045
|
-
describe("
|
|
1046
|
-
test("
|
|
1047
|
-
|
|
1048
|
-
OWNER_USER_ID.toString(),
|
|
1049
|
-
makeUser(OWNER_USER_ID, "Grace Hopper", "grace@example.com"),
|
|
1050
|
-
);
|
|
1051
|
-
|
|
1052
|
-
await harness.service.recordCreate({
|
|
1053
|
-
model: new ServiceLevelObjectiveOwnerUser(),
|
|
1054
|
-
createdItem: makeOwnerUser(),
|
|
1055
|
-
props: USER_PROPS,
|
|
1056
|
-
});
|
|
540
|
+
describe("the AuditLog model grants create to nobody", () => {
|
|
541
|
+
test("no create permission at table level", () => {
|
|
542
|
+
const model: AuditLog = new AuditLog();
|
|
1057
543
|
|
|
1058
|
-
expect(
|
|
544
|
+
expect(model.getCreatePermissions()).toEqual([]);
|
|
545
|
+
expect(model.getUpdatePermissions()).toEqual([]);
|
|
546
|
+
expect(model.getDeletePermissions()).toEqual([]);
|
|
1059
547
|
});
|
|
1060
548
|
|
|
1061
|
-
test("
|
|
1062
|
-
|
|
1063
|
-
OWNER_USER_ID.toString(),
|
|
1064
|
-
makeUser(OWNER_USER_ID, null, "grace@example.com"),
|
|
1065
|
-
);
|
|
1066
|
-
|
|
1067
|
-
await harness.service.recordDelete({
|
|
1068
|
-
model: new ServiceLevelObjectiveOwnerUser(),
|
|
1069
|
-
deletedItem: makeOwnerUser(),
|
|
1070
|
-
itemId: OWNER_ROW_ID,
|
|
1071
|
-
props: USER_PROPS,
|
|
1072
|
-
});
|
|
1073
|
-
|
|
1074
|
-
expect(onlyEntry().resourceName).toBe("grace@example.com");
|
|
1075
|
-
});
|
|
549
|
+
test("no create permission on any column", () => {
|
|
550
|
+
const columns: Array<AnalyticsTableColumn> = new AuditLog().tableColumns;
|
|
1076
551
|
|
|
1077
|
-
|
|
1078
|
-
const team: Team = new Team();
|
|
1079
|
-
team._id = TEAM_ID.toString();
|
|
1080
|
-
team.projectId = PROJECT_ID;
|
|
1081
|
-
team.name = "Site Reliability";
|
|
1082
|
-
relatedRows.set("Team", [team]);
|
|
1083
|
-
|
|
1084
|
-
await harness.service.recordCreate({
|
|
1085
|
-
model: new ServiceLevelObjectiveOwnerTeam(),
|
|
1086
|
-
createdItem: makeOwnerTeam(),
|
|
1087
|
-
props: USER_PROPS,
|
|
1088
|
-
});
|
|
552
|
+
expect(columns.length).toBeGreaterThan(10);
|
|
1089
553
|
|
|
1090
|
-
|
|
554
|
+
const creatable: Array<string> = columns
|
|
555
|
+
.filter((column: AnalyticsTableColumn): boolean => {
|
|
556
|
+
return (column.accessControl?.create || []).length > 0;
|
|
557
|
+
})
|
|
558
|
+
.map((column: AnalyticsTableColumn): string => {
|
|
559
|
+
return column.key;
|
|
560
|
+
});
|
|
1091
561
|
|
|
1092
|
-
|
|
1093
|
-
expect(lookup.tableName).toBe("Team");
|
|
1094
|
-
expect(idsInLookup(lookup)).toEqual([TEAM_ID.toString()]);
|
|
1095
|
-
expect(lookup.query["projectId"]).toEqual(PROJECT_ID);
|
|
562
|
+
expect(creatable).toEqual([]);
|
|
1096
563
|
});
|
|
1097
564
|
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
const entry: AuditLog = onlyEntry();
|
|
1108
|
-
expect(entry.resourceName).toBeUndefined();
|
|
1109
|
-
expect(entry.rootResourceId?.toString()).toBe(SLO_ID.toString());
|
|
565
|
+
// Reading the trail is unchanged.
|
|
566
|
+
test("owners, admins, settings admins and audit readers can still read it", () => {
|
|
567
|
+
expect(new AuditLog().getReadPermissions()).toEqual([
|
|
568
|
+
Permission.ProjectOwner,
|
|
569
|
+
Permission.ProjectAdmin,
|
|
570
|
+
Permission.SettingsAdmin,
|
|
571
|
+
Permission.ReadAuditLog,
|
|
572
|
+
]);
|
|
1110
573
|
});
|
|
1111
574
|
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
575
|
+
/*
|
|
576
|
+
* The permission layer on its own turns a project owner or admin away,
|
|
577
|
+
* before AuditLogService is even reached.
|
|
578
|
+
*/
|
|
579
|
+
test.each([
|
|
580
|
+
["a project owner", Permission.ProjectOwner],
|
|
581
|
+
["a project admin", Permission.ProjectAdmin],
|
|
582
|
+
])(
|
|
583
|
+
"the analytics create check refuses %s",
|
|
584
|
+
(_caller: string, permission: Permission) => {
|
|
585
|
+
expect(() => {
|
|
586
|
+
ModelPermission.checkCreatePermissions(
|
|
587
|
+
AuditLog,
|
|
588
|
+
auditEntry(),
|
|
589
|
+
projectMemberProps([permission]),
|
|
590
|
+
);
|
|
591
|
+
}).toThrow(NotAuthorizedException);
|
|
592
|
+
},
|
|
593
|
+
);
|
|
1124
594
|
});
|