diff --git a/.rhcicd/clowdapp-aggregator.yaml b/.rhcicd/clowdapp-aggregator.yaml index 525661785b..851719af7b 100644 --- a/.rhcicd/clowdapp-aggregator.yaml +++ b/.rhcicd/clowdapp-aggregator.yaml @@ -16,6 +16,7 @@ objects: - notifications-backend database: sharedDbAppName: notifications-backend + featureFlags: true kafkaTopics: - topicName: platform.notifications.ingress partitions: 3 @@ -47,6 +48,8 @@ objects: env: - name: ENV_NAME value: ${ENV_NAME} + - name: NOTIFICATIONS_UNLEASH_ENABLED + value: ${NOTIFICATIONS_UNLEASH_ENABLED} - name: QUARKUS_LOG_CATEGORY__COM_REDHAT_CLOUD_NOTIFICATIONS__LEVEL value: ${NOTIFICATIONS_LOG_LEVEL} - name: QUARKUS_LOG_CLOUDWATCH_ENABLED @@ -59,6 +62,8 @@ objects: value: ${SENTRY_DSN}${ENV_NAME} - name: QUARKUS_LOG_SENTRY_ENVIRONMENT value: ${ENV_NAME} + - name: QUARKUS_UNLEASH_ACTIVE + value: ${NOTIFICATIONS_UNLEASH_ENABLED} - name: PROMETHEUS_PUSHGATEWAY_URL value: ${PROMETHEUS_PUSHGATEWAY} - name: NOTIFICATIONS_BUNDLE_LEVEL_DIGEST_ENABLED @@ -94,6 +99,8 @@ parameters: - name: NOTIFICATIONS_LOG_LEVEL description: Log level for com.redhat.cloud.notifications value: INFO +- name: NOTIFICATIONS_UNLEASH_ENABLED + value: "false" - name: SENTRY_DSN description: The DSN to push data to Sentry — i.e. https://public_key@host/project_id?environment= - name: SENTRY_ENABLED diff --git a/.rhcicd/clowdapp-backend.yaml b/.rhcicd/clowdapp-backend.yaml index 3bc0f50f5a..05297a6404 100644 --- a/.rhcicd/clowdapp-backend.yaml +++ b/.rhcicd/clowdapp-backend.yaml @@ -20,6 +20,7 @@ objects: database: name: notifications-backend version: 15 + featureFlags: true testing: iqePlugin: notifications jobs: @@ -118,8 +119,12 @@ objects: name: notifications-ephemeral-data key: ephemeral_data.json optional: true + - name: NOTIFICATIONS_UNLEASH_ENABLED + value: ${NOTIFICATIONS_UNLEASH_ENABLED} - name: QUARKUS_REST_CLIENT_RBAC_AUTHENTICATION_READ_TIMEOUT value: ${RBAC_AUTHENTICATION_READ_TIMEOUT} + - name: QUARKUS_UNLEASH_ACTIVE + value: ${NOTIFICATIONS_UNLEASH_ENABLED} - name: INTERNAL_RBAC_ENABLED value: ${INTERNAL_RBAC_ENABLED} - name: INTERNAL_ADMIN_ROLE @@ -226,6 +231,8 @@ parameters: - name: NOTIFICATIONS_EMAILS_ONLY_MODE_ENABLED description: When this is true, all integration types except emails are disabled value: "true" +- name: NOTIFICATIONS_UNLEASH_ENABLED + value: "false" - name: QUARKUS_LOG_CLOUDWATCH_API_CALL_TIMEOUT description: Amount of time to allow the CloudWatch client to complete the execution of an API call expressed with the ISO-8601 duration format PnDTnHnMn.nS. value: PT30S diff --git a/.rhcicd/clowdapp-connector-drawer.yaml b/.rhcicd/clowdapp-connector-drawer.yaml index a004379bec..f01a3eb486 100644 --- a/.rhcicd/clowdapp-connector-drawer.yaml +++ b/.rhcicd/clowdapp-connector-drawer.yaml @@ -12,6 +12,7 @@ objects: dependencies: - notifications-recipients-resolver envName: ${ENV_NAME} + featureFlags: true kafkaTopics: - topicName: platform.notifications.tocamel partitions: 3 @@ -73,6 +74,8 @@ objects: value: ${NOTIFICATIONS_CONNECTOR_SEDA_CONCURRENT_CONSUMERS} - name: NOTIFICATIONS_CONNECTOR_SEDA_QUEUE_SIZE value: ${NOTIFICATIONS_CONNECTOR_SEDA_QUEUE_SIZE} + - name: NOTIFICATIONS_UNLEASH_ENABLED + value: ${NOTIFICATIONS_UNLEASH_ENABLED} - name: QUARKUS_HTTP_PORT value: ${QUARKUS_HTTP_PORT} - name: QUARKUS_LOG_CATEGORY__COM_REDHAT_CLOUD_NOTIFICATIONS__LEVEL @@ -99,6 +102,8 @@ objects: value: ${SENTRY_DSN}${ENV_NAME} - name: QUARKUS_LOG_SENTRY_ENVIRONMENT value: ${ENV_NAME} + - name: QUARKUS_UNLEASH_ACTIVE + value: ${NOTIFICATIONS_UNLEASH_ENABLED} parameters: - name: CPU_LIMIT description: CPU limit @@ -152,6 +157,8 @@ parameters: - name: NOTIFICATIONS_LOG_LEVEL description: Log level of Notifications value: INFO +- name: NOTIFICATIONS_UNLEASH_ENABLED + value: "false" - name: QUARKUS_HTTP_PORT description: Quarkus HTTP server port, defaulting to the default Clowder private port value: "9000" diff --git a/.rhcicd/clowdapp-connector-email.yaml b/.rhcicd/clowdapp-connector-email.yaml index 974506d8df..ad3ef2a434 100644 --- a/.rhcicd/clowdapp-connector-email.yaml +++ b/.rhcicd/clowdapp-connector-email.yaml @@ -12,6 +12,7 @@ objects: dependencies: - notifications-recipients-resolver envName: ${ENV_NAME} + featureFlags: true kafkaTopics: - topicName: platform.notifications.tocamel partitions: 3 @@ -97,6 +98,8 @@ objects: value: ${NOTIFICATIONS_CONNECTOR_MAX_RECIPIENTS_PER_EMAIL} - name: NOTIFICATIONS_EMAILS_INTERNAL_ONLY_ENABLED value: ${NOTIFICATIONS_EMAILS_INTERNAL_ONLY_ENABLED} + - name: NOTIFICATIONS_UNLEASH_ENABLED + value: ${NOTIFICATIONS_UNLEASH_ENABLED} - name: QUARKUS_HTTP_PORT value: ${QUARKUS_HTTP_PORT} - name: QUARKUS_LOG_CATEGORY__COM_REDHAT_CLOUD_NOTIFICATIONS__LEVEL @@ -123,6 +126,8 @@ objects: value: ${SENTRY_DSN}${ENV_NAME} - name: QUARKUS_LOG_SENTRY_ENVIRONMENT value: ${ENV_NAME} + - name: QUARKUS_UNLEASH_ACTIVE + value: ${NOTIFICATIONS_UNLEASH_ENABLED} parameters: - name: BACKOFFICE_CLIENT_ENV description: Back-office client environment @@ -205,6 +210,8 @@ parameters: value: "50" - name: NOTIFICATIONS_EMAILS_INTERNAL_ONLY_ENABLED value: "false" +- name: NOTIFICATIONS_UNLEASH_ENABLED + value: "false" - name: QUARKUS_HTTP_PORT description: Quarkus HTTP server port, defaulting to the default Clowder private port value: "9000" diff --git a/.rhcicd/clowdapp-connector-google-chat.yaml b/.rhcicd/clowdapp-connector-google-chat.yaml index 5c788705bb..3fb7464fd8 100644 --- a/.rhcicd/clowdapp-connector-google-chat.yaml +++ b/.rhcicd/clowdapp-connector-google-chat.yaml @@ -10,6 +10,7 @@ objects: name: notifications-connector-google-chat spec: envName: ${ENV_NAME} + featureFlags: true kafkaTopics: - topicName: platform.notifications.tocamel partitions: 3 @@ -79,6 +80,8 @@ objects: value: ${NOTIFICATIONS_CONNECTOR_SEDA_QUEUE_SIZE} - name: NOTIFICATIONS_CONNECTOR_HTTP_DISABLE_FAULTY_ENDPOINTS value: ${NOTIFICATIONS_CONNECTOR_HTTP_DISABLE_FAULTY_ENDPOINTS} + - name: NOTIFICATIONS_UNLEASH_ENABLED + value: ${NOTIFICATIONS_UNLEASH_ENABLED} - name: QUARKUS_HTTP_PORT value: ${QUARKUS_HTTP_PORT} - name: QUARKUS_LOG_CATEGORY__COM_REDHAT_CLOUD_NOTIFICATIONS__LEVEL @@ -105,6 +108,8 @@ objects: value: ${SENTRY_DSN}${ENV_NAME} - name: QUARKUS_LOG_SENTRY_ENVIRONMENT value: ${ENV_NAME} + - name: QUARKUS_UNLEASH_ACTIVE + value: ${NOTIFICATIONS_UNLEASH_ENABLED} parameters: - name: CPU_LIMIT description: CPU limit @@ -170,6 +175,8 @@ parameters: - name: NOTIFICATIONS_CONNECTOR_HTTP_DISABLE_FAULTY_ENDPOINTS description: Add flags on connector response to let engine disable endpoint regarding HTTP error type value: "true" +- name: NOTIFICATIONS_UNLEASH_ENABLED + value: "false" - name: QUARKUS_HTTP_PORT description: Quarkus HTTP server port, defaulting to the default Clowder private port value: "9000" diff --git a/.rhcicd/clowdapp-connector-microsoft-teams.yaml b/.rhcicd/clowdapp-connector-microsoft-teams.yaml index 4fc886a3d3..99bce42720 100644 --- a/.rhcicd/clowdapp-connector-microsoft-teams.yaml +++ b/.rhcicd/clowdapp-connector-microsoft-teams.yaml @@ -10,6 +10,7 @@ objects: name: notifications-connector-microsoft-teams spec: envName: ${ENV_NAME} + featureFlags: true kafkaTopics: - topicName: platform.notifications.tocamel partitions: 3 @@ -79,6 +80,8 @@ objects: value: ${NOTIFICATIONS_CONNECTOR_SEDA_QUEUE_SIZE} - name: NOTIFICATIONS_CONNECTOR_HTTP_DISABLE_FAULTY_ENDPOINTS value: ${NOTIFICATIONS_CONNECTOR_HTTP_DISABLE_FAULTY_ENDPOINTS} + - name: NOTIFICATIONS_UNLEASH_ENABLED + value: ${NOTIFICATIONS_UNLEASH_ENABLED} - name: QUARKUS_HTTP_PORT value: ${QUARKUS_HTTP_PORT} - name: QUARKUS_LOG_CATEGORY__COM_REDHAT_CLOUD_NOTIFICATIONS__LEVEL @@ -105,6 +108,8 @@ objects: value: ${SENTRY_DSN}${ENV_NAME} - name: QUARKUS_LOG_SENTRY_ENVIRONMENT value: ${ENV_NAME} + - name: QUARKUS_UNLEASH_ACTIVE + value: ${NOTIFICATIONS_UNLEASH_ENABLED} parameters: - name: CPU_LIMIT description: CPU limit @@ -170,6 +175,8 @@ parameters: - name: NOTIFICATIONS_CONNECTOR_HTTP_DISABLE_FAULTY_ENDPOINTS description: Add flags on connector response to let engine disable endpoint regarding HTTP error type value: "true" +- name: NOTIFICATIONS_UNLEASH_ENABLED + value: "false" - name: QUARKUS_HTTP_PORT description: Quarkus HTTP server port, defaulting to the default Clowder private port value: "9000" diff --git a/.rhcicd/clowdapp-connector-servicenow.yaml b/.rhcicd/clowdapp-connector-servicenow.yaml index 8badb429a0..d42aaf3784 100644 --- a/.rhcicd/clowdapp-connector-servicenow.yaml +++ b/.rhcicd/clowdapp-connector-servicenow.yaml @@ -18,6 +18,7 @@ objects: - drift - notifications-backend - policies-engine + featureFlags: true kafkaTopics: - topicName: platform.notifications.tocamel partitions: 3 @@ -92,6 +93,8 @@ objects: value: ${NOTIFICATIONS_CONNECTOR_SEDA_QUEUE_SIZE} - name: NOTIFICATIONS_CONNECTOR_HTTP_DISABLE_FAULTY_ENDPOINTS value: ${NOTIFICATIONS_CONNECTOR_HTTP_DISABLE_FAULTY_ENDPOINTS} + - name: NOTIFICATIONS_UNLEASH_ENABLED + value: ${NOTIFICATIONS_UNLEASH_ENABLED} - name: QUARKUS_HTTP_PORT value: ${QUARKUS_HTTP_PORT} - name: QUARKUS_LOG_CATEGORY__COM_REDHAT_CLOUD_NOTIFICATIONS__LEVEL @@ -118,6 +121,8 @@ objects: value: ${SENTRY_DSN}${ENV_NAME} - name: QUARKUS_LOG_SENTRY_ENVIRONMENT value: ${ENV_NAME} + - name: QUARKUS_UNLEASH_ACTIVE + value: ${NOTIFICATIONS_UNLEASH_ENABLED} parameters: - name: CPU_LIMIT description: CPU limit @@ -183,6 +188,8 @@ parameters: - name: NOTIFICATIONS_CONNECTOR_HTTP_DISABLE_FAULTY_ENDPOINTS description: Add flags on connector response to let engine disable endpoint regarding HTTP error type value: "true" +- name: NOTIFICATIONS_UNLEASH_ENABLED + value: "false" - name: QUARKUS_HTTP_PORT description: Quarkus HTTP server port, defaulting to the default Clowder private port value: "9000" diff --git a/.rhcicd/clowdapp-connector-slack.yaml b/.rhcicd/clowdapp-connector-slack.yaml index d66c09f327..8be2ff35f2 100644 --- a/.rhcicd/clowdapp-connector-slack.yaml +++ b/.rhcicd/clowdapp-connector-slack.yaml @@ -10,6 +10,7 @@ objects: name: notifications-connector-slack spec: envName: ${ENV_NAME} + featureFlags: true kafkaTopics: - topicName: platform.notifications.tocamel partitions: 3 @@ -73,6 +74,8 @@ objects: value: ${NOTIFICATIONS_CONNECTOR_SEDA_CONCURRENT_CONSUMERS} - name: NOTIFICATIONS_CONNECTOR_SEDA_QUEUE_SIZE value: ${NOTIFICATIONS_CONNECTOR_SEDA_QUEUE_SIZE} + - name: NOTIFICATIONS_UNLEASH_ENABLED + value: ${NOTIFICATIONS_UNLEASH_ENABLED} - name: QUARKUS_HTTP_PORT value: ${QUARKUS_HTTP_PORT} - name: QUARKUS_LOG_CATEGORY__COM_REDHAT_CLOUD_NOTIFICATIONS__LEVEL @@ -99,6 +102,8 @@ objects: value: ${SENTRY_DSN}${ENV_NAME} - name: QUARKUS_LOG_SENTRY_ENVIRONMENT value: ${ENV_NAME} + - name: QUARKUS_UNLEASH_ACTIVE + value: ${NOTIFICATIONS_UNLEASH_ENABLED} parameters: - name: CPU_LIMIT description: CPU limit @@ -155,6 +160,8 @@ parameters: - name: NOTIFICATIONS_LOG_LEVEL description: Log level of Notifications value: INFO +- name: NOTIFICATIONS_UNLEASH_ENABLED + value: "false" - name: QUARKUS_HTTP_PORT description: Quarkus HTTP server port, defaulting to the default Clowder private port value: "9000" diff --git a/.rhcicd/clowdapp-connector-splunk.yaml b/.rhcicd/clowdapp-connector-splunk.yaml index 7ad5255f56..799e4f5b87 100644 --- a/.rhcicd/clowdapp-connector-splunk.yaml +++ b/.rhcicd/clowdapp-connector-splunk.yaml @@ -18,6 +18,7 @@ objects: - drift - notifications-backend - policies-engine + featureFlags: true kafkaTopics: - topicName: platform.notifications.tocamel partitions: 3 @@ -92,6 +93,8 @@ objects: value: ${NOTIFICATIONS_CONNECTOR_SEDA_QUEUE_SIZE} - name: NOTIFICATIONS_CONNECTOR_HTTP_DISABLE_FAULTY_ENDPOINTS value: ${NOTIFICATIONS_CONNECTOR_HTTP_DISABLE_FAULTY_ENDPOINTS} + - name: NOTIFICATIONS_UNLEASH_ENABLED + value: ${NOTIFICATIONS_UNLEASH_ENABLED} - name: QUARKUS_HTTP_PORT value: ${QUARKUS_HTTP_PORT} - name: QUARKUS_LOG_CATEGORY__COM_REDHAT_CLOUD_NOTIFICATIONS__LEVEL @@ -118,6 +121,8 @@ objects: value: ${SENTRY_DSN}${ENV_NAME} - name: QUARKUS_LOG_SENTRY_ENVIRONMENT value: ${ENV_NAME} + - name: QUARKUS_UNLEASH_ACTIVE + value: ${NOTIFICATIONS_UNLEASH_ENABLED} parameters: - name: CPU_LIMIT description: CPU limit @@ -183,6 +188,8 @@ parameters: - name: NOTIFICATIONS_CONNECTOR_HTTP_DISABLE_FAULTY_ENDPOINTS description: Add flags on connector response to let engine disable endpoint regarding HTTP error type value: "true" +- name: NOTIFICATIONS_UNLEASH_ENABLED + value: "false" - name: QUARKUS_HTTP_PORT description: Quarkus HTTP server port, defaulting to the default Clowder private port value: "9000" diff --git a/.rhcicd/clowdapp-connector-webhook.yaml b/.rhcicd/clowdapp-connector-webhook.yaml index 2e38d3a7ba..a5b3367718 100644 --- a/.rhcicd/clowdapp-connector-webhook.yaml +++ b/.rhcicd/clowdapp-connector-webhook.yaml @@ -16,6 +16,7 @@ objects: - sources-api optionalDependencies: - notifications-backend + featureFlags: true kafkaTopics: - topicName: platform.notifications.tocamel partitions: 3 @@ -94,6 +95,8 @@ objects: value: ${NOTIFICATIONS_CONNECTOR_SEDA_QUEUE_SIZE} - name: NOTIFICATIONS_CONNECTOR_HTTP_DISABLE_FAULTY_ENDPOINTS value: ${NOTIFICATIONS_CONNECTOR_HTTP_DISABLE_FAULTY_ENDPOINTS} + - name: NOTIFICATIONS_UNLEASH_ENABLED + value: ${NOTIFICATIONS_UNLEASH_ENABLED} - name: QUARKUS_HTTP_PORT value: ${QUARKUS_HTTP_PORT} - name: QUARKUS_LOG_CATEGORY__COM_REDHAT_CLOUD_NOTIFICATIONS__LEVEL @@ -120,6 +123,8 @@ objects: value: ${SENTRY_DSN}${ENV_NAME} - name: QUARKUS_LOG_SENTRY_ENVIRONMENT value: ${ENV_NAME} + - name: QUARKUS_UNLEASH_ACTIVE + value: ${NOTIFICATIONS_UNLEASH_ENABLED} parameters: - name: CPU_LIMIT description: CPU limit @@ -191,6 +196,8 @@ parameters: - name: NOTIFICATIONS_CONNECTOR_HTTP_DISABLE_FAULTY_ENDPOINTS description: Add flags on connector response to let engine disable endpoint regarding HTTP error type value: "true" +- name: NOTIFICATIONS_UNLEASH_ENABLED + value: "false" - name: QUARKUS_HTTP_PORT description: Quarkus HTTP server port, defaulting to the default Clowder private port value: "9000" diff --git a/.rhcicd/clowdapp-recipients-resolver.yaml b/.rhcicd/clowdapp-recipients-resolver.yaml index ae4e25b721..ea3df97aa6 100644 --- a/.rhcicd/clowdapp-recipients-resolver.yaml +++ b/.rhcicd/clowdapp-recipients-resolver.yaml @@ -14,6 +14,7 @@ objects: envName: ${ENV_NAME} dependencies: - rbac + featureFlags: true deployments: - name: service minReplicas: ${{MIN_REPLICAS}} @@ -73,6 +74,8 @@ objects: value: ${NOTIFICATIONS_RECIPIENTS_RESOLVER_FETCH_USERS_MBOP_ENABLED} - name: NOTIFICATIONS_RECIPIENTS_RESOLVER_FETCH_USERS_RBAC_ENABLED value: ${NOTIFICATIONS_RECIPIENTS_RESOLVER_FETCH_USERS_RBAC_ENABLED} + - name: NOTIFICATIONS_UNLEASH_ENABLED + value: ${NOTIFICATIONS_UNLEASH_ENABLED} - name: QUARKUS_CACHE_CAFFEINE_RBAC_RECIPIENT_USERS_PROVIDER_GET_USERS_EXPIRE_AFTER_WRITE value: ${RBAC_USERS_RETENTION_DELAY} - name: QUARKUS_CACHE_CAFFEINE_RBAC_RECIPIENT_USERS_PROVIDER_GET_GROUP_USERS_EXPIRE_AFTER_WRITE @@ -123,6 +126,8 @@ objects: value: ${IT_S2S_READ_TIMEOUT} - name: QUARKUS_REST_CLIENT_RBAC_S2S_READ_TIMEOUT value: ${RBAC_S2S_READ_TIMEOUT} + - name: QUARKUS_UNLEASH_ACTIVE + value: ${NOTIFICATIONS_UNLEASH_ENABLED} - name: RBAC_SERVICE_TO_SERVICE_APPLICATION value: ${RBAC_SERVICE_TO_SERVICE_APP} - name: RBAC_SERVICE_TO_SERVICE_SECRET_MAP @@ -176,6 +181,8 @@ parameters: - name: NOTIFICATIONS_RECIPIENTS_RESOLVER_FETCH_USERS_RBAC_ENABLED description: Users from an organization will be retrieved from RBAC if true value: "true" +- name: NOTIFICATIONS_UNLEASH_ENABLED + value: "false" - name: QUARKUS_LOG_CATEGORY__ORG_JBOSS_RESTEASY_REACTIVE_CLIENT_LOGGING__LEVEL description: When QUARKUS_REST_CLIENT_LOGGING_SCOPE is set to 'request-response', this logger level needs to be set to DEBUG value: INFO diff --git a/aggregator/pom.xml b/aggregator/pom.xml index 3df63d9ee4..8b5d5f3be1 100644 --- a/aggregator/pom.xml +++ b/aggregator/pom.xml @@ -61,6 +61,11 @@ quarkus-logging-cloudwatch ${quarkus-logging-cloudwatch.version} + + io.quarkiverse.unleash + quarkus-unleash + ${quarkus-unleash.version} + diff --git a/aggregator/src/main/java/com/redhat/cloud/notifications/DailyEmailAggregationJob.java b/aggregator/src/main/java/com/redhat/cloud/notifications/DailyEmailAggregationJob.java index e036164b78..de45b20f4b 100644 --- a/aggregator/src/main/java/com/redhat/cloud/notifications/DailyEmailAggregationJob.java +++ b/aggregator/src/main/java/com/redhat/cloud/notifications/DailyEmailAggregationJob.java @@ -1,6 +1,6 @@ package com.redhat.cloud.notifications; -import com.redhat.cloud.notifications.config.FeatureFlipper; +import com.redhat.cloud.notifications.config.AggregatorConfig; import com.redhat.cloud.notifications.db.AggregationOrgConfigRepository; import com.redhat.cloud.notifications.db.EmailAggregationRepository; import com.redhat.cloud.notifications.ingress.Action; @@ -44,7 +44,7 @@ public class DailyEmailAggregationJob { public static final String EVENT_TYPE_NAME = "aggregation"; @Inject - FeatureFlipper featureFlipper; + AggregatorConfig aggregatorConfig; @Inject EmailAggregationRepository emailAggregationResources; @@ -80,7 +80,7 @@ public void processDailyEmail() { aggregationOrgConfigRepository.createMissingDefaultConfiguration(defaultDailyDigestTime); List aggregationCommands = processAggregateEmailsWithOrgPref(now, registry); Log.infof("found %s commands", aggregationCommands.size()); - if (!featureFlipper.isSingleDailyDigestEnabled()) { + if (!aggregatorConfig.isSingleDailyDigestEnabled()) { aggregationCommands.stream().forEach(aggregationCommand -> sendIt(List.of(aggregationCommand))); } else { aggregationCommands.stream().collect(Collectors.groupingBy(AggregationCommand::getOrgId)) @@ -147,7 +147,7 @@ private void sendIt(List aggregationCommands) { .withTimestamp(LocalDateTime.now(UTC)) .withEvents(eventList) .withContext(new Context.ContextBuilder() - .withAdditionalProperty("single_daily_digest_enabled", featureFlipper.isSingleDailyDigestEnabled()) + .withAdditionalProperty("single_daily_digest_enabled", aggregatorConfig.isSingleDailyDigestEnabled()) .build()) .build(); diff --git a/aggregator/src/main/java/com/redhat/cloud/notifications/config/AggregatorConfig.java b/aggregator/src/main/java/com/redhat/cloud/notifications/config/AggregatorConfig.java new file mode 100644 index 0000000000..a2fc4d46a3 --- /dev/null +++ b/aggregator/src/main/java/com/redhat/cloud/notifications/config/AggregatorConfig.java @@ -0,0 +1,61 @@ +package com.redhat.cloud.notifications.config; + +import io.getunleash.Unleash; +import io.quarkus.logging.Log; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.event.Observes; +import jakarta.enterprise.event.Startup; +import jakarta.inject.Inject; +import org.eclipse.microprofile.config.inject.ConfigProperty; + +import java.util.Map; +import java.util.TreeMap; + +@ApplicationScoped +public class AggregatorConfig { + + /* + * Env vars configuration + */ + private static final String UNLEASH = "notifications.unleash.enabled"; + + /* + * Unleash configuration + */ + private static final String SINGLE_DAILY_DIGEST = toggleName("single-daily-digest"); + + private static String toggleName(String feature) { + return String.format("notifications-aggregator.%s.enabled", feature); + } + + @ConfigProperty(name = UNLEASH, defaultValue = "false") + @Deprecated(forRemoval = true, since = "To be removed when we're done migrating to Unleash in all environments") + boolean unleashEnabled; + + @ConfigProperty(name = "notifications.bundle.level.digest.enabled", defaultValue = "false") + @Deprecated(forRemoval = true, since = "To be removed when we're done migrating to Unleash in all environments") + boolean singleDailyDigestEnabled; + + @Inject + Unleash unleash; + + void logConfigAtStartup(@Observes Startup event) { + + Map config = new TreeMap<>(); + config.put(SINGLE_DAILY_DIGEST, isSingleDailyDigestEnabled()); + config.put(UNLEASH, unleashEnabled); + + Log.info("=== Startup configuration ==="); + config.forEach((key, value) -> { + Log.infof("%s=%s", key, value); + }); + } + + public boolean isSingleDailyDigestEnabled() { + if (unleashEnabled) { + return unleash.isEnabled(SINGLE_DAILY_DIGEST, false); + } else { + return singleDailyDigestEnabled; + } + } +} diff --git a/aggregator/src/main/java/com/redhat/cloud/notifications/config/FeatureFlipper.java b/aggregator/src/main/java/com/redhat/cloud/notifications/config/FeatureFlipper.java deleted file mode 100644 index 7b39d702ed..0000000000 --- a/aggregator/src/main/java/com/redhat/cloud/notifications/config/FeatureFlipper.java +++ /dev/null @@ -1,69 +0,0 @@ -package com.redhat.cloud.notifications.config; - -import io.quarkus.logging.Log; -import io.quarkus.runtime.StartupEvent; -import io.quarkus.runtime.configuration.ProfileManager; -import jakarta.enterprise.context.ApplicationScoped; -import jakarta.enterprise.event.Observes; -import org.eclipse.microprofile.config.inject.ConfigProperty; - -import static io.quarkus.runtime.LaunchMode.TEST; - -/** - *

- * This class centralizes all configuration values used to enable or disable a feature. - *

- *

- * Any config value used to flip a temporary or permanent feature can be added that way: - *

- * @ApplicationScoped
- * public class FeatureFlipper {
- *
- *     @ConfigProperty(name = "amazing-feature.enabled", defaultValue = "false")
- *     boolean amazingFeatureEnabled;
- *
- *     public boolean isAmazingFeatureEnabled() {
- *         return amazingFeatureEnabled;
- *     }
- *
- *     public void setAmazingFeatureEnabled(boolean amazingFeatureEnabled) {
- *         // Add this if the config value should only be overridden in TEST launch mode.
- *         checkTestLaunchMode();
- *         this.amazingFeatureEnabled = amazingFeatureEnabled;
- *     }
- * }
- * 
- *

- */ -@ApplicationScoped -public class FeatureFlipper { - - @ConfigProperty(name = "notifications.bundle.level.digest.enabled", defaultValue = "false") - boolean singleDailyDigestEnabled; - - void logFeaturesStatusAtStartup(@Observes StartupEvent event) { - Log.infof("=== %s startup status ===", FeatureFlipper.class.getSimpleName()); - Log.infof("The daily digest at bundle level is %s", singleDailyDigestEnabled ? "enabled" : "disabled"); - - } - - public boolean isSingleDailyDigestEnabled() { - return singleDailyDigestEnabled; - } - - public void setSingleDailyDigestEnabled(boolean singleDailyDigestEnabled) { - checkTestLaunchMode(); - this.singleDailyDigestEnabled = singleDailyDigestEnabled; - } - - /** - * This method throws an {@link IllegalStateException} if it is invoked with a launch mode different from - * {@link io.quarkus.runtime.LaunchMode#TEST TEST}. It should be added to methods that allow overriding a - * config value from tests only, preventing doing so from runtime code. - */ - private static void checkTestLaunchMode() { - if (ProfileManager.getLaunchMode() != TEST) { - throw new IllegalStateException("Illegal config value override detected"); - } - } -} diff --git a/aggregator/src/main/resources/application.properties b/aggregator/src/main/resources/application.properties index 9da22f374c..b0ff72a250 100644 --- a/aggregator/src/main/resources/application.properties +++ b/aggregator/src/main/resources/application.properties @@ -38,3 +38,7 @@ quarkus.log.cloudwatch.access-key-id=placeholder quarkus.log.cloudwatch.access-key-secret=placeholder %test.quarkus.flyway.migrate-at-start=true + +quarkus.unleash.active=false +quarkus.unleash.synchronous-fetch-on-initialisation=true +quarkus.unleash.url=http://localhost:4242 diff --git a/backend/src/main/java/com/redhat/cloud/notifications/config/BackendConfig.java b/backend/src/main/java/com/redhat/cloud/notifications/config/BackendConfig.java new file mode 100644 index 0000000000..99693e27b0 --- /dev/null +++ b/backend/src/main/java/com/redhat/cloud/notifications/config/BackendConfig.java @@ -0,0 +1,133 @@ +package com.redhat.cloud.notifications.config; + +import io.getunleash.Unleash; +import io.quarkus.logging.Log; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.event.Observes; +import jakarta.enterprise.event.Startup; +import jakarta.inject.Inject; +import org.eclipse.microprofile.config.inject.ConfigProperty; + +import java.util.Map; +import java.util.TreeMap; + +@ApplicationScoped +public class BackendConfig { + + /* + * Env vars configuration + */ + private static final String DEFAULT_TEMPLATE = "notifications.use-default-template"; + private static final String EMAILS_ONLY_MODE = "notifications.emails-only-mode.enabled"; + private static final String INSTANT_EMAILS = "notifications.instant-emails.enabled"; + private static final String UNLEASH = "notifications.unleash.enabled"; + + /* + * Unleash configuration + */ + private static final String DRAWER = toggleName("drawer"); + private static final String FORBID_SLACK_CHANNEL_USAGE = toggleName("forbid-slack-channel-usage"); + private static final String UNIQUE_BG_NAME = toggleName("unique-bg-name"); + private static final String UNIQUE_INTEGRATION_NAME = toggleName("unique-integration-name"); + + private static String toggleName(String feature) { + return String.format("notifications-backend.%s.enabled", feature); + } + + @ConfigProperty(name = UNLEASH, defaultValue = "false") + @Deprecated(forRemoval = true, since = "To be removed when we're done migrating to Unleash in all environments") + boolean unleashEnabled; + + @ConfigProperty(name = "notifications.enforce-bg-name-unicity", defaultValue = "false") + @Deprecated(forRemoval = true, since = "To be removed when we're done migrating to Unleash in all environments") + boolean enforceBehaviorGroupNameUnicity; + + @ConfigProperty(name = "notifications.enforce-integration-name-unicity", defaultValue = "false") + @Deprecated(forRemoval = true, since = "To be removed when we're done migrating to Unleash in all environments") + boolean enforceIntegrationNameUnicity; + + @ConfigProperty(name = "notifications.drawer.enabled", defaultValue = "false") + @Deprecated(forRemoval = true, since = "To be removed when we're done migrating to Unleash in all environments") + boolean drawerEnabled; + + @ConfigProperty(name = "notifications.slack.forbid.channel.usage.enabled", defaultValue = "false") + @Deprecated(forRemoval = true, since = "To be removed when we're done migrating to Unleash in all environments") + boolean slackForbidChannelUsageEnabled; + + // Only used in stage environments. + @ConfigProperty(name = DEFAULT_TEMPLATE, defaultValue = "false") + boolean defaultTemplateEnabled; + + // Only used in special environments. + @ConfigProperty(name = EMAILS_ONLY_MODE, defaultValue = "false") + boolean emailsOnlyModeEnabled; + + // Only used in special environments. + @ConfigProperty(name = INSTANT_EMAILS, defaultValue = "false") + boolean instantEmailsEnabled; + + @Inject + Unleash unleash; + + void logConfigAtStartup(@Observes Startup event) { + + Map config = new TreeMap<>(); + config.put(DEFAULT_TEMPLATE, isDefaultTemplateEnabled()); + config.put(DRAWER, isDrawerEnabled()); + config.put(EMAILS_ONLY_MODE, isEmailsOnlyModeEnabled()); + config.put(INSTANT_EMAILS, isInstantEmailsEnabled()); + config.put(FORBID_SLACK_CHANNEL_USAGE, isForbidSlackChannelUsage()); + config.put(UNIQUE_BG_NAME, isUniqueBgNameEnabled()); + config.put(UNIQUE_INTEGRATION_NAME, isUniqueIntegrationNameEnabled()); + config.put(UNLEASH, unleashEnabled); + + Log.info("=== Startup configuration ==="); + config.forEach((key, value) -> { + Log.infof("%s=%s", key, value); + }); + } + + public boolean isDefaultTemplateEnabled() { + return defaultTemplateEnabled; + } + + public boolean isDrawerEnabled() { + if (unleashEnabled) { + return unleash.isEnabled(DRAWER, false); + } else { + return drawerEnabled; + } + } + + public boolean isEmailsOnlyModeEnabled() { + return emailsOnlyModeEnabled; + } + + public boolean isForbidSlackChannelUsage() { + if (unleashEnabled) { + return unleash.isEnabled(FORBID_SLACK_CHANNEL_USAGE, false); + } else { + return slackForbidChannelUsageEnabled; + } + } + + public boolean isInstantEmailsEnabled() { + return instantEmailsEnabled; + } + + public boolean isUniqueBgNameEnabled() { + if (unleashEnabled) { + return unleash.isEnabled(UNIQUE_BG_NAME, false); + } else { + return enforceBehaviorGroupNameUnicity; + } + } + + public boolean isUniqueIntegrationNameEnabled() { + if (unleashEnabled) { + return unleash.isEnabled(UNIQUE_INTEGRATION_NAME, false); + } else { + return enforceIntegrationNameUnicity; + } + } +} diff --git a/backend/src/main/java/com/redhat/cloud/notifications/db/repositories/BehaviorGroupRepository.java b/backend/src/main/java/com/redhat/cloud/notifications/db/repositories/BehaviorGroupRepository.java index 8839a57f5d..c5cca9cfa2 100644 --- a/backend/src/main/java/com/redhat/cloud/notifications/db/repositories/BehaviorGroupRepository.java +++ b/backend/src/main/java/com/redhat/cloud/notifications/db/repositories/BehaviorGroupRepository.java @@ -1,6 +1,6 @@ package com.redhat.cloud.notifications.db.repositories; -import com.redhat.cloud.notifications.config.FeatureFlipper; +import com.redhat.cloud.notifications.config.BackendConfig; import com.redhat.cloud.notifications.db.Query; import com.redhat.cloud.notifications.models.BehaviorGroup; import com.redhat.cloud.notifications.models.Bundle; @@ -39,7 +39,7 @@ public class BehaviorGroupRepository { EntityManager entityManager; @Inject - FeatureFlipper featureFlipper; + BackendConfig backendConfig; /** * Counts all the behavior groups by their org id and event type id. @@ -207,7 +207,7 @@ void update(String orgId, BehaviorGroup behaviorGroup, boolean isDefaultBehavior } private void checkBehaviorGroupDisplayNameDuplicate(String orgId, BehaviorGroup behaviorGroup, boolean isDefaultBehaviorGroup) { - if (!featureFlipper.isEnforceBehaviorGroupNameUnicity()) { + if (!backendConfig.isUniqueBgNameEnabled()) { // The check is disabled from configuration. return; } diff --git a/backend/src/main/java/com/redhat/cloud/notifications/db/repositories/EndpointRepository.java b/backend/src/main/java/com/redhat/cloud/notifications/db/repositories/EndpointRepository.java index fce8892a3c..783f08f1e0 100644 --- a/backend/src/main/java/com/redhat/cloud/notifications/db/repositories/EndpointRepository.java +++ b/backend/src/main/java/com/redhat/cloud/notifications/db/repositories/EndpointRepository.java @@ -1,6 +1,6 @@ package com.redhat.cloud.notifications.db.repositories; -import com.redhat.cloud.notifications.config.FeatureFlipper; +import com.redhat.cloud.notifications.config.BackendConfig; import com.redhat.cloud.notifications.db.Query; import com.redhat.cloud.notifications.db.builder.QueryBuilder; import com.redhat.cloud.notifications.db.builder.WhereBuilder; @@ -41,10 +41,10 @@ public class EndpointRepository { EntityManager entityManager; @Inject - FeatureFlipper featureFlipper; + BackendConfig backendConfig; public void checkEndpointNameDuplicate(Endpoint endpoint) { - if (!featureFlipper.isEnforceIntegrationNameUnicity()) { + if (!backendConfig.isUniqueIntegrationNameEnabled()) { // Check disabled from configuration return; } diff --git a/backend/src/main/java/com/redhat/cloud/notifications/db/repositories/SubscriptionRepository.java b/backend/src/main/java/com/redhat/cloud/notifications/db/repositories/SubscriptionRepository.java index 380575db1c..4562010783 100644 --- a/backend/src/main/java/com/redhat/cloud/notifications/db/repositories/SubscriptionRepository.java +++ b/backend/src/main/java/com/redhat/cloud/notifications/db/repositories/SubscriptionRepository.java @@ -1,6 +1,6 @@ package com.redhat.cloud.notifications.db.repositories; -import com.redhat.cloud.notifications.config.FeatureFlipper; +import com.redhat.cloud.notifications.config.BackendConfig; import com.redhat.cloud.notifications.models.EventType; import com.redhat.cloud.notifications.models.EventTypeEmailSubscription; import com.redhat.cloud.notifications.models.SubscriptionType; @@ -23,7 +23,7 @@ public class SubscriptionRepository { EntityManager entityManager; @Inject - FeatureFlipper featureFlipper; + BackendConfig backendConfig; public void subscribe(String orgId, String username, UUID eventTypeId, SubscriptionType subscriptionType) { updateSubscription(orgId, username, eventTypeId, subscriptionType, true); @@ -93,7 +93,7 @@ public List getEmailSubscriptionsPerEventTypeForUser } private List getAvailableTypes() { - if (featureFlipper.isDrawerEnabled()) { + if (backendConfig.isDrawerEnabled()) { return List.of(INSTANT, DAILY, DRAWER); } else { return List.of(INSTANT, DAILY); diff --git a/backend/src/main/java/com/redhat/cloud/notifications/db/repositories/TemplateRepository.java b/backend/src/main/java/com/redhat/cloud/notifications/db/repositories/TemplateRepository.java index 78e418fdaa..a2bebb42f5 100644 --- a/backend/src/main/java/com/redhat/cloud/notifications/db/repositories/TemplateRepository.java +++ b/backend/src/main/java/com/redhat/cloud/notifications/db/repositories/TemplateRepository.java @@ -1,6 +1,6 @@ package com.redhat.cloud.notifications.db.repositories; -import com.redhat.cloud.notifications.config.FeatureFlipper; +import com.redhat.cloud.notifications.config.BackendConfig; import com.redhat.cloud.notifications.models.AggregationEmailTemplate; import com.redhat.cloud.notifications.models.Application; import com.redhat.cloud.notifications.models.EventType; @@ -29,7 +29,7 @@ public class TemplateRepository { EntityManager entityManager; @Inject - FeatureFlipper featureFlipper; + BackendConfig backendConfig; @Transactional public Template createTemplate(Template template) { @@ -317,7 +317,7 @@ private Template findTemplate(UUID id, String notFoundMessage) { public boolean isEmailSubscriptionSupported(String bundleName, String appName, SubscriptionType subscriptionType) { switch (subscriptionType) { case INSTANT: - if (featureFlipper.isUseDefaultTemplate()) { + if (backendConfig.isDefaultTemplateEnabled()) { return true; } @@ -330,7 +330,7 @@ public boolean isEmailSubscriptionSupported(String bundleName, String appName, S case DAILY: return isEmailAggregationSupported(bundleName, appName, List.of(subscriptionType)); case DRAWER: - return featureFlipper.isDrawerEnabled(); + return backendConfig.isDrawerEnabled(); default: return false; } diff --git a/backend/src/main/java/com/redhat/cloud/notifications/routers/EndpointResource.java b/backend/src/main/java/com/redhat/cloud/notifications/routers/EndpointResource.java index c01eaa3de6..fc9b6d3ca3 100644 --- a/backend/src/main/java/com/redhat/cloud/notifications/routers/EndpointResource.java +++ b/backend/src/main/java/com/redhat/cloud/notifications/routers/EndpointResource.java @@ -4,7 +4,7 @@ import com.redhat.cloud.notifications.auth.ConsoleIdentityProvider; import com.redhat.cloud.notifications.auth.principal.rhid.RhIdPrincipal; import com.redhat.cloud.notifications.auth.rbac.RbacGroupValidator; -import com.redhat.cloud.notifications.config.FeatureFlipper; +import com.redhat.cloud.notifications.config.BackendConfig; import com.redhat.cloud.notifications.db.Query; import com.redhat.cloud.notifications.db.repositories.EndpointRepository; import com.redhat.cloud.notifications.db.repositories.NotificationRepository; @@ -100,7 +100,7 @@ public class EndpointResource { RbacGroupValidator rbacGroupValidator; @Inject - FeatureFlipper featureFlipper; + BackendConfig backendConfig; /** * Used to create the secrets in Sources and update the endpoint's properties' IDs. @@ -296,7 +296,7 @@ public Endpoint createEndpoint(@Context SecurityContext sec, private void checkSlackChannel(CamelProperties camelProperties, CamelProperties previousCamelProperties) { String channel = camelProperties.getExtras().get("channel"); - if (featureFlipper.isSlackForbidChannelUsageEnabled()) { + if (backendConfig.isForbidSlackChannelUsage()) { // throw an exception if we receive a channel on endpoint creation if (null == previousCamelProperties && channel != null) { throw new BadRequestException(DEPRECATED_SLACK_CHANNEL_ERROR); @@ -562,7 +562,7 @@ private static void checkSystemEndpoint(EndpointType endpointType) { } private boolean isEndpointTypeAllowed(EndpointType endpointType) { - return !featureFlipper.isEmailsOnlyMode() || endpointType.isSystemEndpointType; + return !backendConfig.isEmailsOnlyModeEnabled() || endpointType.isSystemEndpointType; } /** diff --git a/backend/src/main/java/com/redhat/cloud/notifications/routers/UserConfigResource.java b/backend/src/main/java/com/redhat/cloud/notifications/routers/UserConfigResource.java index 73bd09d355..011f974fc7 100644 --- a/backend/src/main/java/com/redhat/cloud/notifications/routers/UserConfigResource.java +++ b/backend/src/main/java/com/redhat/cloud/notifications/routers/UserConfigResource.java @@ -3,7 +3,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.redhat.cloud.notifications.Constants; -import com.redhat.cloud.notifications.config.FeatureFlipper; +import com.redhat.cloud.notifications.config.BackendConfig; import com.redhat.cloud.notifications.db.repositories.ApplicationRepository; import com.redhat.cloud.notifications.db.repositories.BundleRepository; import com.redhat.cloud.notifications.db.repositories.EventTypeRepository; @@ -66,7 +66,7 @@ public class UserConfigResource { TemplateRepository templateRepository; @Inject - FeatureFlipper featureFlipper; + BackendConfig backendConfig; @Path(Constants.API_NOTIFICATIONS_V_1_0 + "/user-config") public static class V1 extends UserConfigResource { @@ -92,7 +92,7 @@ public Response saveSettingsByEventType(@Context SecurityContext sec, @NotNull @ // If the instant emails are disabled, we need to check that the request // does not contain any subscription with SubscriptionType.INSTANT. - if (!featureFlipper.isInstantEmailsEnabled() && userSettings.bundles.values().stream() + if (!backendConfig.isInstantEmailsEnabled() && userSettings.bundles.values().stream() .flatMap(bundleSettings -> bundleSettings.applications.values().stream()) .flatMap(appSettings -> appSettings.eventTypes.values().stream()) .flatMap(eventTypeSettings -> eventTypeSettings.emailSubscriptionTypes.keySet().stream()) @@ -218,7 +218,7 @@ private void addApplicationStructureDetails(final SettingsValuesByEventType sett eventTypeSettingsValue.hasForcedEmail = withForcedEmails; eventTypeSettingsValue.subscriptionLocked = eventType.isSubscriptionLocked(); for (SubscriptionType subscriptionType : SubscriptionType.values()) { - if (featureFlipper.isInstantEmailsEnabled() || subscriptionType != INSTANT) { + if (backendConfig.isInstantEmailsEnabled() || subscriptionType != INSTANT) { boolean supported = templateRepository.isEmailSubscriptionSupported(bundle.getName(), application.getName(), subscriptionType); if (supported) { boolean subscribedByDefault = subscriptionType.isSubscribedByDefault() || eventType.isSubscribedByDefault(); diff --git a/backend/src/main/java/com/redhat/cloud/notifications/routers/UserPreferencesForPolicy.java b/backend/src/main/java/com/redhat/cloud/notifications/routers/UserPreferencesForPolicy.java index 066df696cc..2c336ade1e 100644 --- a/backend/src/main/java/com/redhat/cloud/notifications/routers/UserPreferencesForPolicy.java +++ b/backend/src/main/java/com/redhat/cloud/notifications/routers/UserPreferencesForPolicy.java @@ -1,7 +1,7 @@ package com.redhat.cloud.notifications.routers; import com.redhat.cloud.notifications.Constants; -import com.redhat.cloud.notifications.config.FeatureFlipper; +import com.redhat.cloud.notifications.config.BackendConfig; import com.redhat.cloud.notifications.db.repositories.SubscriptionRepository; import com.redhat.cloud.notifications.models.EventTypeEmailSubscription; import com.redhat.cloud.notifications.routers.models.UserConfigPreferences; @@ -30,7 +30,7 @@ public class UserPreferencesForPolicy { SubscriptionRepository subscriptionRepository; @Inject - FeatureFlipper featureFlipper; + BackendConfig backendConfig; @Path(Constants.API_NOTIFICATIONS_V_1_0 + "/user-config") public static class V1 extends UserPreferencesForPolicy { @@ -64,7 +64,7 @@ public UserConfigPreferences getPreferences( for (EventTypeEmailSubscription subscribedEvent : subscriptionTypes) { if (DAILY == subscribedEvent.getType()) { preferences.setDailyEmail(subscribedEvent.isSubscribed()); - } else if (featureFlipper.isInstantEmailsEnabled() && INSTANT == subscribedEvent.getType()) { + } else if (backendConfig.isInstantEmailsEnabled() && INSTANT == subscribedEvent.getType()) { preferences.setInstantEmail(subscribedEvent.isSubscribed()); } } diff --git a/backend/src/main/resources/application.properties b/backend/src/main/resources/application.properties index 18fe864dc3..8abba48978 100644 --- a/backend/src/main/resources/application.properties +++ b/backend/src/main/resources/application.properties @@ -97,3 +97,7 @@ quarkus.otel.service.name=notifications-backend # is missing. In the case that you are using a real Sources application to test the integration, you will need to # set this PSK in the SOURCES_PSKS environment variable on that end. sources.psk=development-value-123 + +quarkus.unleash.active=false +quarkus.unleash.synchronous-fetch-on-initialisation=true +quarkus.unleash.url=http://localhost:4242 diff --git a/backend/src/test/java/com/redhat/cloud/notifications/db/repositories/BehaviorGroupRepositoryTest.java b/backend/src/test/java/com/redhat/cloud/notifications/db/repositories/BehaviorGroupRepositoryTest.java index 5ec30358b0..b1de2d8144 100644 --- a/backend/src/test/java/com/redhat/cloud/notifications/db/repositories/BehaviorGroupRepositoryTest.java +++ b/backend/src/test/java/com/redhat/cloud/notifications/db/repositories/BehaviorGroupRepositoryTest.java @@ -2,7 +2,7 @@ import com.redhat.cloud.notifications.TestHelpers; import com.redhat.cloud.notifications.TestLifecycleManager; -import com.redhat.cloud.notifications.config.FeatureFlipper; +import com.redhat.cloud.notifications.config.BackendConfig; import com.redhat.cloud.notifications.db.DbIsolatedTest; import com.redhat.cloud.notifications.db.Query; import com.redhat.cloud.notifications.db.ResourceHelpers; @@ -13,6 +13,7 @@ import com.redhat.cloud.notifications.models.Endpoint; import com.redhat.cloud.notifications.models.EventType; import com.redhat.cloud.notifications.models.EventTypeBehavior; +import io.quarkus.test.InjectMock; import io.quarkus.test.common.QuarkusTestResource; import io.quarkus.test.junit.QuarkusTest; import jakarta.inject.Inject; @@ -42,6 +43,7 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.when; @QuarkusTest @QuarkusTestResource(TestLifecycleManager.class) @@ -58,49 +60,41 @@ public class BehaviorGroupRepositoryTest extends DbIsolatedTest { @Inject BehaviorGroupRepository behaviorGroupRepository; - @Inject - FeatureFlipper featureFlipper; + @InjectMock + BackendConfig backendConfig; @Test void shouldThrowExceptionWhenCreatingWithExistingDisplayNameAndSameOrgId() { - try { - featureFlipper.setEnforceBehaviorGroupNameUnicity(true); - Bundle bundle = resourceHelpers.createBundle(); - BehaviorGroup behaviorGroup1 = resourceHelpers.createBehaviorGroup(DEFAULT_ACCOUNT_ID, DEFAULT_ORG_ID, "displayName", bundle.getId()); - - BehaviorGroup behaviorGroup2 = new BehaviorGroup(); - behaviorGroup2.setAccountId(behaviorGroup1.getAccountId()); - behaviorGroup2.setOrgId(behaviorGroup1.getOrgId()); - behaviorGroup2.setDisplayName(behaviorGroup1.getDisplayName()); - behaviorGroup2.setBundleId(bundle.getId()); - - BadRequestException e = assertThrows(BadRequestException.class, () -> { - behaviorGroupRepository.create(behaviorGroup2.getAccountId(), behaviorGroup2.getOrgId(), behaviorGroup2); - }); - assertEquals("A behavior group with display name [" + behaviorGroup1.getDisplayName() + "] already exists", e.getMessage()); - } finally { - featureFlipper.setEnforceBehaviorGroupNameUnicity(false); - } + when(backendConfig.isUniqueBgNameEnabled()).thenReturn(true); + Bundle bundle = resourceHelpers.createBundle(); + BehaviorGroup behaviorGroup1 = resourceHelpers.createBehaviorGroup(DEFAULT_ACCOUNT_ID, DEFAULT_ORG_ID, "displayName", bundle.getId()); + + BehaviorGroup behaviorGroup2 = new BehaviorGroup(); + behaviorGroup2.setAccountId(behaviorGroup1.getAccountId()); + behaviorGroup2.setOrgId(behaviorGroup1.getOrgId()); + behaviorGroup2.setDisplayName(behaviorGroup1.getDisplayName()); + behaviorGroup2.setBundleId(bundle.getId()); + + BadRequestException e = assertThrows(BadRequestException.class, () -> { + behaviorGroupRepository.create(behaviorGroup2.getAccountId(), behaviorGroup2.getOrgId(), behaviorGroup2); + }); + assertEquals("A behavior group with display name [" + behaviorGroup1.getDisplayName() + "] already exists", e.getMessage()); } @Test void shouldThrowExceptionWhenCreatingDefaultWithExistingDisplayName() { - try { - featureFlipper.setEnforceBehaviorGroupNameUnicity(true); - Bundle bundle = resourceHelpers.createBundle(); - BehaviorGroup behaviorGroup1 = resourceHelpers.createDefaultBehaviorGroup("displayName", bundle.getId()); - - BehaviorGroup behaviorGroup2 = new BehaviorGroup(); - behaviorGroup2.setDisplayName(behaviorGroup1.getDisplayName()); - behaviorGroup2.setBundleId(bundle.getId()); - - BadRequestException e = assertThrows(BadRequestException.class, () -> { - behaviorGroupRepository.createDefault(behaviorGroup2); - }); - assertEquals("A behavior group with display name [" + behaviorGroup1.getDisplayName() + "] already exists", e.getMessage()); - } finally { - featureFlipper.setEnforceBehaviorGroupNameUnicity(false); - } + when(backendConfig.isUniqueBgNameEnabled()).thenReturn(true); + Bundle bundle = resourceHelpers.createBundle(); + BehaviorGroup behaviorGroup1 = resourceHelpers.createDefaultBehaviorGroup("displayName", bundle.getId()); + + BehaviorGroup behaviorGroup2 = new BehaviorGroup(); + behaviorGroup2.setDisplayName(behaviorGroup1.getDisplayName()); + behaviorGroup2.setBundleId(bundle.getId()); + + BadRequestException e = assertThrows(BadRequestException.class, () -> { + behaviorGroupRepository.createDefault(behaviorGroup2); + }); + assertEquals("A behavior group with display name [" + behaviorGroup1.getDisplayName() + "] already exists", e.getMessage()); } @Test @@ -119,38 +113,30 @@ void shouldNotThrowExceptionWhenCreatingWithExistingDisplayNameButDifferentOrgId @Test void shouldThrowExceptionWhenUpdatingToExistingDisplayNameAndSameOrgId() { - try { - featureFlipper.setEnforceBehaviorGroupNameUnicity(true); - Bundle bundle = resourceHelpers.createBundle("name", "displayName"); - BehaviorGroup behaviorGroup1 = resourceHelpers.createBehaviorGroup(DEFAULT_ACCOUNT_ID, DEFAULT_ORG_ID, "displayName1", bundle.getId()); - BehaviorGroup behaviorGroup2 = resourceHelpers.createBehaviorGroup(behaviorGroup1.getAccountId(), behaviorGroup1.getOrgId(), "displayName2", bundle.getId()); - behaviorGroup2.setDisplayName(behaviorGroup1.getDisplayName()); - - BadRequestException e = assertThrows(BadRequestException.class, () -> { - behaviorGroupRepository.update(behaviorGroup2.getOrgId(), behaviorGroup2); - }); - assertEquals("A behavior group with display name [" + behaviorGroup1.getDisplayName() + "] already exists", e.getMessage()); - } finally { - featureFlipper.setEnforceBehaviorGroupNameUnicity(false); - } + when(backendConfig.isUniqueBgNameEnabled()).thenReturn(true); + Bundle bundle = resourceHelpers.createBundle("name", "displayName"); + BehaviorGroup behaviorGroup1 = resourceHelpers.createBehaviorGroup(DEFAULT_ACCOUNT_ID, DEFAULT_ORG_ID, "displayName1", bundle.getId()); + BehaviorGroup behaviorGroup2 = resourceHelpers.createBehaviorGroup(behaviorGroup1.getAccountId(), behaviorGroup1.getOrgId(), "displayName2", bundle.getId()); + behaviorGroup2.setDisplayName(behaviorGroup1.getDisplayName()); + + BadRequestException e = assertThrows(BadRequestException.class, () -> { + behaviorGroupRepository.update(behaviorGroup2.getOrgId(), behaviorGroup2); + }); + assertEquals("A behavior group with display name [" + behaviorGroup1.getDisplayName() + "] already exists", e.getMessage()); } @Test void shouldThrowExceptionWhenUpdatingDefaultToExistingDisplayName() { - try { - featureFlipper.setEnforceBehaviorGroupNameUnicity(true); - Bundle bundle = resourceHelpers.createBundle("name", "displayName"); - BehaviorGroup behaviorGroup1 = resourceHelpers.createDefaultBehaviorGroup("displayName1", bundle.getId()); - BehaviorGroup behaviorGroup2 = resourceHelpers.createDefaultBehaviorGroup("displayName2", bundle.getId()); - behaviorGroup2.setDisplayName(behaviorGroup1.getDisplayName()); - - BadRequestException e = assertThrows(BadRequestException.class, () -> { - behaviorGroupRepository.updateDefault(behaviorGroup2); - }); - assertEquals("A behavior group with display name [" + behaviorGroup1.getDisplayName() + "] already exists", e.getMessage()); - } finally { - featureFlipper.setEnforceBehaviorGroupNameUnicity(false); - } + when(backendConfig.isUniqueBgNameEnabled()).thenReturn(true); + Bundle bundle = resourceHelpers.createBundle("name", "displayName"); + BehaviorGroup behaviorGroup1 = resourceHelpers.createDefaultBehaviorGroup("displayName1", bundle.getId()); + BehaviorGroup behaviorGroup2 = resourceHelpers.createDefaultBehaviorGroup("displayName2", bundle.getId()); + behaviorGroup2.setDisplayName(behaviorGroup1.getDisplayName()); + + BadRequestException e = assertThrows(BadRequestException.class, () -> { + behaviorGroupRepository.updateDefault(behaviorGroup2); + }); + assertEquals("A behavior group with display name [" + behaviorGroup1.getDisplayName() + "] already exists", e.getMessage()); } @Test diff --git a/backend/src/test/java/com/redhat/cloud/notifications/events/LifecycleITest.java b/backend/src/test/java/com/redhat/cloud/notifications/events/LifecycleITest.java index 333da5bdf0..26c09425bf 100644 --- a/backend/src/test/java/com/redhat/cloud/notifications/events/LifecycleITest.java +++ b/backend/src/test/java/com/redhat/cloud/notifications/events/LifecycleITest.java @@ -4,7 +4,7 @@ import com.redhat.cloud.notifications.MockServerConfig; import com.redhat.cloud.notifications.TestHelpers; import com.redhat.cloud.notifications.TestLifecycleManager; -import com.redhat.cloud.notifications.config.FeatureFlipper; +import com.redhat.cloud.notifications.config.BackendConfig; import com.redhat.cloud.notifications.db.DbIsolatedTest; import com.redhat.cloud.notifications.models.Application; import com.redhat.cloud.notifications.models.BehaviorGroup; @@ -34,7 +34,6 @@ import jakarta.transaction.Transactional; import org.eclipse.microprofile.config.inject.ConfigProperty; import org.eclipse.microprofile.rest.client.inject.RestClient; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.Mockito; @@ -66,6 +65,7 @@ import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; +import static org.mockito.Mockito.when; @QuarkusTest @QuarkusTestResource(TestLifecycleManager.class) @@ -90,8 +90,8 @@ public class LifecycleITest extends DbIsolatedTest { @ConfigProperty(name = "internal.admin-role") String adminRole; - @Inject - FeatureFlipper featureFlipper; + @InjectMock + BackendConfig backendConfig; /** * We mock the sources service's REST client because there are a few tests @@ -104,12 +104,7 @@ public class LifecycleITest extends DbIsolatedTest { @BeforeEach void beforeEach() { - featureFlipper.setInstantEmailsEnabled(true); - } - - @AfterEach - void afterEach() { - featureFlipper.setInstantEmailsEnabled(false); + when(backendConfig.isInstantEmailsEnabled()).thenReturn(true); } private Header initRbacMock(String accountId, String orgId, String username, RbacAccess access) { @@ -120,7 +115,7 @@ private Header initRbacMock(String accountId, String orgId, String username, Rba @Test void shouldReturn400AndBadRequestExceptionWhenDisplayNameIsAlreadyPresent() { - if (!featureFlipper.isEnforceBehaviorGroupNameUnicity()) { + if (!backendConfig.isUniqueBgNameEnabled()) { // The check is disabled from configuration. return; } @@ -546,14 +541,13 @@ private String createWebhookEndpoint(Header identityHeader, String secretToken) // The SecretUtils class follows the "basic authentication", "secret // token" and "bearer token" order, so that is why we make the returns // in that order for the mock. - Mockito - .when(this.sourcesServiceMock.create(Mockito.anyString(), Mockito.anyString(), Mockito.any())) + when(this.sourcesServiceMock.create(Mockito.anyString(), Mockito.anyString(), Mockito.any())) .thenReturn(secretTokenSecret); // Make sure that when the secrets are loaded when we fetch the // endpoint again for checking the assertions, we simulate fetching // the secrets from Sources too. - Mockito.when(this.sourcesServiceMock.getById(Mockito.anyString(), Mockito.anyString(), Mockito.eq(secretTokenSecret.id))).thenReturn(secretTokenSecret); + when(this.sourcesServiceMock.getById(Mockito.anyString(), Mockito.anyString(), Mockito.eq(secretTokenSecret.id))).thenReturn(secretTokenSecret); String responseBody = given() .basePath(API_INTEGRATIONS_V_1_0) diff --git a/backend/src/test/java/com/redhat/cloud/notifications/routers/EmailsOnlyModeTest.java b/backend/src/test/java/com/redhat/cloud/notifications/routers/EmailsOnlyModeTest.java index 5c21310046..65109aa8d5 100644 --- a/backend/src/test/java/com/redhat/cloud/notifications/routers/EmailsOnlyModeTest.java +++ b/backend/src/test/java/com/redhat/cloud/notifications/routers/EmailsOnlyModeTest.java @@ -5,7 +5,7 @@ import com.redhat.cloud.notifications.MockServerConfig; import com.redhat.cloud.notifications.TestHelpers; import com.redhat.cloud.notifications.TestLifecycleManager; -import com.redhat.cloud.notifications.config.FeatureFlipper; +import com.redhat.cloud.notifications.config.BackendConfig; import com.redhat.cloud.notifications.db.DbIsolatedTest; import com.redhat.cloud.notifications.db.repositories.EndpointRepository; import com.redhat.cloud.notifications.models.Endpoint; @@ -13,7 +13,6 @@ import io.quarkus.test.common.QuarkusTestResource; import io.quarkus.test.junit.QuarkusTest; import io.restassured.http.Header; -import jakarta.inject.Inject; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; @@ -34,8 +33,8 @@ @QuarkusTestResource(TestLifecycleManager.class) public class EmailsOnlyModeTest extends DbIsolatedTest { - @Inject - FeatureFlipper featureFlipper; + @InjectMock + BackendConfig backendConfig; @InjectMock EndpointRepository endpointRepository; @@ -43,149 +42,124 @@ public class EmailsOnlyModeTest extends DbIsolatedTest { @ParameterizedTest @ValueSource(strings = {Constants.API_INTEGRATIONS_V_1_0, Constants.API_INTEGRATIONS_V_2_0}) void testCreateUnsupportedEndpointType(String apiPath) { - featureFlipper.setEmailsOnlyMode(true); - try { - - String identityHeaderValue = TestHelpers.encodeRHIdentityInfo("account-id", "org-id", "username"); - Header identityHeader = TestHelpers.createRHIdentityHeader(identityHeaderValue); - MockServerConfig.addMockRbacAccess(identityHeaderValue, FULL_ACCESS); - - Endpoint endpoint = new Endpoint(); - endpoint.setType(WEBHOOK); - endpoint.setName("name"); - endpoint.setDescription("description"); - - String responseBody = given() - .basePath(apiPath) - .header(identityHeader) - .when() - .contentType(JSON) - .body(Json.encode(endpoint)) - .post("/endpoints") - .then() - .statusCode(400) - .extract().asString(); - assertEquals(UNSUPPORTED_ENDPOINT_TYPE, responseBody); - - } finally { - featureFlipper.setEmailsOnlyMode(false); - } + when(backendConfig.isEmailsOnlyModeEnabled()).thenReturn(true); + + String identityHeaderValue = TestHelpers.encodeRHIdentityInfo("account-id", "org-id", "username"); + Header identityHeader = TestHelpers.createRHIdentityHeader(identityHeaderValue); + MockServerConfig.addMockRbacAccess(identityHeaderValue, FULL_ACCESS); + + Endpoint endpoint = new Endpoint(); + endpoint.setType(WEBHOOK); + endpoint.setName("name"); + endpoint.setDescription("description"); + + String responseBody = given() + .basePath(apiPath) + .header(identityHeader) + .when() + .contentType(JSON) + .body(Json.encode(endpoint)) + .post("/endpoints") + .then() + .statusCode(400) + .extract().asString(); + assertEquals(UNSUPPORTED_ENDPOINT_TYPE, responseBody); } @ParameterizedTest @ValueSource(strings = {Constants.API_INTEGRATIONS_V_1_0, Constants.API_INTEGRATIONS_V_2_0}) void testUpdateUnsupportedEndpointType(String apiPath) { - featureFlipper.setEmailsOnlyMode(true); - try { - - String identityHeaderValue = TestHelpers.encodeRHIdentityInfo("account-id", "org-id", "username"); - Header identityHeader = TestHelpers.createRHIdentityHeader(identityHeaderValue); - MockServerConfig.addMockRbacAccess(identityHeaderValue, FULL_ACCESS); - - Endpoint endpoint = new Endpoint(); - endpoint.setType(WEBHOOK); - endpoint.setName("name"); - endpoint.setDescription("description"); - - String responseBody = given() - .basePath(apiPath) - .header(identityHeader) - .pathParam("id", UUID.randomUUID().toString()) - .when() - .contentType(JSON) - .body(Json.encode(endpoint)) - .put("/endpoints/{id}") - .then() - .statusCode(400) - .extract().asString(); - assertEquals(UNSUPPORTED_ENDPOINT_TYPE, responseBody); - - } finally { - featureFlipper.setEmailsOnlyMode(false); - } + when(backendConfig.isEmailsOnlyModeEnabled()).thenReturn(true); + + String identityHeaderValue = TestHelpers.encodeRHIdentityInfo("account-id", "org-id", "username"); + Header identityHeader = TestHelpers.createRHIdentityHeader(identityHeaderValue); + MockServerConfig.addMockRbacAccess(identityHeaderValue, FULL_ACCESS); + + Endpoint endpoint = new Endpoint(); + endpoint.setType(WEBHOOK); + endpoint.setName("name"); + endpoint.setDescription("description"); + + String responseBody = given() + .basePath(apiPath) + .header(identityHeader) + .pathParam("id", UUID.randomUUID().toString()) + .when() + .contentType(JSON) + .body(Json.encode(endpoint)) + .put("/endpoints/{id}") + .then() + .statusCode(400) + .extract().asString(); + assertEquals(UNSUPPORTED_ENDPOINT_TYPE, responseBody); } @ParameterizedTest @ValueSource(strings = {Constants.API_INTEGRATIONS_V_1_0, Constants.API_INTEGRATIONS_V_2_0}) void testDeleteUnsupportedEndpointType(String apiPath) { - featureFlipper.setEmailsOnlyMode(true); - try { - - String identityHeaderValue = TestHelpers.encodeRHIdentityInfo("account-id", "org-id", "username"); - Header identityHeader = TestHelpers.createRHIdentityHeader(identityHeaderValue); - MockServerConfig.addMockRbacAccess(identityHeaderValue, FULL_ACCESS); - - when(endpointRepository.getEndpointTypeById(anyString(), any(UUID.class))).thenReturn(CAMEL); - - String responseBody = given() - .basePath(apiPath) - .header(identityHeader) - .pathParam("id", UUID.randomUUID().toString()) - .when() - .delete("/endpoints/{id}") - .then() - .statusCode(400) - .extract().asString(); - assertEquals(UNSUPPORTED_ENDPOINT_TYPE, responseBody); - - } finally { - featureFlipper.setEmailsOnlyMode(false); - } + when(backendConfig.isEmailsOnlyModeEnabled()).thenReturn(true); + + String identityHeaderValue = TestHelpers.encodeRHIdentityInfo("account-id", "org-id", "username"); + Header identityHeader = TestHelpers.createRHIdentityHeader(identityHeaderValue); + MockServerConfig.addMockRbacAccess(identityHeaderValue, FULL_ACCESS); + + when(endpointRepository.getEndpointTypeById(anyString(), any(UUID.class))).thenReturn(CAMEL); + + String responseBody = given() + .basePath(apiPath) + .header(identityHeader) + .pathParam("id", UUID.randomUUID().toString()) + .when() + .delete("/endpoints/{id}") + .then() + .statusCode(400) + .extract().asString(); + assertEquals(UNSUPPORTED_ENDPOINT_TYPE, responseBody); } @ParameterizedTest @ValueSource(strings = {Constants.API_INTEGRATIONS_V_1_0, Constants.API_INTEGRATIONS_V_2_0}) void testEnableUnsupportedEndpointType(String apiPath) { - featureFlipper.setEmailsOnlyMode(true); - try { - - String identityHeaderValue = TestHelpers.encodeRHIdentityInfo("account-id", "org-id", "username"); - Header identityHeader = TestHelpers.createRHIdentityHeader(identityHeaderValue); - MockServerConfig.addMockRbacAccess(identityHeaderValue, FULL_ACCESS); - - when(endpointRepository.getEndpointTypeById(anyString(), any(UUID.class))).thenReturn(CAMEL); - - String responseBody = given() - .basePath(apiPath) - .header(identityHeader) - .pathParam("id", UUID.randomUUID().toString()) - .when() - .put("/endpoints/{id}/enable") - .then() - .statusCode(400) - .extract().asString(); - assertEquals(UNSUPPORTED_ENDPOINT_TYPE, responseBody); - - } finally { - featureFlipper.setEmailsOnlyMode(false); - } + when(backendConfig.isEmailsOnlyModeEnabled()).thenReturn(true); + + String identityHeaderValue = TestHelpers.encodeRHIdentityInfo("account-id", "org-id", "username"); + Header identityHeader = TestHelpers.createRHIdentityHeader(identityHeaderValue); + MockServerConfig.addMockRbacAccess(identityHeaderValue, FULL_ACCESS); + + when(endpointRepository.getEndpointTypeById(anyString(), any(UUID.class))).thenReturn(CAMEL); + + String responseBody = given() + .basePath(apiPath) + .header(identityHeader) + .pathParam("id", UUID.randomUUID().toString()) + .when() + .put("/endpoints/{id}/enable") + .then() + .statusCode(400) + .extract().asString(); + assertEquals(UNSUPPORTED_ENDPOINT_TYPE, responseBody); } @ParameterizedTest @ValueSource(strings = {Constants.API_INTEGRATIONS_V_1_0, Constants.API_INTEGRATIONS_V_2_0}) void testDisableUnsupportedEndpointType(String apiPath) { - featureFlipper.setEmailsOnlyMode(true); - try { - - String identityHeaderValue = TestHelpers.encodeRHIdentityInfo("account-id", "org-id", "username"); - Header identityHeader = TestHelpers.createRHIdentityHeader(identityHeaderValue); - MockServerConfig.addMockRbacAccess(identityHeaderValue, FULL_ACCESS); - - when(endpointRepository.getEndpointTypeById(anyString(), any(UUID.class))).thenReturn(CAMEL); - - String responseBody = given() - .basePath(apiPath) - .header(identityHeader) - .pathParam("id", UUID.randomUUID().toString()) - .when() - .delete("/endpoints/{id}/enable") - .then() - .statusCode(400) - .extract().asString(); - assertEquals(UNSUPPORTED_ENDPOINT_TYPE, responseBody); - - } finally { - featureFlipper.setEmailsOnlyMode(false); - } + when(backendConfig.isEmailsOnlyModeEnabled()).thenReturn(true); + + String identityHeaderValue = TestHelpers.encodeRHIdentityInfo("account-id", "org-id", "username"); + Header identityHeader = TestHelpers.createRHIdentityHeader(identityHeaderValue); + MockServerConfig.addMockRbacAccess(identityHeaderValue, FULL_ACCESS); + + when(endpointRepository.getEndpointTypeById(anyString(), any(UUID.class))).thenReturn(CAMEL); + + String responseBody = given() + .basePath(apiPath) + .header(identityHeader) + .pathParam("id", UUID.randomUUID().toString()) + .when() + .delete("/endpoints/{id}/enable") + .then() + .statusCode(400) + .extract().asString(); + assertEquals(UNSUPPORTED_ENDPOINT_TYPE, responseBody); } } diff --git a/backend/src/test/java/com/redhat/cloud/notifications/routers/EndpointResourceTest.java b/backend/src/test/java/com/redhat/cloud/notifications/routers/EndpointResourceTest.java index f85a946e5e..709ca9fa6a 100644 --- a/backend/src/test/java/com/redhat/cloud/notifications/routers/EndpointResourceTest.java +++ b/backend/src/test/java/com/redhat/cloud/notifications/routers/EndpointResourceTest.java @@ -5,7 +5,7 @@ import com.redhat.cloud.notifications.TestConstants; import com.redhat.cloud.notifications.TestHelpers; import com.redhat.cloud.notifications.TestLifecycleManager; -import com.redhat.cloud.notifications.config.FeatureFlipper; +import com.redhat.cloud.notifications.config.BackendConfig; import com.redhat.cloud.notifications.db.DbIsolatedTest; import com.redhat.cloud.notifications.db.ResourceHelpers; import com.redhat.cloud.notifications.db.repositories.EndpointRepository; @@ -41,7 +41,6 @@ import jakarta.persistence.EntityManager; import org.eclipse.microprofile.config.inject.ConfigProperty; import org.eclipse.microprofile.rest.client.inject.RestClient; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -99,12 +98,7 @@ public class EndpointResourceTest extends DbIsolatedTest { @BeforeEach void beforeEach() { RestAssured.basePath = TestConstants.API_INTEGRATIONS_V_1_0; - featureFlipper.setInstantEmailsEnabled(true); - } - - @AfterEach - void afterEach() { - featureFlipper.setInstantEmailsEnabled(false); + when(backendConfig.isInstantEmailsEnabled()).thenReturn(true); } @Inject @@ -113,8 +107,8 @@ void afterEach() { @Inject ResourceHelpers resourceHelpers; - @Inject - FeatureFlipper featureFlipper; + @InjectMock + BackendConfig backendConfig; @Inject EndpointRepository endpointRepository; @@ -296,123 +290,119 @@ void testEndpointAdding() { @Test void testRepeatedEndpointName() { - try { - featureFlipper.setEnforceIntegrationNameUnicity(true); - String orgId = "repeatEndpoint"; - String userName = "user"; + when(backendConfig.isUniqueIntegrationNameEnabled()).thenReturn(true); + String orgId = "repeatEndpoint"; + String userName = "user"; - String identityHeaderValue = TestHelpers.encodeRHIdentityInfo(orgId, orgId, userName); - Header identityHeader = TestHelpers.createRHIdentityHeader(identityHeaderValue); + String identityHeaderValue = TestHelpers.encodeRHIdentityInfo(orgId, orgId, userName); + Header identityHeader = TestHelpers.createRHIdentityHeader(identityHeaderValue); - MockServerConfig.addMockRbacAccess(identityHeaderValue, FULL_ACCESS); + MockServerConfig.addMockRbacAccess(identityHeaderValue, FULL_ACCESS); - // Endpoint1 - WebhookProperties properties = new WebhookProperties(); - properties.setMethod(POST); - properties.setDisableSslVerification(false); - properties.setSecretToken("my-super-secret-token"); - properties.setUrl(getMockServerUrl()); + // Endpoint1 + WebhookProperties properties = new WebhookProperties(); + properties.setMethod(POST); + properties.setDisableSslVerification(false); + properties.setSecretToken("my-super-secret-token"); + properties.setUrl(getMockServerUrl()); - Endpoint endpoint1 = new Endpoint(); - endpoint1.setType(EndpointType.WEBHOOK); - endpoint1.setName("Endpoint1"); - endpoint1.setDescription("needle in the haystack"); - endpoint1.setEnabled(true); - endpoint1.setProperties(properties); - endpoint1.setServerErrors(3); + Endpoint endpoint1 = new Endpoint(); + endpoint1.setType(EndpointType.WEBHOOK); + endpoint1.setName("Endpoint1"); + endpoint1.setDescription("needle in the haystack"); + endpoint1.setEnabled(true); + endpoint1.setProperties(properties); + endpoint1.setServerErrors(3); - mockSources(properties); + mockSources(properties); - Response response = given() - .header(identityHeader) - .when() - .contentType(JSON) - .body(Json.encode(endpoint1)) - .post("/endpoints") - .then() - .statusCode(200) - .extract().response(); + Response response = given() + .header(identityHeader) + .when() + .contentType(JSON) + .body(Json.encode(endpoint1)) + .post("/endpoints") + .then() + .statusCode(200) + .extract().response(); - String endpoint1Id = new JsonObject(response.getBody().asString()).getString("id"); - assertNotNull(endpoint1Id); + String endpoint1Id = new JsonObject(response.getBody().asString()).getString("id"); + assertNotNull(endpoint1Id); - // Trying to add the same endpoint name again results in a 400 error - given() - .header(identityHeader) - .when() - .contentType(JSON) - .body(Json.encode(endpoint1)) - .post("/endpoints") - .then() - .statusCode(400); + // Trying to add the same endpoint name again results in a 400 error + given() + .header(identityHeader) + .when() + .contentType(JSON) + .body(Json.encode(endpoint1)) + .post("/endpoints") + .then() + .statusCode(400); - // Endpoint2 - Endpoint ep = new Endpoint(); - ep.setType(EndpointType.WEBHOOK); - ep.setName("Endpoint2"); - ep.setDescription("needle in the haystack"); - ep.setEnabled(true); - ep.setProperties(properties); - ep.setServerErrors(3); + // Endpoint2 + Endpoint ep = new Endpoint(); + ep.setType(EndpointType.WEBHOOK); + ep.setName("Endpoint2"); + ep.setDescription("needle in the haystack"); + ep.setEnabled(true); + ep.setProperties(properties); + ep.setServerErrors(3); - given() - .header(identityHeader) - .when() - .contentType(JSON) - .body(Json.encode(ep)) - .post("/endpoints") - .then() - .statusCode(200); - - // Different endpoint type with same name - CamelProperties camelProperties = new CamelProperties(); - camelProperties.setBasicAuthentication(new BasicAuthentication()); - camelProperties.setExtras(Map.of()); - camelProperties.setSecretToken("secret"); - camelProperties.setUrl("http://nowhere"); - - ep = new Endpoint(); - ep.setType(EndpointType.CAMEL); - ep.setSubType("stuff"); - ep.setName("Endpoint1"); - ep.setDescription("needle in the haystack"); - ep.setEnabled(true); - ep.setProperties(camelProperties); - ep.setServerErrors(3); + given() + .header(identityHeader) + .when() + .contentType(JSON) + .body(Json.encode(ep)) + .post("/endpoints") + .then() + .statusCode(200); - given() - .header(identityHeader) - .when() - .contentType(JSON) - .body(Json.encode(ep)) - .post("/endpoints") - .then() - .statusCode(400); + // Different endpoint type with same name + CamelProperties camelProperties = new CamelProperties(); + camelProperties.setBasicAuthentication(new BasicAuthentication()); + camelProperties.setExtras(Map.of()); + camelProperties.setSecretToken("secret"); + camelProperties.setUrl("http://nowhere"); - // Updating endpoint1 name is possible - endpoint1.setName("Endpoint1-updated"); - given() - .header(identityHeader) - .contentType(JSON) - .body(Json.encode(endpoint1)) - .when() - .put("/endpoints/" + endpoint1Id) - .then() - .statusCode(200); + ep = new Endpoint(); + ep.setType(EndpointType.CAMEL); + ep.setSubType("stuff"); + ep.setName("Endpoint1"); + ep.setDescription("needle in the haystack"); + ep.setEnabled(true); + ep.setProperties(camelProperties); + ep.setServerErrors(3); - // Updating to the name of an already existing endpoint is not possible - endpoint1.setName("Endpoint2"); - given() - .header(identityHeader) - .contentType(JSON) - .body(Json.encode(endpoint1)) - .when() - .put("/endpoints/" + endpoint1Id) - .then() - .statusCode(400); - } finally { - featureFlipper.setEnforceIntegrationNameUnicity(false); - } + given() + .header(identityHeader) + .when() + .contentType(JSON) + .body(Json.encode(ep)) + .post("/endpoints") + .then() + .statusCode(400); + + // Updating endpoint1 name is possible + endpoint1.setName("Endpoint1-updated"); + given() + .header(identityHeader) + .contentType(JSON) + .body(Json.encode(endpoint1)) + .when() + .put("/endpoints/" + endpoint1Id) + .then() + .statusCode(200); + + // Updating to the name of an already existing endpoint is not possible + endpoint1.setName("Endpoint2"); + given() + .header(identityHeader) + .contentType(JSON) + .body(Json.encode(endpoint1)) + .when() + .put("/endpoints/" + endpoint1Id) + .then() + .statusCode(400); } private JsonObject fetchSingle(String id, Header identityHeader) { @@ -689,77 +679,73 @@ void testMissingSlackChannel() { @Test void testForbidSlackChannelUsage() { - try { - featureFlipper.setSlackForbidChannelUsageEnabled(true); - String identityHeaderValue = TestHelpers.encodeRHIdentityInfo("account-id", "org-id", "user"); - Header identityHeader = TestHelpers.createRHIdentityHeader(identityHeaderValue); - MockServerConfig.addMockRbacAccess(identityHeaderValue, FULL_ACCESS); + when(backendConfig.isForbidSlackChannelUsage()).thenReturn(true); - Map extras = new HashMap<>(Map.of("channel", "")); // Having a channel value is invalid. - CamelProperties camelProperties = new CamelProperties(); - camelProperties.setUrl("https://foo.com"); - camelProperties.setExtras(extras); + String identityHeaderValue = TestHelpers.encodeRHIdentityInfo("account-id", "org-id", "user"); + Header identityHeader = TestHelpers.createRHIdentityHeader(identityHeaderValue); + MockServerConfig.addMockRbacAccess(identityHeaderValue, FULL_ACCESS); - Endpoint endpoint = new Endpoint(); - endpoint.setType(EndpointType.CAMEL); - endpoint.setSubType("slack"); - endpoint.setName("name"); - endpoint.setDescription("description"); - endpoint.setProperties(camelProperties); + Map extras = new HashMap<>(Map.of("channel", "")); // Having a channel value is invalid. + CamelProperties camelProperties = new CamelProperties(); + camelProperties.setUrl("https://foo.com"); + camelProperties.setExtras(extras); - String responseBody = given() - .header(identityHeader) - .when() - .contentType(JSON) - .body(Json.encode(endpoint)) - .post("/endpoints") - .then() - .statusCode(400) - .extract().asString(); + Endpoint endpoint = new Endpoint(); + endpoint.setType(EndpointType.CAMEL); + endpoint.setSubType("slack"); + endpoint.setName("name"); + endpoint.setDescription("description"); + endpoint.setProperties(camelProperties); - assertEquals(DEPRECATED_SLACK_CHANNEL_ERROR, responseBody); + String responseBody = given() + .header(identityHeader) + .when() + .contentType(JSON) + .body(Json.encode(endpoint)) + .post("/endpoints") + .then() + .statusCode(400) + .extract().asString(); - extras.remove("channel"); + assertEquals(DEPRECATED_SLACK_CHANNEL_ERROR, responseBody); - String createdEndpoint = given() - .header(identityHeader) - .when() - .contentType(JSON) - .body(Json.encode(endpoint)) - .post("/endpoints") - .then() - .statusCode(200) - .extract().asString(); + extras.remove("channel"); - final JsonObject jsonResponse = new JsonObject(createdEndpoint); - final String endpointUuidRaw = jsonResponse.getString("id"); + String createdEndpoint = given() + .header(identityHeader) + .when() + .contentType(JSON) + .body(Json.encode(endpoint)) + .post("/endpoints") + .then() + .statusCode(200) + .extract().asString(); - // try to update endpoint without channel - given() - .header(identityHeader) - .contentType(JSON) - .pathParam("id", endpointUuidRaw) - .body(Json.encode(endpoint)) - .when() - .put("/endpoints/{id}") - .then() - .statusCode(200); + final JsonObject jsonResponse = new JsonObject(createdEndpoint); + final String endpointUuidRaw = jsonResponse.getString("id"); - // try to update endpoint with channel - extras.put("channel", "refused"); - given() - .header(identityHeader) - .contentType(JSON) - .pathParam("id", endpointUuidRaw) - .body(Json.encode(endpoint)) - .when() - .put("/endpoints/{id}") - .then() - .statusCode(400); + // try to update endpoint without channel + given() + .header(identityHeader) + .contentType(JSON) + .pathParam("id", endpointUuidRaw) + .body(Json.encode(endpoint)) + .when() + .put("/endpoints/{id}") + .then() + .statusCode(200); - } finally { - featureFlipper.setSlackForbidChannelUsageEnabled(false); - } + // try to update endpoint with channel + extras.put("channel", "refused"); + given() + .header(identityHeader) + .contentType(JSON) + .pathParam("id", endpointUuidRaw) + .body(Json.encode(endpoint)) + .when() + .put("/endpoints/{id}") + .then() + .statusCode(400); } @Test diff --git a/backend/src/test/java/com/redhat/cloud/notifications/routers/NotificationResourceTest.java b/backend/src/test/java/com/redhat/cloud/notifications/routers/NotificationResourceTest.java index 231f0e98ea..d4f35fdd36 100644 --- a/backend/src/test/java/com/redhat/cloud/notifications/routers/NotificationResourceTest.java +++ b/backend/src/test/java/com/redhat/cloud/notifications/routers/NotificationResourceTest.java @@ -6,7 +6,7 @@ import com.redhat.cloud.notifications.TestConstants; import com.redhat.cloud.notifications.TestHelpers; import com.redhat.cloud.notifications.TestLifecycleManager; -import com.redhat.cloud.notifications.config.FeatureFlipper; +import com.redhat.cloud.notifications.config.BackendConfig; import com.redhat.cloud.notifications.db.DbIsolatedTest; import com.redhat.cloud.notifications.db.ResourceHelpers; import com.redhat.cloud.notifications.db.repositories.ApplicationRepository; @@ -21,6 +21,7 @@ import com.redhat.cloud.notifications.routers.models.behaviorgroup.CreateBehaviorGroupRequest; import com.redhat.cloud.notifications.routers.models.behaviorgroup.CreateBehaviorGroupResponse; import com.redhat.cloud.notifications.routers.models.behaviorgroup.UpdateBehaviorGroupRequest; +import io.quarkus.test.InjectMock; import io.quarkus.test.common.QuarkusTestResource; import io.quarkus.test.junit.QuarkusTest; import io.restassured.RestAssured; @@ -31,7 +32,6 @@ import io.vertx.core.json.JsonObject; import jakarta.inject.Inject; import jakarta.validation.constraints.Size; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -61,6 +61,7 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.when; @QuarkusTest @QuarkusTestResource(TestLifecycleManager.class) @@ -86,19 +87,14 @@ public class NotificationResourceTest extends DbIsolatedTest { @Inject BehaviorGroupRepository behaviorGroupRepository; - @Inject - FeatureFlipper featureFlipper; + @InjectMock + BackendConfig backendConfig; @BeforeEach void beforeEach() { RestAssured.basePath = TestConstants.API_NOTIFICATIONS_V_1_0; MockServerConfig.clearRbac(); - featureFlipper.setEnforceBehaviorGroupNameUnicity(true); - } - - @AfterEach - void afterEach() { - featureFlipper.setEnforceBehaviorGroupNameUnicity(false); + when(backendConfig.isUniqueBgNameEnabled()).thenReturn(true); } private Header initRbacMock(String accountId, String orgId, String username, RbacAccess access) { @@ -934,52 +930,47 @@ void testBehaviorGroupsAffectedByRemovalOfUnknownEndpointId() { @Test void testBehaviorGroupSameName() { - try { - final String BEHAVIOR_GROUP_1_NAME = "BehaviorGroup1"; - final String BEHAVIOR_GROUP_2_NAME = "BehaviorGroup2"; + final String BEHAVIOR_GROUP_1_NAME = "BehaviorGroup1"; + final String BEHAVIOR_GROUP_2_NAME = "BehaviorGroup2"; - featureFlipper.setEnforceBehaviorGroupNameUnicity(true); - Header identityHeader = initRbacMock("tenant", "sameBehaviorGroupName", "user", FULL_ACCESS); + when(backendConfig.isUniqueBgNameEnabled()).thenReturn(true); + Header identityHeader = initRbacMock("tenant", "sameBehaviorGroupName", "user", FULL_ACCESS); - Bundle bundle1 = helpers.createBundle(TEST_BUNDLE_NAME, "Bundle1"); - Bundle bundle2 = helpers.createBundle(TEST_BUNDLE_2_NAME, "Bundle2"); + Bundle bundle1 = helpers.createBundle(TEST_BUNDLE_NAME, "Bundle1"); + Bundle bundle2 = helpers.createBundle(TEST_BUNDLE_2_NAME, "Bundle2"); - CreateBehaviorGroupRequest createBehaviorGroupRequest = new CreateBehaviorGroupRequest(); - createBehaviorGroupRequest.displayName = BEHAVIOR_GROUP_1_NAME; - createBehaviorGroupRequest.bundleId = bundle1.getId(); + CreateBehaviorGroupRequest createBehaviorGroupRequest = new CreateBehaviorGroupRequest(); + createBehaviorGroupRequest.displayName = BEHAVIOR_GROUP_1_NAME; + createBehaviorGroupRequest.bundleId = bundle1.getId(); - UUID behaviorGroup1Id = createBehaviorGroup(identityHeader, createBehaviorGroupRequest, 200).get().id; - assertNotNull(behaviorGroup1Id); + UUID behaviorGroup1Id = createBehaviorGroup(identityHeader, createBehaviorGroupRequest, 200).get().id; + assertNotNull(behaviorGroup1Id); - // same display name in same bundle is not possible - createBehaviorGroup(identityHeader, createBehaviorGroupRequest, 400); + // same display name in same bundle is not possible + createBehaviorGroup(identityHeader, createBehaviorGroupRequest, 400); - // same display name in a different bundle is OK - createBehaviorGroupRequest.bundleId = bundle2.getId(); - createBehaviorGroup(identityHeader, createBehaviorGroupRequest, 200); + // same display name in a different bundle is OK + createBehaviorGroupRequest.bundleId = bundle2.getId(); + createBehaviorGroup(identityHeader, createBehaviorGroupRequest, 200); - // Different display name in bundle1 - createBehaviorGroupRequest.bundleId = bundle1.getId(); - createBehaviorGroupRequest.displayName = BEHAVIOR_GROUP_2_NAME; - createBehaviorGroup(identityHeader, createBehaviorGroupRequest, 200); + // Different display name in bundle1 + createBehaviorGroupRequest.bundleId = bundle1.getId(); + createBehaviorGroupRequest.displayName = BEHAVIOR_GROUP_2_NAME; + createBehaviorGroup(identityHeader, createBehaviorGroupRequest, 200); - // Cannot update Behavior Group 1 name to "BehaviorGroup2" as it already exists - UpdateBehaviorGroupRequest updateBehaviorGroupRequest = new UpdateBehaviorGroupRequest(); - updateBehaviorGroupRequest.displayName = BEHAVIOR_GROUP_2_NAME; - updateBehaviorGroup(identityHeader, behaviorGroup1Id, updateBehaviorGroupRequest, 400); + // Cannot update Behavior Group 1 name to "BehaviorGroup2" as it already exists + UpdateBehaviorGroupRequest updateBehaviorGroupRequest = new UpdateBehaviorGroupRequest(); + updateBehaviorGroupRequest.displayName = BEHAVIOR_GROUP_2_NAME; + updateBehaviorGroup(identityHeader, behaviorGroup1Id, updateBehaviorGroupRequest, 400); - // Can update other properties without changing the name - updateBehaviorGroupRequest.displayName = BEHAVIOR_GROUP_1_NAME; - updateBehaviorGroupRequest.eventTypeIds = Set.of(); - updateBehaviorGroup(identityHeader, behaviorGroup1Id, updateBehaviorGroupRequest, 200); + // Can update other properties without changing the name + updateBehaviorGroupRequest.displayName = BEHAVIOR_GROUP_1_NAME; + updateBehaviorGroupRequest.eventTypeIds = Set.of(); + updateBehaviorGroup(identityHeader, behaviorGroup1Id, updateBehaviorGroupRequest, 200); - // Can use other name - updateBehaviorGroupRequest.displayName = "OtherName"; - updateBehaviorGroup(identityHeader, behaviorGroup1Id, updateBehaviorGroupRequest, 200); - - } finally { - featureFlipper.setEnforceBehaviorGroupNameUnicity(false); - } + // Can use other name + updateBehaviorGroupRequest.displayName = "OtherName"; + updateBehaviorGroup(identityHeader, behaviorGroup1Id, updateBehaviorGroupRequest, 200); } /** diff --git a/backend/src/test/java/com/redhat/cloud/notifications/routers/UserConfigResourceTest.java b/backend/src/test/java/com/redhat/cloud/notifications/routers/UserConfigResourceTest.java index 5b7dec84b6..0b380d8e59 100644 --- a/backend/src/test/java/com/redhat/cloud/notifications/routers/UserConfigResourceTest.java +++ b/backend/src/test/java/com/redhat/cloud/notifications/routers/UserConfigResourceTest.java @@ -6,7 +6,7 @@ import com.redhat.cloud.notifications.TestConstants; import com.redhat.cloud.notifications.TestHelpers; import com.redhat.cloud.notifications.TestLifecycleManager; -import com.redhat.cloud.notifications.config.FeatureFlipper; +import com.redhat.cloud.notifications.config.BackendConfig; import com.redhat.cloud.notifications.db.DbIsolatedTest; import com.redhat.cloud.notifications.db.repositories.ApplicationRepository; import com.redhat.cloud.notifications.db.repositories.EventTypeRepository; @@ -20,6 +20,7 @@ import com.redhat.cloud.notifications.routers.models.SettingsValueByEventTypeJsonForm; import com.redhat.cloud.notifications.routers.models.SettingsValuesByEventType; import com.redhat.cloud.notifications.routers.models.UserConfigPreferences; +import io.quarkus.test.InjectMock; import io.quarkus.test.common.QuarkusTestResource; import io.quarkus.test.junit.QuarkusTest; import io.quarkus.test.junit.mockito.InjectSpy; @@ -32,7 +33,6 @@ import jakarta.transaction.Transactional; import org.apache.commons.lang3.RandomStringUtils; import org.eclipse.microprofile.config.inject.ConfigProperty; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -70,16 +70,11 @@ public class UserConfigResourceTest extends DbIsolatedTest { @BeforeEach void beforeEach() { RestAssured.basePath = TestConstants.API_NOTIFICATIONS_V_1_0; - featureFlipper.setInstantEmailsEnabled(true); + when(backendConfig.isInstantEmailsEnabled()).thenReturn(true); } - @AfterEach - void afterEach() { - featureFlipper.setInstantEmailsEnabled(false); - } - - @Inject - FeatureFlipper featureFlipper; + @InjectMock + BackendConfig backendConfig; @Inject SubscriptionRepository subscriptionRepository; @@ -128,7 +123,7 @@ private SettingsValuesByEventType createSettingsValue(String bundle, String appl SettingsValuesByEventType.EventTypeSettingsValue eventTypeSettingsValue = new SettingsValuesByEventType.EventTypeSettingsValue(); eventTypeSettingsValue.emailSubscriptionTypes.put(DAILY, daily); eventTypeSettingsValue.emailSubscriptionTypes.put(INSTANT, instant); - if (featureFlipper.isDrawerEnabled()) { + if (backendConfig.isDrawerEnabled()) { eventTypeSettingsValue.emailSubscriptionTypes.put(DRAWER, drawer); } @@ -151,12 +146,8 @@ void testLegacySettingsByEventType() { @Test void testSettingsByEventTypeWithDrawerEnabled() { - try { - featureFlipper.setDrawerEnabled(true); - testSettingsByEventType(); - } finally { - featureFlipper.setDrawerEnabled(false); - } + when(backendConfig.isDrawerEnabled()).thenReturn(true); + testSettingsByEventType(); } void testSettingsByEventType() { @@ -216,21 +207,21 @@ void testSettingsByEventType() { rhelPolicy = rhelPolicyForm(settingsValuesByEventType); assertNotNull(rhelPolicy.eventTypes.get(0).fields.get(0).infoMessage); - featureFlipper.setInstantEmailsEnabled(false); + when(backendConfig.isInstantEmailsEnabled()).thenReturn(false); SettingsValuesByEventType settingsValues = createSettingsValue(bundle, application, eventType, true, true, false); postPreferencesByEventType(path, identityHeader, settingsValues, 400); - featureFlipper.setInstantEmailsEnabled(true); + when(backendConfig.isInstantEmailsEnabled()).thenReturn(true); postPreferencesByEventType(path, identityHeader, settingsValues, 200); - featureFlipper.setInstantEmailsEnabled(false); + when(backendConfig.isInstantEmailsEnabled()).thenReturn(false); SettingsValueByEventTypeJsonForm settingsValue = getPreferencesByEventType(path, identityHeader); rhelPolicy = rhelPolicyForm(settingsValue); boolean instantEmailSettingsReturned = extractNotificationValues(rhelPolicy.eventTypes, bundle, application, eventType) .keySet().stream().anyMatch(INSTANT::equals); assertFalse(instantEmailSettingsReturned, "Instant email subscription settings should not be returned when instant emails are disabled"); - featureFlipper.setInstantEmailsEnabled(true); + when(backendConfig.isInstantEmailsEnabled()).thenReturn(true); // Daily and Instant to false updateAndCheckUserPreference(path, identityHeader, bundle, application, eventType, List.of(DRAWER), List.of(DRAWER)); @@ -253,7 +244,7 @@ void testSettingsByEventType() { // Unsubscribing from everything should work this time. updateAndCheckUserPreference(path, identityHeader, bundle, application, eventType, emptyList(), emptyList()); - if (featureFlipper.isDrawerEnabled()) { + if (backendConfig.isDrawerEnabled()) { // Daily, Instant and drawer to false updateAndCheckUserPreference(path, identityHeader, bundle, application, eventType, emptyList(), emptyList()); @@ -325,7 +316,7 @@ void testSettingsByEventType() { Map notificationPreferenes = extractNotificationValues(rhelPolicy.eventTypes, bundle, application, eventType); - if (featureFlipper.isDrawerEnabled()) { + if (backendConfig.isDrawerEnabled()) { assertEquals(2, notificationPreferenes.size()); assertTrue(notificationPreferenes.containsKey(DRAWER)); } else { @@ -343,7 +334,7 @@ void testSettingsByEventType() { .contentType(JSON) .extract().body().as(SettingsValueByEventTypeJsonForm.class); rhelPolicy = rhelPolicyForm(settingsValueJsonForm); - if (featureFlipper.isDrawerEnabled()) { + if (backendConfig.isDrawerEnabled()) { // drawer type will be always supported assertNotNull(rhelPolicy); assertEquals(1, settingsValueJsonForm.bundles.size()); @@ -365,7 +356,7 @@ private void updateAndCheckUserPreference(String path, Header identityHeader, St assertEquals(expectedResult.contains(DAILY), initialValues.get(DAILY)); assertEquals(expectedResult.contains(INSTANT), initialValues.get(INSTANT)); - if (featureFlipper.isDrawerEnabled()) { + if (backendConfig.isDrawerEnabled()) { assertEquals(expectedResult.contains(DRAWER), initialValues.get(DRAWER)); } @@ -381,7 +372,7 @@ private void updateAndCheckUserPreference(String path, Header identityHeader, St Map notificationPreferenes = extractNotificationValues(preferences.eventTypes, bundle, application, eventType); assertEquals(expectedResult.contains(DAILY), notificationPreferenes.get(DAILY)); assertEquals(expectedResult.contains(INSTANT), notificationPreferenes.get(INSTANT)); - if (featureFlipper.isDrawerEnabled()) { + if (backendConfig.isDrawerEnabled()) { assertEquals(expectedResult.contains(DRAWER), notificationPreferenes.get(DRAWER)); } } diff --git a/common/pom.xml b/common/pom.xml index aa71fb5b43..738bceb402 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -44,6 +44,13 @@ quarkus-smallrye-fault-tolerance
+ + + io.quarkiverse.unleash + quarkus-unleash + ${quarkus-unleash.version} + + com.redhat.cloud.common diff --git a/common/src/main/java/com/redhat/cloud/notifications/config/FeatureFlipper.java b/common/src/main/java/com/redhat/cloud/notifications/config/FeatureFlipper.java deleted file mode 100644 index 0a4e623319..0000000000 --- a/common/src/main/java/com/redhat/cloud/notifications/config/FeatureFlipper.java +++ /dev/null @@ -1,241 +0,0 @@ -package com.redhat.cloud.notifications.config; - -import io.quarkus.logging.Log; -import io.quarkus.runtime.StartupEvent; -import io.quarkus.runtime.configuration.ProfileManager; -import jakarta.enterprise.context.ApplicationScoped; -import jakarta.enterprise.event.Observes; -import org.eclipse.microprofile.config.inject.ConfigProperty; - -import static io.quarkus.runtime.LaunchMode.TEST; - -/** - *

- * This class centralizes all configuration values used to enable or disable a feature. - *

- *

- * Any config value used to flip a temporary or permanent feature can be added that way: - *

- * @ApplicationScoped
- * public class FeatureFlipper {
- *
- *     @ConfigProperty(name = "amazing-feature.enabled", defaultValue = "false")
- *     boolean amazingFeatureEnabled;
- *
- *     public boolean isAmazingFeatureEnabled() {
- *         return amazingFeatureEnabled;
- *     }
- *
- *     public void setAmazingFeatureEnabled(boolean amazingFeatureEnabled) {
- *         // Add this if the config value should only be overridden in TEST launch mode.
- *         checkTestLaunchMode();
- *         this.amazingFeatureEnabled = amazingFeatureEnabled;
- *     }
- * }
- * 
- *

- */ -@ApplicationScoped -public class FeatureFlipper { - - @ConfigProperty(name = "notifications.enforce-bg-name-unicity", defaultValue = "false") - boolean enforceBehaviorGroupNameUnicity; - - @ConfigProperty(name = "notifications.enforce-integration-name-unicity", defaultValue = "false") - boolean enforceIntegrationNameUnicity; - - @ConfigProperty(name = "notifications.kafka-consumed-total-checker.enabled", defaultValue = "false") - boolean kafkaConsumedTotalCheckerEnabled; - - @ConfigProperty(name = "notifications.use-default-template", defaultValue = "false") - boolean useDefaultTemplate; - - @ConfigProperty(name = "notifications.use-rbac-for-fetching-users", defaultValue = "false") - boolean useRbacForFetchingUsers; - - @ConfigProperty(name = "notifications.emails-only-mode.enabled", defaultValue = "false") - boolean emailsOnlyMode; - - @ConfigProperty(name = "notifications.use-secured-email-templates.enabled", defaultValue = "false") - boolean useSecuredEmailTemplates; - - @ConfigProperty(name = "notifications.instant-emails.enabled", defaultValue = "false") - boolean instantEmailsEnabled; - - @ConfigProperty(name = "mp.messaging.incoming.exportrequests.enabled", defaultValue = "false") - boolean exportServiceIntegrationEnabled; - - @ConfigProperty(name = "notifications.drawer.enabled", defaultValue = "false") - boolean drawerEnabled; - - @ConfigProperty(name = "notifications.use-mbop-for-fetching-users", defaultValue = "false") - boolean useMBOPForFetchingUsers; - - @ConfigProperty(name = "notifications.async-aggregation.enabled", defaultValue = "true") - boolean asyncAggregation; - - @ConfigProperty(name = "processor.email.aggregation.use-recipients-resolver-clowdapp.enabled", defaultValue = "false") - boolean useRecipientsResolverClowdappForDailyDigestEnabled; - - @ConfigProperty(name = "notifications.email.hcc-sender-name.enabled", defaultValue = "false") - boolean hccEmailSenderNameEnabled; - - @ConfigProperty(name = "notifications.slack.forbid.channel.usage.enabled", defaultValue = "false") - boolean slackForbidChannelUsageEnabled; - - void logFeaturesStatusAtStartup(@Observes StartupEvent event) { - Log.infof("=== %s startup status ===", FeatureFlipper.class.getSimpleName()); - Log.infof("The behavior groups unique name constraint is %s", enforceBehaviorGroupNameUnicity ? "enabled" : "disabled"); - Log.infof("The integrations unique name constraint is %s", enforceIntegrationNameUnicity ? "enabled" : "disabled"); - Log.infof("The Kafka outage detector is %s", kafkaConsumedTotalCheckerEnabled ? "enabled" : "disabled"); - Log.infof("The use of default templates is %s", useDefaultTemplate ? "enabled" : "disabled"); - Log.infof("The use of rbac for fetching users is %s", useRbacForFetchingUsers ? "enabled" : "disabled"); - Log.infof("Emails only mode is %s", emailsOnlyMode ? "enabled" : "disabled"); - Log.infof("The use of secured email templates is %s", useSecuredEmailTemplates ? "enabled" : "disabled"); - Log.infof("Instant emails are %s", instantEmailsEnabled ? "enabled" : "disabled"); - Log.infof("The integration with the export service is %s", exportServiceIntegrationEnabled ? "enabled" : "disabled"); - Log.infof("Drawer feature is %s", drawerEnabled ? "enabled" : "disabled"); - Log.infof("The use of BOP/MBOP for fetching users is %s", useMBOPForFetchingUsers ? "enabled" : "disabled"); - Log.infof("The async aggregation is %s", asyncAggregation ? "enabled" : "disabled"); - Log.infof("The Recipients resolver usage for daily digest is %s", useRecipientsResolverClowdappForDailyDigestEnabled ? "enabled" : "disabled"); - Log.infof("HCC sender name is %s in emails", hccEmailSenderNameEnabled ? "enabled" : "disabled"); - Log.infof("The disable of channel field on Slack is %s", slackForbidChannelUsageEnabled ? "enabled" : "disabled"); - } - - public boolean isEnforceBehaviorGroupNameUnicity() { - return enforceBehaviorGroupNameUnicity; - } - - public void setEnforceBehaviorGroupNameUnicity(boolean enforceBehaviorGroupNameUnicity) { - checkTestLaunchMode(); - this.enforceBehaviorGroupNameUnicity = enforceBehaviorGroupNameUnicity; - } - - public boolean isEnforceIntegrationNameUnicity() { - return enforceIntegrationNameUnicity; - } - - public void setEnforceIntegrationNameUnicity(boolean enforceIntegrationNameUnicity) { - checkTestLaunchMode(); - this.enforceIntegrationNameUnicity = enforceIntegrationNameUnicity; - } - - public boolean isKafkaConsumedTotalCheckerEnabled() { - return kafkaConsumedTotalCheckerEnabled; - } - - public void setKafkaConsumedTotalCheckerEnabled(boolean kafkaConsumedTotalCheckerEnabled) { - // It's ok to override this config value at runtime. - this.kafkaConsumedTotalCheckerEnabled = kafkaConsumedTotalCheckerEnabled; - } - - public boolean isUseDefaultTemplate() { - return useDefaultTemplate; - } - - public void setUseDefaultTemplate(boolean useDefaultTemplate) { - checkTestLaunchMode(); - this.useDefaultTemplate = useDefaultTemplate; - } - - public boolean isUseRbacForFetchingUsers() { - return this.useRbacForFetchingUsers; - } - - public void setUseRbacForFetchingUsers(boolean useRbacForFetchingUsers) { - checkTestLaunchMode(); - this.useRbacForFetchingUsers = useRbacForFetchingUsers; - } - - public boolean isEmailsOnlyMode() { - return emailsOnlyMode; - } - - public void setEmailsOnlyMode(boolean emailsOnlyMode) { - checkTestLaunchMode(); - this.emailsOnlyMode = emailsOnlyMode; - } - - public boolean isUseSecuredEmailTemplates() { - return useSecuredEmailTemplates; - } - - public void setUseSecuredEmailTemplates(boolean useSecuredEmailTemplates) { - checkTestLaunchMode(); - this.useSecuredEmailTemplates = useSecuredEmailTemplates; - } - - public boolean isInstantEmailsEnabled() { - return instantEmailsEnabled; - } - - public void setInstantEmailsEnabled(boolean instantEmailsEnabled) { - checkTestLaunchMode(); - this.instantEmailsEnabled = instantEmailsEnabled; - } - - public boolean isDrawerEnabled() { - return drawerEnabled; - } - - public void setDrawerEnabled(boolean drawerEnabled) { - checkTestLaunchMode(); - this.drawerEnabled = drawerEnabled; - } - - public boolean isUseMBOPForFetchingUsers() { - return this.useMBOPForFetchingUsers; - } - - public void setUseMBOPForFetchingUsers(final boolean useMBOPForFetchingUsers) { - checkTestLaunchMode(); - this.useMBOPForFetchingUsers = useMBOPForFetchingUsers; - } - - public boolean isAsyncAggregation() { - return asyncAggregation; - } - - public void setAsyncAggregation(boolean asyncAggregation) { - checkTestLaunchMode(); - this.asyncAggregation = asyncAggregation; - } - - public boolean isUseRecipientsResolverClowdappForDailyDigestEnabled() { - return useRecipientsResolverClowdappForDailyDigestEnabled; - } - - public void setUseRecipientsResolverClowdappForDailyDigestEnabled(boolean useRecipientsResolverClowdappForDailyDigestEnabled) { - checkTestLaunchMode(); - this.useRecipientsResolverClowdappForDailyDigestEnabled = useRecipientsResolverClowdappForDailyDigestEnabled; - } - - public boolean isHccEmailSenderNameEnabled() { - return hccEmailSenderNameEnabled; - } - - public void setHccEmailSenderNameEnabled(boolean hccEmailSenderNameEnabled) { - checkTestLaunchMode(); - this.hccEmailSenderNameEnabled = hccEmailSenderNameEnabled; - } - - public boolean isSlackForbidChannelUsageEnabled() { - return slackForbidChannelUsageEnabled; - } - - public void setSlackForbidChannelUsageEnabled(boolean slackForbidChannelUsageEnabled) { - checkTestLaunchMode(); - this.slackForbidChannelUsageEnabled = slackForbidChannelUsageEnabled; - } - - /** - * This method throws an {@link IllegalStateException} if it is invoked with a launch mode different from - * {@link io.quarkus.runtime.LaunchMode#TEST TEST}. It should be added to methods that allow overriding a - * config value from tests only, preventing doing so from runtime code. - */ - public static void checkTestLaunchMode() { - if (ProfileManager.getLaunchMode() != TEST) { - throw new IllegalStateException("Illegal config value override detected"); - } - } -} diff --git a/connector-common-authentication/src/test/resources/application.properties b/connector-common-authentication/src/test/resources/application.properties index b5f59b9355..d4b114467a 100644 --- a/connector-common-authentication/src/test/resources/application.properties +++ b/connector-common-authentication/src/test/resources/application.properties @@ -23,3 +23,7 @@ quarkus.rest-client.sources.url=http://localhost:8000 mp.messaging.tocamel.topic=platform.notifications.tocamel mp.messaging.fromcamel.topic=platform.notifications.fromcamel + +quarkus.unleash.active=false +quarkus.unleash.synchronous-fetch-on-initialisation=true +quarkus.unleash.url=http://localhost:4242 diff --git a/connector-common-http/src/main/java/com/redhat/cloud/notifications/connector/http/HttpConnectorConfig.java b/connector-common-http/src/main/java/com/redhat/cloud/notifications/connector/http/HttpConnectorConfig.java index 675f70bb88..ec7c56ab10 100644 --- a/connector-common-http/src/main/java/com/redhat/cloud/notifications/connector/http/HttpConnectorConfig.java +++ b/connector-common-http/src/main/java/com/redhat/cloud/notifications/connector/http/HttpConnectorConfig.java @@ -12,6 +12,9 @@ @ApplicationScoped public class HttpConnectorConfig extends ConnectorConfig { + /* + * Env vars configuration + */ private static final String CLIENT_ERROR_LOG_LEVEL = "notifications.connector.http.client-error.log-level"; private static final String COMPONENTS = "notifications.connector.http.components"; private static final String CONNECT_TIMEOUT_MS = "notifications.connector.http.connect-timeout-ms"; @@ -22,6 +25,11 @@ public class HttpConnectorConfig extends ConnectorConfig { private static final String SERVER_ERROR_LOG_LEVEL = "notifications.connector.http.server-error.log-level"; private static final String SOCKET_TIMEOUT_MS = "notifications.connector.http.socket-timeout-ms"; + /* + * Unleash configuration + */ + private final String disableFaultyEndpointsToggleName = toggleName("disable-faulty-endpoints"); + @ConfigProperty(name = CLIENT_ERROR_LOG_LEVEL, defaultValue = "DEBUG") Level clientErrorLogLevel; @@ -35,6 +43,7 @@ public class HttpConnectorConfig extends ConnectorConfig { int httpConnectionsPerRoute; @ConfigProperty(name = DISABLE_FAULTY_ENDPOINTS, defaultValue = "true") + @Deprecated(forRemoval = true, since = "To be removed when we're done migrating to Unleash in all environments") boolean disableFaultyEndpoints; @ConfigProperty(name = FOLLOW_REDIRECTS, defaultValue = "false") @@ -56,7 +65,7 @@ protected Map getLoggedConfiguration() { config.put(COMPONENTS, httpComponents); config.put(CONNECT_TIMEOUT_MS, httpConnectTimeout); config.put(CONNECTIONS_PER_ROUTE, httpConnectionsPerRoute); - config.put(DISABLE_FAULTY_ENDPOINTS, disableFaultyEndpoints); + config.put(disableFaultyEndpointsToggleName, isDisableFaultyEndpoints()); config.put(FOLLOW_REDIRECTS, followRedirects); config.put(MAX_TOTAL_CONNECTIONS, httpMaxTotalConnections); config.put(SERVER_ERROR_LOG_LEVEL, serverErrorLogLevel); @@ -81,7 +90,11 @@ public int getHttpConnectionsPerRoute() { } public boolean isDisableFaultyEndpoints() { - return disableFaultyEndpoints; + if (unleashEnabled) { + return unleash.isEnabled(disableFaultyEndpointsToggleName, true); + } else { + return disableFaultyEndpoints; + } } public boolean isFollowRedirects() { diff --git a/connector-common-http/src/test/resources/application.properties b/connector-common-http/src/test/resources/application.properties index 54a5c64b38..e6a2eb06ed 100644 --- a/connector-common-http/src/test/resources/application.properties +++ b/connector-common-http/src/test/resources/application.properties @@ -21,3 +21,7 @@ camel.context.name=notifications-connector-common-http mp.messaging.tocamel.topic=platform.notifications.tocamel mp.messaging.fromcamel.topic=platform.notifications.fromcamel + +quarkus.unleash.active=false +quarkus.unleash.synchronous-fetch-on-initialisation=true +quarkus.unleash.url=http://localhost:4242 diff --git a/connector-common/pom.xml b/connector-common/pom.xml index 7b6917ceae..50323e82a9 100644 --- a/connector-common/pom.xml +++ b/connector-common/pom.xml @@ -93,6 +93,11 @@ quarkus-logging-sentry ${quarkus-logging-sentry.version}
+ + io.quarkiverse.unleash + quarkus-unleash + ${quarkus-unleash.version} + diff --git a/connector-common/src/main/java/com/redhat/cloud/notifications/connector/ConnectorConfig.java b/connector-common/src/main/java/com/redhat/cloud/notifications/connector/ConnectorConfig.java index 1a5014b993..828133ff49 100644 --- a/connector-common/src/main/java/com/redhat/cloud/notifications/connector/ConnectorConfig.java +++ b/connector-common/src/main/java/com/redhat/cloud/notifications/connector/ConnectorConfig.java @@ -1,16 +1,16 @@ package com.redhat.cloud.notifications.connector; +import io.getunleash.Unleash; import io.quarkus.arc.DefaultBean; import io.quarkus.logging.Log; import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; import org.eclipse.microprofile.config.inject.ConfigProperty; import java.util.List; import java.util.Map; import java.util.TreeMap; -import static java.util.Map.Entry; - @ApplicationScoped @DefaultBean public class ConnectorConfig { @@ -30,6 +30,11 @@ public class ConnectorConfig { private static final String SEDA_CONCURRENT_CONSUMERS = "notifications.connector.seda.concurrent-consumers"; private static final String SEDA_QUEUE_SIZE = "notifications.connector.seda.queue-size"; private static final String SUPPORTED_CONNECTOR_HEADERS = "notifications.connector.supported-connector-headers"; + private static final String UNLEASH = "notifications.unleash.enabled"; + + @ConfigProperty(name = UNLEASH, defaultValue = "false") + @Deprecated(forRemoval = true, since = "To be removed when we're done migrating to Unleash in all environments") + protected boolean unleashEnabled; @ConfigProperty(name = ENDPOINT_CACHE_MAX_SIZE, defaultValue = "100") int endpointCacheMaxSize; @@ -80,11 +85,18 @@ public class ConnectorConfig { @ConfigProperty(name = SUPPORTED_CONNECTOR_HEADERS) List supportedConnectorHeaders; + @Inject + protected Unleash unleash; + public void log() { - Log.info("=== Connector configuration ==="); - for (Entry configEntry : getLoggedConfiguration().entrySet()) { - Log.infof("%s=%s", configEntry.getKey(), configEntry.getValue()); - } + Log.info("=== Startup configuration ==="); + getLoggedConfiguration().forEach((key, value) -> { + Log.infof("%s=%s", key, value); + }); + } + + protected String toggleName(String feature) { + return String.format("notifications-connector-%s.%s.enabled", getConnectorName(), feature); } protected Map getLoggedConfiguration() { @@ -104,6 +116,7 @@ protected Map getLoggedConfiguration() { config.put(SEDA_CONCURRENT_CONSUMERS, sedaConcurrentConsumers); config.put(SEDA_QUEUE_SIZE, sedaQueueSize); config.put(SUPPORTED_CONNECTOR_HEADERS, supportedConnectorHeaders); + config.put(UNLEASH, unleashEnabled); return config; } diff --git a/connector-common/src/test/resources/application.properties b/connector-common/src/test/resources/application.properties index 0b67634e68..908eb08314 100644 --- a/connector-common/src/test/resources/application.properties +++ b/connector-common/src/test/resources/application.properties @@ -21,3 +21,7 @@ camel.context.name=notifications-connector-common mp.messaging.tocamel.topic=platform.notifications.tocamel mp.messaging.fromcamel.topic=platform.notifications.fromcamel + +quarkus.unleash.active=false +quarkus.unleash.synchronous-fetch-on-initialisation=true +quarkus.unleash.url=http://localhost:4242 diff --git a/connector-drawer/src/main/resources/application.properties b/connector-drawer/src/main/resources/application.properties index a616bcffe6..40ff612812 100644 --- a/connector-drawer/src/main/resources/application.properties +++ b/connector-drawer/src/main/resources/application.properties @@ -45,3 +45,7 @@ mp.messaging.todrawer.topic=platform.chrome # The URL for the recipients-resolver. notifications.connector.recipients-resolver.url=${clowder.endpoints.notifications-recipients-resolver-service.url:http://localhost:9008} + +quarkus.unleash.active=false +quarkus.unleash.synchronous-fetch-on-initialisation=true +quarkus.unleash.url=http://localhost:4242 diff --git a/connector-email/src/main/resources/application.properties b/connector-email/src/main/resources/application.properties index 52266943b5..c64d56003e 100644 --- a/connector-email/src/main/resources/application.properties +++ b/connector-email/src/main/resources/application.properties @@ -48,3 +48,7 @@ notifications.connector.user-provider.bop.url=https://backoffice-proxy.apps.ext. notifications.connector.recipients-resolver.url=${clowder.endpoints.notifications-recipients-resolver-service.url:http://localhost:9008} %test.notifications.connector.max-recipients-per-email=4 + +quarkus.unleash.active=false +quarkus.unleash.synchronous-fetch-on-initialisation=true +quarkus.unleash.url=http://localhost:4242 diff --git a/connector-google-chat/src/main/resources/application.properties b/connector-google-chat/src/main/resources/application.properties index b8726c1614..88473cd431 100644 --- a/connector-google-chat/src/main/resources/application.properties +++ b/connector-google-chat/src/main/resources/application.properties @@ -33,3 +33,7 @@ camel.context.name=notifications-connector-google-chat mp.messaging.tocamel.topic=platform.notifications.tocamel mp.messaging.fromcamel.topic=platform.notifications.fromcamel + +quarkus.unleash.active=false +quarkus.unleash.synchronous-fetch-on-initialisation=true +quarkus.unleash.url=http://localhost:4242 diff --git a/connector-microsoft-teams/src/main/resources/application.properties b/connector-microsoft-teams/src/main/resources/application.properties index ff5bbdc8bf..5304b54e85 100644 --- a/connector-microsoft-teams/src/main/resources/application.properties +++ b/connector-microsoft-teams/src/main/resources/application.properties @@ -33,3 +33,7 @@ camel.context.name=notifications-connector-microsoft-teams mp.messaging.tocamel.topic=platform.notifications.tocamel mp.messaging.fromcamel.topic=platform.notifications.fromcamel + +quarkus.unleash.active=false +quarkus.unleash.synchronous-fetch-on-initialisation=true +quarkus.unleash.url=http://localhost:4242 diff --git a/connector-servicenow/src/main/resources/application.properties b/connector-servicenow/src/main/resources/application.properties index 1f5ee589ee..79b476f8f0 100644 --- a/connector-servicenow/src/main/resources/application.properties +++ b/connector-servicenow/src/main/resources/application.properties @@ -42,3 +42,7 @@ quarkus.rest-client.sources.url=${clowder.endpoints.sources-api-svc.url:http://l quarkus.rest-client.sources.trust-store=${clowder.endpoints.sources-api-svc.trust-store-path} quarkus.rest-client.sources.trust-store-password=${clowder.endpoints.sources-api-svc.trust-store-password} quarkus.rest-client.sources.trust-store-type=${clowder.endpoints.sources-api-svc.trust-store-type} + +quarkus.unleash.active=false +quarkus.unleash.synchronous-fetch-on-initialisation=true +quarkus.unleash.url=http://localhost:4242 diff --git a/connector-slack/src/main/resources/application.properties b/connector-slack/src/main/resources/application.properties index 07b85164ee..cfd59232c8 100644 --- a/connector-slack/src/main/resources/application.properties +++ b/connector-slack/src/main/resources/application.properties @@ -35,3 +35,7 @@ mp.messaging.tocamel.topic=platform.notifications.tocamel mp.messaging.fromcamel.topic=platform.notifications.fromcamel %test.quarkus.log.category."com.redhat.cloud.notifications".level=DEBUG + +quarkus.unleash.active=false +quarkus.unleash.synchronous-fetch-on-initialisation=true +quarkus.unleash.url=http://localhost:4242 diff --git a/connector-splunk/src/main/resources/application.properties b/connector-splunk/src/main/resources/application.properties index 9cf85c57a2..97a0a38c17 100644 --- a/connector-splunk/src/main/resources/application.properties +++ b/connector-splunk/src/main/resources/application.properties @@ -42,3 +42,7 @@ quarkus.rest-client.sources.url=${clowder.endpoints.sources-api-svc.url:http://l quarkus.rest-client.sources.trust-store=${clowder.endpoints.sources-api-svc.trust-store-path} quarkus.rest-client.sources.trust-store-password=${clowder.endpoints.sources-api-svc.trust-store-password} quarkus.rest-client.sources.trust-store-type=${clowder.endpoints.sources-api-svc.trust-store-type} + +quarkus.unleash.active=false +quarkus.unleash.synchronous-fetch-on-initialisation=true +quarkus.unleash.url=http://localhost:4242 diff --git a/connector-webhook/src/main/resources/application.properties b/connector-webhook/src/main/resources/application.properties index b28278cfc1..227b7815a9 100644 --- a/connector-webhook/src/main/resources/application.properties +++ b/connector-webhook/src/main/resources/application.properties @@ -42,3 +42,7 @@ quarkus.rest-client.sources.url=${clowder.endpoints.sources-api-svc.url:http://l quarkus.rest-client.sources.trust-store=${clowder.endpoints.sources-api-svc.trust-store-path} quarkus.rest-client.sources.trust-store-password=${clowder.endpoints.sources-api-svc.trust-store-password} quarkus.rest-client.sources.trust-store-type=${clowder.endpoints.sources-api-svc.trust-store-type} + +quarkus.unleash.active=false +quarkus.unleash.synchronous-fetch-on-initialisation=true +quarkus.unleash.url=http://localhost:4242 diff --git a/engine/pom.xml b/engine/pom.xml index 9bad9bc7e6..42107302ce 100644 --- a/engine/pom.xml +++ b/engine/pom.xml @@ -126,13 +126,6 @@ ${quarkus-logging-cloudwatch.version} - - - io.quarkiverse.unleash - quarkus-unleash - 1.4.0 - - dev.failsafe diff --git a/engine/src/main/java/com/redhat/cloud/notifications/EngineConfig.java b/engine/src/main/java/com/redhat/cloud/notifications/EngineConfig.java deleted file mode 100644 index 37f7499fc1..0000000000 --- a/engine/src/main/java/com/redhat/cloud/notifications/EngineConfig.java +++ /dev/null @@ -1,125 +0,0 @@ -package com.redhat.cloud.notifications; - -import com.redhat.cloud.notifications.config.FeatureFlipper; -import io.getunleash.Unleash; -import io.getunleash.UnleashContext; -import io.quarkus.logging.Log; -import jakarta.enterprise.context.ApplicationScoped; -import jakarta.enterprise.event.Observes; -import jakarta.enterprise.event.Startup; -import jakarta.inject.Inject; -import org.eclipse.microprofile.config.inject.ConfigProperty; - -import java.util.Map; -import java.util.function.Supplier; - -@ApplicationScoped -public class EngineConfig { - - // TODO Remove this when we're fully migrated to Unleash in all environments. - @ConfigProperty(name = "notifications.unleash.enabled", defaultValue = "false") - boolean unleashEnabled; - - @Inject - Unleash unleash; - - // TODO Remove this when we're fully migrated to Unleash in all environments. - @Inject - FeatureFlipper featureFlipper; - - private static String toggleName(String feature) { - return String.format("notifications-engine.%s.enabled", feature); - } - - // Unleash toggles names. - private static final String AGGREGATION_WITH_RECIPIENTS_RESOLVER_ENABLED = toggleName("aggregation-with-recipients-resolver"); - private static final String ASYNC_AGGREGATION_ENABLED = toggleName("async-aggregation"); - private static final String DEFAULT_TEMPLATE_ENABLED = toggleName("default-template"); - private static final String DRAWER_ENABLED = toggleName("drawer"); - private static final String EMAILS_ONLY_MODE_ENABLED = toggleName("emails-only-mode"); - private static final String HCC_EMAIL_SENDER_NAME_ENABLED = toggleName("hcc-email-sender-name"); - private static final String KAFKA_CONSUMED_TOTAL_CHECKER_ENABLED = toggleName("kafka-consumed-total-checker"); - private static final String SECURED_EMAIL_TEMPLATES_ENABLED = toggleName("secured-email-templates"); - - private final Map> loggedToggles = Map.of( - AGGREGATION_WITH_RECIPIENTS_RESOLVER_ENABLED, this::isAggregationWithRecipientsResolverEnabled, - ASYNC_AGGREGATION_ENABLED, this::isAsyncAggregationEnabled, - DEFAULT_TEMPLATE_ENABLED, this::isDefaultTemplateEnabled, - DRAWER_ENABLED, this::isDrawerEnabled, - EMAILS_ONLY_MODE_ENABLED, this::isEmailsOnlyModeEnabled, - KAFKA_CONSUMED_TOTAL_CHECKER_ENABLED, this::isKafkaConsumedTotalCheckerEnabled, - SECURED_EMAIL_TEMPLATES_ENABLED, this::isSecuredEmailTemplatesEnabled - ); - - void logFeatureTogglesAtStartup(@Observes Startup event) { - Log.info("=== Feature toggles startup status ==="); - loggedToggles.forEach((name, value) -> { - Log.infof("%s=%s", name, value.get()); - }); - } - - public boolean isAggregationWithRecipientsResolverEnabled() { - if (unleashEnabled) { - return unleash.isEnabled(AGGREGATION_WITH_RECIPIENTS_RESOLVER_ENABLED, false); - } else { - return featureFlipper.isUseRecipientsResolverClowdappForDailyDigestEnabled(); - } - } - - public boolean isAsyncAggregationEnabled() { - if (unleashEnabled) { - return unleash.isEnabled(ASYNC_AGGREGATION_ENABLED, false); - } else { - return featureFlipper.isAsyncAggregation(); - } - } - - public boolean isDefaultTemplateEnabled() { - if (unleashEnabled) { - return unleash.isEnabled(DEFAULT_TEMPLATE_ENABLED, false); - } else { - return featureFlipper.isUseDefaultTemplate(); - } - } - - public boolean isDrawerEnabled() { - if (unleashEnabled) { - return unleash.isEnabled(DRAWER_ENABLED, false); - } else { - return featureFlipper.isDrawerEnabled(); - } - } - - public boolean isEmailsOnlyModeEnabled() { - if (unleashEnabled) { - return unleash.isEnabled(EMAILS_ONLY_MODE_ENABLED, true); - } else { - return featureFlipper.isEmailsOnlyMode(); - } - } - - public boolean isHccEmailSenderNameEnabled(String orgId) { - if (unleashEnabled) { - UnleashContext unleashContext = UnleashContext.builder().addProperty("orgId", orgId).build(); - return unleash.isEnabled(HCC_EMAIL_SENDER_NAME_ENABLED, unleashContext, false); - } else { - return featureFlipper.isHccEmailSenderNameEnabled(); - } - } - - public boolean isKafkaConsumedTotalCheckerEnabled() { - if (unleashEnabled) { - return unleash.isEnabled(KAFKA_CONSUMED_TOTAL_CHECKER_ENABLED, false); - } else { - return featureFlipper.isKafkaConsumedTotalCheckerEnabled(); - } - } - - public boolean isSecuredEmailTemplatesEnabled() { - if (unleashEnabled) { - return unleash.isEnabled(SECURED_EMAIL_TEMPLATES_ENABLED, true); - } else { - return featureFlipper.isUseSecuredEmailTemplates(); - } - } -} diff --git a/engine/src/main/java/com/redhat/cloud/notifications/config/EngineConfig.java b/engine/src/main/java/com/redhat/cloud/notifications/config/EngineConfig.java new file mode 100644 index 0000000000..a218f09435 --- /dev/null +++ b/engine/src/main/java/com/redhat/cloud/notifications/config/EngineConfig.java @@ -0,0 +1,166 @@ +package com.redhat.cloud.notifications.config; + +import io.getunleash.Unleash; +import io.getunleash.UnleashContext; +import io.quarkus.logging.Log; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.event.Observes; +import jakarta.enterprise.event.Startup; +import jakarta.inject.Inject; +import org.eclipse.microprofile.config.inject.ConfigProperty; + +import java.util.Map; +import java.util.TreeMap; + +@ApplicationScoped +public class EngineConfig { + + /* + * Env vars configuration + */ + private static final String DEFAULT_TEMPLATE = "notifications.use-default-template"; + private static final String EMAILS_ONLY_MODE = "notifications.emails-only-mode.enabled"; + private static final String SECURED_EMAIL_TEMPLATES = "notifications.use-secured-email-templates.enabled"; + private static final String UNLEASH = "notifications.unleash.enabled"; + + /* + * Unleash configuration + */ + private static final String AGGREGATION_WITH_RECIPIENTS_RESOLVER = toggleName("aggregation-with-recipients-resolver"); + private static final String ASYNC_AGGREGATION = toggleName("async-aggregation"); + private static final String DRAWER = toggleName("drawer"); + private static final String HCC_EMAIL_SENDER_NAME = toggleName("hcc-email-sender-name"); + private static final String KAFKA_CONSUMED_TOTAL_CHECKER = toggleName("kafka-consumed-total-checker"); + + private static String toggleName(String feature) { + return String.format("notifications-engine.%s.enabled", feature); + } + + @ConfigProperty(name = UNLEASH, defaultValue = "false") + @Deprecated(forRemoval = true, since = "To be removed when we're done migrating to Unleash in all environments") + boolean unleashEnabled; + + @ConfigProperty(name = "notifications.drawer.enabled", defaultValue = "false") + @Deprecated(forRemoval = true, since = "To be removed when we're done migrating to Unleash in all environments") + boolean drawerEnabled; + + @ConfigProperty(name = "notifications.async-aggregation.enabled", defaultValue = "true") + @Deprecated(forRemoval = true, since = "To be removed when we're done migrating to Unleash in all environments") + boolean asyncAggregation; + + @ConfigProperty(name = "processor.email.aggregation.use-recipients-resolver-clowdapp.enabled", defaultValue = "false") + @Deprecated(forRemoval = true, since = "To be removed when we're done migrating to Unleash in all environments") + boolean useRecipientsResolverClowdappForDailyDigestEnabled; + + @ConfigProperty(name = "notifications.kafka-consumed-total-checker.enabled", defaultValue = "false") + @Deprecated(forRemoval = true, since = "To be removed when we're done migrating to Unleash in all environments") + boolean kafkaConsumedTotalCheckerEnabled; + + @ConfigProperty(name = "notifications.email.hcc-sender-name.enabled", defaultValue = "false") + @Deprecated(forRemoval = true, since = "To be removed when we're done migrating to Unleash in all environments") + boolean hccEmailSenderNameEnabled; + + @ConfigProperty(name = "notifications.use-rbac-for-fetching-users", defaultValue = "false") + @Deprecated(forRemoval = true, since = "To be removed when we're done migrating to Unleash in all environments") + boolean useRbacForFetchingUsers; + + @ConfigProperty(name = "notifications.use-mbop-for-fetching-users", defaultValue = "false") + @Deprecated(forRemoval = true, since = "To be removed when we're done migrating to Unleash in all environments") + boolean useMBOPForFetchingUsers; + + // Only used in stage environments. + @ConfigProperty(name = DEFAULT_TEMPLATE, defaultValue = "false") + boolean defaultTemplateEnabled; + + // Only used in special environments. + @ConfigProperty(name = EMAILS_ONLY_MODE, defaultValue = "false") + boolean emailsOnlyModeEnabled; + + // Only used in special environments. + @ConfigProperty(name = SECURED_EMAIL_TEMPLATES, defaultValue = "false") + boolean useSecuredEmailTemplates; + + @Inject + Unleash unleash; + + void logConfigAtStartup(@Observes Startup event) { + + Map config = new TreeMap<>(); + config.put(AGGREGATION_WITH_RECIPIENTS_RESOLVER, isAggregationWithRecipientsResolverEnabled()); + config.put(ASYNC_AGGREGATION, isAsyncAggregationEnabled()); + config.put(DEFAULT_TEMPLATE, isDefaultTemplateEnabled()); + config.put(DRAWER, isDrawerEnabled()); + config.put(EMAILS_ONLY_MODE, isEmailsOnlyModeEnabled()); + config.put(KAFKA_CONSUMED_TOTAL_CHECKER, isKafkaConsumedTotalCheckerEnabled()); + config.put(SECURED_EMAIL_TEMPLATES, isSecuredEmailTemplatesEnabled()); + config.put(UNLEASH, unleashEnabled); + + Log.info("=== Startup configuration ==="); + config.forEach((key, value) -> { + Log.infof("%s=%s", key, value); + }); + } + + public boolean isAggregationWithRecipientsResolverEnabled() { + if (unleashEnabled) { + return unleash.isEnabled(AGGREGATION_WITH_RECIPIENTS_RESOLVER, false); + } else { + return useRecipientsResolverClowdappForDailyDigestEnabled; + } + } + + public boolean isAsyncAggregationEnabled() { + if (unleashEnabled) { + return unleash.isEnabled(ASYNC_AGGREGATION, false); + } else { + return asyncAggregation; + } + } + + public boolean isDefaultTemplateEnabled() { + return defaultTemplateEnabled; + } + + public boolean isDrawerEnabled() { + if (unleashEnabled) { + return unleash.isEnabled(DRAWER, false); + } else { + return drawerEnabled; + } + } + + public boolean isEmailsOnlyModeEnabled() { + return emailsOnlyModeEnabled; + } + + public boolean isHccEmailSenderNameEnabled(String orgId) { + if (unleashEnabled) { + UnleashContext unleashContext = UnleashContext.builder().addProperty("orgId", orgId).build(); + return unleash.isEnabled(HCC_EMAIL_SENDER_NAME, unleashContext, false); + } else { + return hccEmailSenderNameEnabled; + } + } + + public boolean isKafkaConsumedTotalCheckerEnabled() { + if (unleashEnabled) { + return unleash.isEnabled(KAFKA_CONSUMED_TOTAL_CHECKER, false); + } else { + return kafkaConsumedTotalCheckerEnabled; + } + } + + public boolean isSecuredEmailTemplatesEnabled() { + return useSecuredEmailTemplates; + } + + @Deprecated(forRemoval = true) + public boolean isUseMBOPForFetchingUsers() { + return useMBOPForFetchingUsers; + } + + @Deprecated(forRemoval = true) + public boolean isUseRbacForFetchingUsers() { + return useRbacForFetchingUsers; + } +} diff --git a/engine/src/main/java/com/redhat/cloud/notifications/db/repositories/TemplateRepository.java b/engine/src/main/java/com/redhat/cloud/notifications/db/repositories/TemplateRepository.java index 9eadcf4719..e5fc775d6d 100644 --- a/engine/src/main/java/com/redhat/cloud/notifications/db/repositories/TemplateRepository.java +++ b/engine/src/main/java/com/redhat/cloud/notifications/db/repositories/TemplateRepository.java @@ -1,6 +1,6 @@ package com.redhat.cloud.notifications.db.repositories; -import com.redhat.cloud.notifications.EngineConfig; +import com.redhat.cloud.notifications.config.EngineConfig; import com.redhat.cloud.notifications.models.AggregationEmailTemplate; import com.redhat.cloud.notifications.models.InstantEmailTemplate; import com.redhat.cloud.notifications.models.IntegrationTemplate; diff --git a/engine/src/main/java/com/redhat/cloud/notifications/events/ConnectorReceiver.java b/engine/src/main/java/com/redhat/cloud/notifications/events/ConnectorReceiver.java index 74d10d3911..d71939d444 100644 --- a/engine/src/main/java/com/redhat/cloud/notifications/events/ConnectorReceiver.java +++ b/engine/src/main/java/com/redhat/cloud/notifications/events/ConnectorReceiver.java @@ -1,6 +1,6 @@ package com.redhat.cloud.notifications.events; -import com.redhat.cloud.notifications.EngineConfig; +import com.redhat.cloud.notifications.config.EngineConfig; import com.redhat.cloud.notifications.db.repositories.NotificationHistoryRepository; import com.redhat.cloud.notifications.models.Endpoint; import com.redhat.cloud.notifications.processors.drawer.DrawerProcessor; diff --git a/engine/src/main/java/com/redhat/cloud/notifications/health/KafkaConsumedTotalChecker.java b/engine/src/main/java/com/redhat/cloud/notifications/health/KafkaConsumedTotalChecker.java index 63c32959ea..174ffa25af 100644 --- a/engine/src/main/java/com/redhat/cloud/notifications/health/KafkaConsumedTotalChecker.java +++ b/engine/src/main/java/com/redhat/cloud/notifications/health/KafkaConsumedTotalChecker.java @@ -1,7 +1,6 @@ package com.redhat.cloud.notifications.health; -import com.redhat.cloud.notifications.EngineConfig; -import com.redhat.cloud.notifications.config.FeatureFlipper; +import com.redhat.cloud.notifications.config.EngineConfig; import io.micrometer.core.instrument.FunctionCounter; import io.micrometer.core.instrument.MeterRegistry; import io.quarkus.logging.Log; @@ -16,9 +15,6 @@ public class KafkaConsumedTotalChecker { private static final String COUNTER_NAME = "kafka.consumer.fetch.manager.records.consumed.total"; private static final String INGRESS_TOPIC = "platform.notifications.ingress"; - @Inject - FeatureFlipper featureFlipper; - @Inject EngineConfig engineConfig; @@ -35,15 +31,13 @@ void postConstruct() { .tag("client.id", "kafka-consumer-ingress") .functionCounter(); if (consumedTotalCounter == null) { - // This will help prevent a NPE throw if the metrics happens to change in our dependency. Log.warnf("%s counter not found, %s is disabled", COUNTER_NAME, KafkaConsumedTotalChecker.class.getSimpleName()); - featureFlipper.setKafkaConsumedTotalCheckerEnabled(false); } } @Scheduled(every = "${notifications.kafka-consumed-total-checker.period:5m}", delayed = "${notifications.kafka-consumed-total-checker.initial-delay:5m}") public void periodicCheck() { - if (engineConfig.isKafkaConsumedTotalCheckerEnabled()) { + if (engineConfig.isKafkaConsumedTotalCheckerEnabled() && consumedTotalCounter != null) { double currentTotal = consumedTotalCounter.count(); if (currentTotal == previousTotal) { Log.debugf("Kafka records consumed total check failed for topic '%s'", INGRESS_TOPIC); diff --git a/engine/src/main/java/com/redhat/cloud/notifications/processors/camel/CamelProcessor.java b/engine/src/main/java/com/redhat/cloud/notifications/processors/camel/CamelProcessor.java index b8996cd400..c8d35b8a6d 100644 --- a/engine/src/main/java/com/redhat/cloud/notifications/processors/camel/CamelProcessor.java +++ b/engine/src/main/java/com/redhat/cloud/notifications/processors/camel/CamelProcessor.java @@ -3,7 +3,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.redhat.cloud.notifications.DelayedThrower; -import com.redhat.cloud.notifications.EngineConfig; +import com.redhat.cloud.notifications.config.EngineConfig; import com.redhat.cloud.notifications.db.repositories.TemplateRepository; import com.redhat.cloud.notifications.models.CamelProperties; import com.redhat.cloud.notifications.models.Endpoint; diff --git a/engine/src/main/java/com/redhat/cloud/notifications/processors/drawer/DrawerProcessor.java b/engine/src/main/java/com/redhat/cloud/notifications/processors/drawer/DrawerProcessor.java index 83c08e169e..636ce0a0a4 100644 --- a/engine/src/main/java/com/redhat/cloud/notifications/processors/drawer/DrawerProcessor.java +++ b/engine/src/main/java/com/redhat/cloud/notifications/processors/drawer/DrawerProcessor.java @@ -2,7 +2,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; -import com.redhat.cloud.notifications.EngineConfig; +import com.redhat.cloud.notifications.config.EngineConfig; import com.redhat.cloud.notifications.db.repositories.BundleRepository; import com.redhat.cloud.notifications.db.repositories.DrawerNotificationRepository; import com.redhat.cloud.notifications.db.repositories.EndpointRepository; diff --git a/engine/src/main/java/com/redhat/cloud/notifications/processors/email/EmailActorsResolver.java b/engine/src/main/java/com/redhat/cloud/notifications/processors/email/EmailActorsResolver.java index 34a6a71673..196336962b 100644 --- a/engine/src/main/java/com/redhat/cloud/notifications/processors/email/EmailActorsResolver.java +++ b/engine/src/main/java/com/redhat/cloud/notifications/processors/email/EmailActorsResolver.java @@ -1,7 +1,6 @@ package com.redhat.cloud.notifications.processors.email; -import com.redhat.cloud.notifications.EngineConfig; -import com.redhat.cloud.notifications.config.FeatureFlipper; +import com.redhat.cloud.notifications.config.EngineConfig; import com.redhat.cloud.notifications.models.Event; import io.quarkus.logging.Log; import jakarta.enterprise.context.ApplicationScoped; @@ -102,7 +101,6 @@ private boolean isPendoMessageEnabled() { } public void setShowPendoUntil(LocalDate showPendoUntil) { - FeatureFlipper.checkTestLaunchMode(); this.showPendoUntil = showPendoUntil; } } diff --git a/engine/src/main/java/com/redhat/cloud/notifications/processors/email/EmailAggregator.java b/engine/src/main/java/com/redhat/cloud/notifications/processors/email/EmailAggregator.java index 307c1286bb..0cfa37fc93 100644 --- a/engine/src/main/java/com/redhat/cloud/notifications/processors/email/EmailAggregator.java +++ b/engine/src/main/java/com/redhat/cloud/notifications/processors/email/EmailAggregator.java @@ -1,6 +1,6 @@ package com.redhat.cloud.notifications.processors.email; -import com.redhat.cloud.notifications.EngineConfig; +import com.redhat.cloud.notifications.config.EngineConfig; import com.redhat.cloud.notifications.db.repositories.EmailAggregationRepository; import com.redhat.cloud.notifications.db.repositories.EndpointRepository; import com.redhat.cloud.notifications.db.repositories.EventTypeRepository; diff --git a/engine/src/main/java/com/redhat/cloud/notifications/processors/email/EmailSubscriptionTypeProcessor.java b/engine/src/main/java/com/redhat/cloud/notifications/processors/email/EmailSubscriptionTypeProcessor.java index 3ef2e69ac1..c6c464c407 100644 --- a/engine/src/main/java/com/redhat/cloud/notifications/processors/email/EmailSubscriptionTypeProcessor.java +++ b/engine/src/main/java/com/redhat/cloud/notifications/processors/email/EmailSubscriptionTypeProcessor.java @@ -1,7 +1,7 @@ package com.redhat.cloud.notifications.processors.email; import com.fasterxml.jackson.databind.ObjectMapper; -import com.redhat.cloud.notifications.EngineConfig; +import com.redhat.cloud.notifications.config.EngineConfig; import com.redhat.cloud.notifications.db.repositories.ApplicationRepository; import com.redhat.cloud.notifications.db.repositories.BundleRepository; import com.redhat.cloud.notifications.db.repositories.EmailAggregationRepository; diff --git a/engine/src/main/java/com/redhat/cloud/notifications/processors/eventing/EventingProcessor.java b/engine/src/main/java/com/redhat/cloud/notifications/processors/eventing/EventingProcessor.java index ed9cc70bfd..5af6a83240 100644 --- a/engine/src/main/java/com/redhat/cloud/notifications/processors/eventing/EventingProcessor.java +++ b/engine/src/main/java/com/redhat/cloud/notifications/processors/eventing/EventingProcessor.java @@ -1,7 +1,7 @@ package com.redhat.cloud.notifications.processors.eventing; import com.redhat.cloud.notifications.DelayedThrower; -import com.redhat.cloud.notifications.EngineConfig; +import com.redhat.cloud.notifications.config.EngineConfig; import com.redhat.cloud.notifications.models.CamelProperties; import com.redhat.cloud.notifications.models.Endpoint; import com.redhat.cloud.notifications.models.Event; diff --git a/engine/src/main/java/com/redhat/cloud/notifications/processors/webhooks/WebhookTypeProcessor.java b/engine/src/main/java/com/redhat/cloud/notifications/processors/webhooks/WebhookTypeProcessor.java index 361ca26683..b53a5e4119 100644 --- a/engine/src/main/java/com/redhat/cloud/notifications/processors/webhooks/WebhookTypeProcessor.java +++ b/engine/src/main/java/com/redhat/cloud/notifications/processors/webhooks/WebhookTypeProcessor.java @@ -1,7 +1,7 @@ package com.redhat.cloud.notifications.processors.webhooks; import com.redhat.cloud.notifications.DelayedThrower; -import com.redhat.cloud.notifications.EngineConfig; +import com.redhat.cloud.notifications.config.EngineConfig; import com.redhat.cloud.notifications.models.Endpoint; import com.redhat.cloud.notifications.models.Event; import com.redhat.cloud.notifications.models.WebhookProperties; diff --git a/engine/src/main/java/com/redhat/cloud/notifications/recipients/rbac/RbacRecipientUsersProvider.java b/engine/src/main/java/com/redhat/cloud/notifications/recipients/rbac/RbacRecipientUsersProvider.java index 076b5f11f5..5a7c3db6a7 100644 --- a/engine/src/main/java/com/redhat/cloud/notifications/recipients/rbac/RbacRecipientUsersProvider.java +++ b/engine/src/main/java/com/redhat/cloud/notifications/recipients/rbac/RbacRecipientUsersProvider.java @@ -1,6 +1,6 @@ package com.redhat.cloud.notifications.recipients.rbac; -import com.redhat.cloud.notifications.config.FeatureFlipper; +import com.redhat.cloud.notifications.config.EngineConfig; import com.redhat.cloud.notifications.recipients.User; import com.redhat.cloud.notifications.recipients.itservice.ITUserService; import com.redhat.cloud.notifications.recipients.itservice.pojo.request.ITUserRequest; @@ -56,7 +56,7 @@ public class RbacRecipientUsersProvider { ITUserService itUserService; @Inject - FeatureFlipper featureFlipper; + EngineConfig engineConfig; @ConfigProperty(name = "recipient-provider.rbac.elements-per-page", defaultValue = "1000") Integer rbacElementsPerPage; @@ -171,10 +171,10 @@ public List getUsers(String orgId, boolean adminsOnly) { Timer.Sample getUsersTotalTimer = Timer.start(meterRegistry); List users; - if (featureFlipper.isUseRbacForFetchingUsers()) { + if (engineConfig.isUseRbacForFetchingUsers()) { users = getWithPagination( page -> retryOnRbacError(() -> rbacServiceToService.getUsers(orgId, adminsOnly, page * rbacElementsPerPage, rbacElementsPerPage))); - } else if (this.featureFlipper.isUseMBOPForFetchingUsers()) { + } else if (engineConfig.isUseMBOPForFetchingUsers()) { final List mbopUsers = new ArrayList<>(); // Keep the offset to ask for more users in case we need it. diff --git a/engine/src/main/java/com/redhat/cloud/notifications/templates/EmailTemplateMigrationService.java b/engine/src/main/java/com/redhat/cloud/notifications/templates/EmailTemplateMigrationService.java index ad59b6fb1e..2350a5b49e 100644 --- a/engine/src/main/java/com/redhat/cloud/notifications/templates/EmailTemplateMigrationService.java +++ b/engine/src/main/java/com/redhat/cloud/notifications/templates/EmailTemplateMigrationService.java @@ -1,7 +1,7 @@ package com.redhat.cloud.notifications.templates; import com.cronutils.utils.StringUtils; -import com.redhat.cloud.notifications.EngineConfig; +import com.redhat.cloud.notifications.config.EngineConfig; import com.redhat.cloud.notifications.models.AggregationEmailTemplate; import com.redhat.cloud.notifications.models.Application; import com.redhat.cloud.notifications.models.EventType; diff --git a/engine/src/main/resources/application.properties b/engine/src/main/resources/application.properties index 5db07d4135..5879aee810 100644 --- a/engine/src/main/resources/application.properties +++ b/engine/src/main/resources/application.properties @@ -178,6 +178,8 @@ quarkus.cache.caffeine.get-bundle-by-id.expire-after-write=PT15M quarkus.cache.caffeine.get-app-by-name.expire-after-write=PT15M quarkus.cache.caffeine.aggregation-target-email-subscription-endpoints.expire-after-write=PT5M +quarkus.unleash.active=false +quarkus.unleash.synchronous-fetch-on-initialisation=true quarkus.unleash.url=http://localhost:4242 %test.notifications.email.show.pendo.until.date=2000-01-01 diff --git a/engine/src/test/java/com/redhat/cloud/notifications/EmailTemplatesInDbHelper.java b/engine/src/test/java/com/redhat/cloud/notifications/EmailTemplatesInDbHelper.java index 6d63543510..24c1f8d08a 100644 --- a/engine/src/test/java/com/redhat/cloud/notifications/EmailTemplatesInDbHelper.java +++ b/engine/src/test/java/com/redhat/cloud/notifications/EmailTemplatesInDbHelper.java @@ -1,6 +1,6 @@ package com.redhat.cloud.notifications; -import com.redhat.cloud.notifications.config.FeatureFlipper; +import com.redhat.cloud.notifications.config.EngineConfig; import com.redhat.cloud.notifications.db.ResourceHelpers; import com.redhat.cloud.notifications.db.repositories.TemplateRepository; import com.redhat.cloud.notifications.ingress.Action; @@ -17,10 +17,10 @@ import io.quarkus.mailer.Mail; import io.quarkus.mailer.Mailer; import io.quarkus.qute.TemplateInstance; +import io.quarkus.test.InjectMock; import io.quarkus.test.junit.mockito.InjectSpy; import jakarta.inject.Inject; import jakarta.persistence.NoResultException; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import java.time.LocalDateTime; @@ -30,6 +30,7 @@ import java.util.UUID; import static com.redhat.cloud.notifications.models.SubscriptionType.DAILY; +import static org.mockito.Mockito.when; public abstract class EmailTemplatesInDbHelper { @@ -54,10 +55,7 @@ public abstract class EmailTemplatesInDbHelper { @Inject protected TemplateService templateService; - @Inject - FeatureFlipper featureFlipper; - - @Inject + @InjectMock EngineConfig engineConfig; @Inject @@ -89,7 +87,7 @@ protected void initData() { } eventTypes.put(eventTypeToCreate, eventType.getId()); } - featureFlipper.setUseSecuredEmailTemplates(useSecuredTemplates()); + when(engineConfig.isSecuredEmailTemplatesEnabled()).thenReturn(useSecuredTemplates()); if (engineConfig.isSecuredEmailTemplatesEnabled()) { emailTemplateMigrationService.deleteAllTemplates(); } @@ -100,11 +98,6 @@ protected void migrate() { emailTemplateMigrationService.migrate(); } - @AfterEach - void restoreEnv() { - featureFlipper.setUseSecuredEmailTemplates(false); - } - protected String generateEmailSubject(String eventTypeStr, Action action) { return generateEmailSubject(eventTypeStr, (Object) action); } diff --git a/engine/src/test/java/com/redhat/cloud/notifications/processors/drawer/DrawerProcessorTest.java b/engine/src/test/java/com/redhat/cloud/notifications/processors/drawer/DrawerProcessorTest.java index 3f6deea101..d006f33812 100644 --- a/engine/src/test/java/com/redhat/cloud/notifications/processors/drawer/DrawerProcessorTest.java +++ b/engine/src/test/java/com/redhat/cloud/notifications/processors/drawer/DrawerProcessorTest.java @@ -1,6 +1,6 @@ package com.redhat.cloud.notifications.processors.drawer; -import com.redhat.cloud.notifications.config.FeatureFlipper; +import com.redhat.cloud.notifications.config.EngineConfig; import com.redhat.cloud.notifications.db.ResourceHelpers; import com.redhat.cloud.notifications.db.repositories.DrawerNotificationRepository; import com.redhat.cloud.notifications.db.repositories.NotificationHistoryRepository; @@ -72,8 +72,8 @@ class DrawerProcessorTest { @InjectSpy NotificationHistoryRepository notificationHistoryRepository; - @Inject - FeatureFlipper featureFlipper; + @InjectMock + EngineConfig engineConfig; @Inject @Any @@ -115,12 +115,9 @@ void shouldCreateTwoDrawerNotifications() { Endpoint endpoint = new Endpoint(); endpoint.setProperties(new SystemSubscriptionProperties()); endpoint.setType(EndpointType.DRAWER); - try { - featureFlipper.setDrawerEnabled(true); - testee.process(createdEvent, List.of(endpoint)); - } finally { - featureFlipper.setDrawerEnabled(false); - } + + when(engineConfig.isDrawerEnabled()).thenReturn(true); + testee.process(createdEvent, List.of(endpoint)); verify(notificationHistoryRepository, times(1)).createNotificationHistory(any(NotificationHistory.class)); diff --git a/engine/src/test/java/com/redhat/cloud/notifications/processors/email/EmailActorsResolverTest.java b/engine/src/test/java/com/redhat/cloud/notifications/processors/email/EmailActorsResolverTest.java index d3a46d3b04..5993da6514 100644 --- a/engine/src/test/java/com/redhat/cloud/notifications/processors/email/EmailActorsResolverTest.java +++ b/engine/src/test/java/com/redhat/cloud/notifications/processors/email/EmailActorsResolverTest.java @@ -1,10 +1,11 @@ package com.redhat.cloud.notifications.processors.email; -import com.redhat.cloud.notifications.config.FeatureFlipper; +import com.redhat.cloud.notifications.config.EngineConfig; import com.redhat.cloud.notifications.models.Application; import com.redhat.cloud.notifications.models.Bundle; import com.redhat.cloud.notifications.models.Event; import com.redhat.cloud.notifications.models.EventType; +import io.quarkus.test.InjectMock; import io.quarkus.test.junit.QuarkusTest; import jakarta.inject.Inject; import org.junit.jupiter.api.Test; @@ -20,6 +21,8 @@ import static com.redhat.cloud.notifications.processors.email.EmailActorsResolver.RH_INSIGHTS_SENDER; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.when; @QuarkusTest public class EmailActorsResolverTest { @@ -27,15 +30,15 @@ public class EmailActorsResolverTest { @Inject EmailActorsResolver emailActorsResolver; - @Inject - FeatureFlipper featureFlipper; + @InjectMock + EngineConfig engineConfig; /** * Tests that the "Red Hat Insights" sender is returned by default. */ @Test void testDefaultEmailSenderInsights() { - Event event = new Event(); + Event event = buildEvent(null, "rhel", "policies"); assertEquals(RH_INSIGHTS_SENDER, emailActorsResolver.getEmailSender(event), "unexpected email sender returned from the function under test"); } @@ -44,8 +47,8 @@ void testDefaultEmailSenderInsights() { */ @Test void testDefaultEmailSenderHCC() { - featureFlipper.setHccEmailSenderNameEnabled(true); - Event event = new Event(); + when(engineConfig.isHccEmailSenderNameEnabled(anyString())).thenReturn(true); + Event event = buildEvent(null, "rhel", "policies"); assertEquals(RH_HCC_SENDER, emailActorsResolver.getEmailSender(event), "unexpected email sender returned from the function under test"); } @@ -54,7 +57,7 @@ void testDefaultEmailSenderHCC() { */ @Test void testOpenshiftClusterManagerStageEmailSender() { - Event event = buildOCMEvent("stage"); + Event event = buildEvent("stage", "openshift", "cluster-manager"); assertEquals(OPENSHIFT_SENDER_STAGE, emailActorsResolver.getEmailSender(event), "unexpected email sender returned from the function under test"); } @@ -63,7 +66,7 @@ void testOpenshiftClusterManagerStageEmailSender() { */ @Test void testOpenshiftClusterManagerDefaultEmailSender() { - Event event = buildOCMEvent("prod"); + Event event = buildEvent("prod", "openshift", "cluster-manager"); assertEquals(OPENSHIFT_SENDER_PROD, emailActorsResolver.getEmailSender(event), "unexpected email sender returned from the function under test"); } @@ -72,7 +75,7 @@ void testOpenshiftClusterManagerDefaultEmailSender() { */ @Test void testDefaultEmailPendoMessage() { - Event event = new Event(); + Event event = buildEvent(null, "rhel", "policies"); assertNull(emailActorsResolver.getPendoEmailMessage(event), "unexpected email pendo message returned from the function under test"); try { @@ -89,7 +92,7 @@ void testDefaultEmailPendoMessage() { */ @Test void testOpenshiftClusterManagerPendoMessage() { - Event event = buildOCMEvent("prod"); + Event event = buildEvent("prod", "openshift", "cluster-manager"); assertNull(emailActorsResolver.getPendoEmailMessage(event), "unexpected email pendo message returned from the function under test"); try { @@ -97,7 +100,7 @@ void testOpenshiftClusterManagerPendoMessage() { assertEquals(String.format(OCM_PENDO_MESSAGE, "January 01, 2050"), emailActorsResolver.getPendoEmailMessage(event).getPendoMessage(), "unexpected email pendo message returned from the function under test"); assertEquals(OCM_PENDO_TITLE, emailActorsResolver.getPendoEmailMessage(event).getPendoTitle(), "unexpected email pendo title returned from the function under test"); - event = buildOCMEvent("stage"); + event = buildEvent("stage", "openshift", "cluster-manager"); assertEquals(String.format(OCM_PENDO_MESSAGE, "January 01, 2050"), emailActorsResolver.getPendoEmailMessage(event).getPendoMessage(), "unexpected email pendo message returned from the function under test"); assertEquals(OCM_PENDO_TITLE, emailActorsResolver.getPendoEmailMessage(event).getPendoTitle(), "unexpected email pendo title returned from the function under test"); @@ -106,19 +109,20 @@ void testOpenshiftClusterManagerPendoMessage() { } } - private static Event buildOCMEvent(String sourceEnvironment) { + private static Event buildEvent(String sourceEnvironment, String bundleName, String appName) { Bundle bundle = new Bundle(); - bundle.setName("openshift"); + bundle.setName(bundleName); Application app = new Application(); app.setBundle(bundle); - app.setName("cluster-manager"); + app.setName(appName); EventType eventType = new EventType(); eventType.setApplication(app); Event event = new Event(); + event.setOrgId("12345"); event.setEventType(eventType); event.setSourceEnvironment(sourceEnvironment); diff --git a/engine/src/test/java/com/redhat/cloud/notifications/processors/email/EmailAggregatorTest.java b/engine/src/test/java/com/redhat/cloud/notifications/processors/email/EmailAggregatorTest.java index 0fbb15aad7..bf751cdb8c 100644 --- a/engine/src/test/java/com/redhat/cloud/notifications/processors/email/EmailAggregatorTest.java +++ b/engine/src/test/java/com/redhat/cloud/notifications/processors/email/EmailAggregatorTest.java @@ -1,8 +1,7 @@ package com.redhat.cloud.notifications.processors.email; -import com.redhat.cloud.notifications.EngineConfig; import com.redhat.cloud.notifications.TestHelpers; -import com.redhat.cloud.notifications.config.FeatureFlipper; +import com.redhat.cloud.notifications.config.EngineConfig; import com.redhat.cloud.notifications.db.ResourceHelpers; import com.redhat.cloud.notifications.db.repositories.EmailAggregationRepository; import com.redhat.cloud.notifications.db.repositories.EndpointRepository; @@ -65,10 +64,7 @@ class EmailAggregatorTest { @RestClient RecipientsResolverService recipientsResolverService; - @Inject - FeatureFlipper featureFlipper; - - @Inject + @InjectMock EngineConfig engineConfig; @InjectSpy @@ -104,7 +100,7 @@ void afterEach() { @ValueSource(booleans = {true, false}) void shouldTestRecipientsFromSubscription(boolean useRecipientsResolverClowdappForDailyDigestEnabled) { - featureFlipper.setUseRecipientsResolverClowdappForDailyDigestEnabled(useRecipientsResolverClowdappForDailyDigestEnabled); + when(engineConfig.isAggregationWithRecipientsResolverEnabled()).thenReturn(useRecipientsResolverClowdappForDailyDigestEnabled); // init test environment application = resourceHelpers.findApp("rhel", "policies"); eventType1 = resourceHelpers.findOrCreateEventType(application.getId(), TestHelpers.eventType); @@ -165,7 +161,7 @@ void shouldTestRecipientsFromSubscription(boolean useRecipientsResolverClowdappF @Test void shouldTestFallbackOnLegacyRecipientsResolverFetching() { - featureFlipper.setUseRecipientsResolverClowdappForDailyDigestEnabled(true); + when(engineConfig.isAggregationWithRecipientsResolverEnabled()).thenReturn(true); // init test environment application = resourceHelpers.findApp("rhel", "policies"); eventType1 = resourceHelpers.findOrCreateEventType(application.getId(), TestHelpers.eventType); diff --git a/engine/src/test/java/com/redhat/cloud/notifications/processors/eventing/EventingTypeProcessorTest.java b/engine/src/test/java/com/redhat/cloud/notifications/processors/eventing/EventingTypeProcessorTest.java index 720f91cae8..68a89d5c72 100644 --- a/engine/src/test/java/com/redhat/cloud/notifications/processors/eventing/EventingTypeProcessorTest.java +++ b/engine/src/test/java/com/redhat/cloud/notifications/processors/eventing/EventingTypeProcessorTest.java @@ -2,7 +2,7 @@ import com.redhat.cloud.notifications.MicrometerAssertionHelper; import com.redhat.cloud.notifications.TestLifecycleManager; -import com.redhat.cloud.notifications.config.FeatureFlipper; +import com.redhat.cloud.notifications.config.EngineConfig; import com.redhat.cloud.notifications.db.ResourceHelpers; import com.redhat.cloud.notifications.db.repositories.NotificationHistoryRepository; import com.redhat.cloud.notifications.events.EventWrapperAction; @@ -48,6 +48,7 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; @QuarkusTest @QuarkusTestResource(TestLifecycleManager.class) @@ -103,8 +104,8 @@ class EventingTypeProcessorTest { @InjectMock NotificationHistoryRepository notificationHistoryRepository; - @Inject - FeatureFlipper featureFlipper; + @InjectMock + EngineConfig engineConfig; private InMemorySink inMemorySink; @@ -188,15 +189,10 @@ void testCamelEndpointProcessing() { @Test void testEmailsOnlyModeCamelProcessor() { - featureFlipper.setEmailsOnlyMode(true); - try { + when(engineConfig.isEmailsOnlyModeEnabled()).thenReturn(true); - camelProcessor.process(buildEvent(), List.of(new Endpoint())); - micrometerAssertionHelper.assertCounterIncrement(EventingProcessor.PROCESSED_COUNTER_NAME, 0); - - } finally { - featureFlipper.setEmailsOnlyMode(false); - } + camelProcessor.process(buildEvent(), List.of(new Endpoint())); + micrometerAssertionHelper.assertCounterIncrement(EventingProcessor.PROCESSED_COUNTER_NAME, 0); } private static Event buildEvent() { diff --git a/engine/src/test/java/com/redhat/cloud/notifications/processors/webhook/WebhookTest.java b/engine/src/test/java/com/redhat/cloud/notifications/processors/webhook/WebhookTest.java index 862cf82406..9fedb7f8d7 100644 --- a/engine/src/test/java/com/redhat/cloud/notifications/processors/webhook/WebhookTest.java +++ b/engine/src/test/java/com/redhat/cloud/notifications/processors/webhook/WebhookTest.java @@ -2,7 +2,7 @@ import com.redhat.cloud.notifications.MicrometerAssertionHelper; import com.redhat.cloud.notifications.TestLifecycleManager; -import com.redhat.cloud.notifications.config.FeatureFlipper; +import com.redhat.cloud.notifications.config.EngineConfig; import com.redhat.cloud.notifications.db.repositories.NotificationHistoryRepository; import com.redhat.cloud.notifications.events.EventWrapperAction; import com.redhat.cloud.notifications.ingress.Action; @@ -43,6 +43,7 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; @QuarkusTest @QuarkusTestResource(TestLifecycleManager.class) @@ -54,8 +55,8 @@ public class WebhookTest { @Inject MicrometerAssertionHelper micrometerAssertionHelper; - @Inject - FeatureFlipper featureFlipper; + @InjectMock + EngineConfig engineConfig; @InjectMock NotificationHistoryRepository notificationHistoryRepository; @@ -116,18 +117,13 @@ void testWebhookUsingConnector() { @Test void testEmailsOnlyMode() { - featureFlipper.setEmailsOnlyMode(true); - try { + when(engineConfig.isEmailsOnlyModeEnabled()).thenReturn(true); - Event event = new Event(); - event.setEventWrapper(new EventWrapperAction(buildWebhookAction())); - - webhookTypeProcessor.process(event, List.of(new Endpoint())); - micrometerAssertionHelper.assertCounterIncrement(PROCESSED_WEBHOOK_COUNTER, 0); + Event event = new Event(); + event.setEventWrapper(new EventWrapperAction(buildWebhookAction())); - } finally { - featureFlipper.setEmailsOnlyMode(false); - } + webhookTypeProcessor.process(event, List.of(new Endpoint())); + micrometerAssertionHelper.assertCounterIncrement(PROCESSED_WEBHOOK_COUNTER, 0); } private static Action buildWebhookAction() { diff --git a/engine/src/test/java/com/redhat/cloud/notifications/recipients/rbac/RbacRecipientUsersProviderTest.java b/engine/src/test/java/com/redhat/cloud/notifications/recipients/rbac/RbacRecipientUsersProviderTest.java index 00f417bdc2..163b23035a 100644 --- a/engine/src/test/java/com/redhat/cloud/notifications/recipients/rbac/RbacRecipientUsersProviderTest.java +++ b/engine/src/test/java/com/redhat/cloud/notifications/recipients/rbac/RbacRecipientUsersProviderTest.java @@ -1,7 +1,7 @@ package com.redhat.cloud.notifications.recipients.rbac; import com.redhat.cloud.notifications.TestConstants; -import com.redhat.cloud.notifications.config.FeatureFlipper; +import com.redhat.cloud.notifications.config.EngineConfig; import com.redhat.cloud.notifications.recipients.User; import com.redhat.cloud.notifications.recipients.itservice.ITUserService; import com.redhat.cloud.notifications.recipients.itservice.pojo.request.ITUserRequest; @@ -76,8 +76,8 @@ public class RbacRecipientUsersProviderTest { @RestClient MBOPService mbopService; - @Inject - FeatureFlipper featureFlipper; + @InjectMock + EngineConfig engineConfig; @Test void getGroupUsersShouldOnlyContainActiveUsers() { @@ -185,24 +185,20 @@ public void getAllUsersFromDefaultGroup() { @Test public void getAllUsersFromDefaultGroupRBAC() { - try { - featureFlipper.setUseRbacForFetchingUsers(true); - RbacGroup defaultGroup = new RbacGroup(); - defaultGroup.setPlatformDefault(true); - defaultGroup.setUuid(UUID.randomUUID()); + when(engineConfig.isUseRbacForFetchingUsers()).thenReturn(true); + RbacGroup defaultGroup = new RbacGroup(); + defaultGroup.setPlatformDefault(true); + defaultGroup.setUuid(UUID.randomUUID()); - int elements = 133; + int elements = 133; - mockGetGroup(defaultGroup); - mockGetUsersRBAC(elements, false); + mockGetGroup(defaultGroup); + mockGetUsersRBAC(elements, false); - List users = rbacRecipientUsersProvider.getGroupUsers(TestConstants.DEFAULT_ORG_ID, false, defaultGroup.getUuid()); - assertEquals(elements, users.size()); - for (int i = 0; i < elements; ++i) { - assertEquals(String.format("username-%d", i), users.get(i).getUsername()); - } - } finally { - featureFlipper.setUseRbacForFetchingUsers(false); + List users = rbacRecipientUsersProvider.getGroupUsers(TestConstants.DEFAULT_ORG_ID, false, defaultGroup.getUuid()); + assertEquals(elements, users.size()); + for (int i = 0; i < elements; ++i) { + assertEquals(String.format("username-%d", i), users.get(i).getUsername()); } } @@ -240,31 +236,26 @@ public void getAllUsersCache() { @Test public void getAllUsersCacheRBAC() { - try { - featureFlipper.setUseRbacForFetchingUsers(true); - int initialSize = 1095; - int updatedSize = 1323; - mockGetUsersRBAC(initialSize, false); - - List users = rbacRecipientUsersProvider.getUsers(TestConstants.DEFAULT_ORG_ID, false); - assertEquals(initialSize, users.size()); - for (int i = 0; i < initialSize; ++i) { - assertEquals(String.format("username-%d", i), users.get(i).getUsername()); - } + when(engineConfig.isUseRbacForFetchingUsers()).thenReturn(true); + int initialSize = 1095; + int updatedSize = 1323; + mockGetUsersRBAC(initialSize, false); - mockGetUsersRBAC(updatedSize, false); + List users = rbacRecipientUsersProvider.getUsers(TestConstants.DEFAULT_ORG_ID, false); + assertEquals(initialSize, users.size()); + for (int i = 0; i < initialSize; ++i) { + assertEquals(String.format("username-%d", i), users.get(i).getUsername()); + } - users = rbacRecipientUsersProvider.getUsers(TestConstants.DEFAULT_ORG_ID, false); - // Should still have the initial size because of the cache - assertEquals(initialSize, users.size()); - clearCached(); + mockGetUsersRBAC(updatedSize, false); - users = rbacRecipientUsersProvider.getUsers(TestConstants.DEFAULT_ORG_ID, false); - assertEquals(updatedSize, users.size()); - } finally { - featureFlipper.setUseRbacForFetchingUsers(false); - } + users = rbacRecipientUsersProvider.getUsers(TestConstants.DEFAULT_ORG_ID, false); + // Should still have the initial size because of the cache + assertEquals(initialSize, users.size()); + clearCached(); + users = rbacRecipientUsersProvider.getUsers(TestConstants.DEFAULT_ORG_ID, false); + assertEquals(updatedSize, users.size()); } @Test @@ -301,52 +292,48 @@ public void getAllGroupsCache() { */ @Test void testGetUsersMBOP() { - try { - this.featureFlipper.setUseMBOPForFetchingUsers(true); - - // Fake a REST call to MBOP. - final List firstPageMBOPUsers = this.mockGetMBOPUsers(this.MBOPMaxResultsPerPage); - final List secondPageMBOPUsers = this.mockGetMBOPUsers(this.MBOPMaxResultsPerPage); - final List thirdPageMBOPUsers = this.mockGetMBOPUsers(this.MBOPMaxResultsPerPage / 2); - - final boolean adminsOnly = false; - - // Return a few pages of results, with the last one being less than - // the configured maximum limit, so that we can test that the loop - // is working as expected. - Mockito - .when(this.mbopService.getUsersByOrgId(Mockito.eq(this.bopApiToken), Mockito.eq(this.bopClientId), Mockito.eq(this.bopEnv), Mockito.eq(TestConstants.DEFAULT_ORG_ID), Mockito.eq(adminsOnly), Mockito.eq(RbacRecipientUsersProvider.MBOP_SORT_ORDER), Mockito.eq(this.MBOPMaxResultsPerPage), Mockito.anyInt())) - .thenReturn(firstPageMBOPUsers, secondPageMBOPUsers, thirdPageMBOPUsers); - - // Call the function under test. - final List result = this.rbacRecipientUsersProvider.getUsers(TestConstants.DEFAULT_ORG_ID, adminsOnly); - - // Verify that the offset argument, which is the one that changes - // on each iteration, has been correctly incremented. - final ArgumentCaptor capturedOffset = ArgumentCaptor.forClass(Integer.class); - Mockito.verify(this.mbopService, Mockito.times(3)).getUsersByOrgId(Mockito.eq(this.bopApiToken), Mockito.eq(this.bopClientId), Mockito.eq(this.bopEnv), Mockito.eq(TestConstants.DEFAULT_ORG_ID), Mockito.eq(adminsOnly), Mockito.eq(RbacRecipientUsersProvider.MBOP_SORT_ORDER), Mockito.eq(this.MBOPMaxResultsPerPage), capturedOffset.capture()); - - final List capturedValues = capturedOffset.getAllValues(); - assertIterableEquals( - List.of( - 0, - this.MBOPMaxResultsPerPage, - this.MBOPMaxResultsPerPage * 2 - ), - capturedValues, - "unexpected offset values used when calling MBOP" - ); - - // Transform the generated MBOP users in order to check that the - // function under test did the transformations as expected. - final List mockUsers = this.rbacRecipientUsersProvider.transformMBOPUserToUser(firstPageMBOPUsers); - mockUsers.addAll(this.rbacRecipientUsersProvider.transformMBOPUserToUser(secondPageMBOPUsers)); - mockUsers.addAll(this.rbacRecipientUsersProvider.transformMBOPUserToUser(thirdPageMBOPUsers)); - - assertIterableEquals(mockUsers, result, "the list of users returned by the function under test is not correct"); - } finally { - this.featureFlipper.setUseMBOPForFetchingUsers(false); - } + when(engineConfig.isUseMBOPForFetchingUsers()).thenReturn(true); + + // Fake a REST call to MBOP. + final List firstPageMBOPUsers = this.mockGetMBOPUsers(this.MBOPMaxResultsPerPage); + final List secondPageMBOPUsers = this.mockGetMBOPUsers(this.MBOPMaxResultsPerPage); + final List thirdPageMBOPUsers = this.mockGetMBOPUsers(this.MBOPMaxResultsPerPage / 2); + + final boolean adminsOnly = false; + + // Return a few pages of results, with the last one being less than + // the configured maximum limit, so that we can test that the loop + // is working as expected. + Mockito + .when(this.mbopService.getUsersByOrgId(Mockito.eq(this.bopApiToken), Mockito.eq(this.bopClientId), Mockito.eq(this.bopEnv), Mockito.eq(TestConstants.DEFAULT_ORG_ID), Mockito.eq(adminsOnly), Mockito.eq(RbacRecipientUsersProvider.MBOP_SORT_ORDER), Mockito.eq(this.MBOPMaxResultsPerPage), Mockito.anyInt())) + .thenReturn(firstPageMBOPUsers, secondPageMBOPUsers, thirdPageMBOPUsers); + + // Call the function under test. + final List result = this.rbacRecipientUsersProvider.getUsers(TestConstants.DEFAULT_ORG_ID, adminsOnly); + + // Verify that the offset argument, which is the one that changes + // on each iteration, has been correctly incremented. + final ArgumentCaptor capturedOffset = ArgumentCaptor.forClass(Integer.class); + Mockito.verify(this.mbopService, Mockito.times(3)).getUsersByOrgId(Mockito.eq(this.bopApiToken), Mockito.eq(this.bopClientId), Mockito.eq(this.bopEnv), Mockito.eq(TestConstants.DEFAULT_ORG_ID), Mockito.eq(adminsOnly), Mockito.eq(RbacRecipientUsersProvider.MBOP_SORT_ORDER), Mockito.eq(this.MBOPMaxResultsPerPage), capturedOffset.capture()); + + final List capturedValues = capturedOffset.getAllValues(); + assertIterableEquals( + List.of( + 0, + this.MBOPMaxResultsPerPage, + this.MBOPMaxResultsPerPage * 2 + ), + capturedValues, + "unexpected offset values used when calling MBOP" + ); + + // Transform the generated MBOP users in order to check that the + // function under test did the transformations as expected. + final List mockUsers = this.rbacRecipientUsersProvider.transformMBOPUserToUser(firstPageMBOPUsers); + mockUsers.addAll(this.rbacRecipientUsersProvider.transformMBOPUserToUser(secondPageMBOPUsers)); + mockUsers.addAll(this.rbacRecipientUsersProvider.transformMBOPUserToUser(thirdPageMBOPUsers)); + + assertIterableEquals(mockUsers, result, "the list of users returned by the function under test is not correct"); } private void mockGetUsers(int elements, boolean adminsOnly) { diff --git a/engine/src/test/java/com/redhat/cloud/notifications/templates/TestDefaultTemplate.java b/engine/src/test/java/com/redhat/cloud/notifications/templates/TestDefaultTemplate.java index a3b81a594a..ad8c559a8e 100644 --- a/engine/src/test/java/com/redhat/cloud/notifications/templates/TestDefaultTemplate.java +++ b/engine/src/test/java/com/redhat/cloud/notifications/templates/TestDefaultTemplate.java @@ -2,12 +2,11 @@ import com.redhat.cloud.notifications.EmailTemplatesInDbHelper; import com.redhat.cloud.notifications.TestHelpers; -import com.redhat.cloud.notifications.config.FeatureFlipper; +import com.redhat.cloud.notifications.config.EngineConfig; import com.redhat.cloud.notifications.ingress.Action; import com.redhat.cloud.notifications.models.NotificationsConsoleCloudEvent; +import io.quarkus.test.InjectMock; import io.quarkus.test.junit.QuarkusTest; -import jakarta.inject.Inject; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -15,14 +14,15 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.when; @QuarkusTest public class TestDefaultTemplate extends EmailTemplatesInDbHelper { private static final String EVENT_TYPE_NAME = "event-type-without-template"; - @Inject - FeatureFlipper featureFlipper; + @InjectMock + EngineConfig engineConfig; @Override protected String getApp() { @@ -36,12 +36,7 @@ protected List getUsedEventTypeNames() { @BeforeEach void beforeEach() { - featureFlipper.setUseDefaultTemplate(true); - } - - @AfterEach - void afterEach() { - featureFlipper.setUseDefaultTemplate(false); + when(engineConfig.isDefaultTemplateEnabled()).thenReturn(true); } @Test diff --git a/pom.xml b/pom.xml index b9a7ad618d..e7fe545e14 100644 --- a/pom.xml +++ b/pom.xml @@ -69,6 +69,7 @@ 6.7.1 2.0.5 + 1.4.0 quarkus-bom io.quarkus.platform diff --git a/recipients-resolver/pom.xml b/recipients-resolver/pom.xml index cf291086c2..a02637296e 100644 --- a/recipients-resolver/pom.xml +++ b/recipients-resolver/pom.xml @@ -53,6 +53,11 @@ quarkus-logging-sentry ${quarkus-logging-sentry.version} + + io.quarkiverse.unleash + quarkus-unleash + ${quarkus-unleash.version} + diff --git a/recipients-resolver/src/main/java/com/redhat/cloud/notifications/recipients/config/RecipientsResolverConfig.java b/recipients-resolver/src/main/java/com/redhat/cloud/notifications/recipients/config/RecipientsResolverConfig.java index 463389b4cc..a6b15dbb69 100644 --- a/recipients-resolver/src/main/java/com/redhat/cloud/notifications/recipients/config/RecipientsResolverConfig.java +++ b/recipients-resolver/src/main/java/com/redhat/cloud/notifications/recipients/config/RecipientsResolverConfig.java @@ -1,34 +1,65 @@ package com.redhat.cloud.notifications.recipients.config; +import io.getunleash.Unleash; import io.quarkus.logging.Log; -import io.quarkus.runtime.StartupEvent; import jakarta.enterprise.context.ApplicationScoped; import jakarta.enterprise.event.Observes; +import jakarta.enterprise.event.Startup; +import jakarta.inject.Inject; import org.eclipse.microprofile.config.inject.ConfigProperty; import java.time.Duration; +import java.util.Map; +import java.util.TreeMap; @ApplicationScoped public class RecipientsResolverConfig { + /* + * Env vars configuration + */ + private static final String MAX_RESULTS_PER_PAGE = "notifications.recipients-resolver.max-results-per-page"; + private static final String MBOP_ENV = "mbop.env"; + private static final String RETRY_INITIAL_BACKOFF = "notifications.recipients-resolver.retry.initial-backoff"; + private static final String RETRY_MAX_ATTEMPTS = "notifications.recipients-resolver.retry.max-attempts"; + private static final String RETRY_MAX_BACKOFF = "notifications.recipients-resolver.retry.max-backoff"; + private static final String WARN_IF_DURATION_EXCEEDS = "notifications.recipients-resolver.warn-if-request-duration-exceeds"; + private static final String UNLEASH = "notifications.unleash.enabled"; + + /* + * Unleash configuration + */ + private static final String FETCH_USERS_WITH_MBOP = toggleName("fetch-users-with-mbop"); + private static final String FETCH_USERS_WITH_RBAC = toggleName("fetch-users-with-rbac"); + + private static String toggleName(String feature) { + return String.format("notifications-engine.%s.enabled", feature); + } + + @ConfigProperty(name = UNLEASH, defaultValue = "false") + @Deprecated(forRemoval = true, since = "To be removed when we're done migrating to Unleash in all environments") + boolean unleashEnabled; + @ConfigProperty(name = "notifications.recipients-resolver.fetch.users.rbac.enabled", defaultValue = "false") - boolean fetchUsersWithRBAC; + @Deprecated(forRemoval = true, since = "To be removed when we're done migrating to Unleash in all environments") + boolean fetchUsersWithRbacEnabled; @ConfigProperty(name = "notifications.recipients-resolver.fetch.users.mbop.enabled", defaultValue = "false") - boolean fetchUsersWithMbop; + @Deprecated(forRemoval = true, since = "To be removed when we're done migrating to Unleash in all environments") + boolean fetchUsersWithMbopEnabled; - @ConfigProperty(name = "notifications.recipients-resolver.max-results-per-page", defaultValue = "1000") + @ConfigProperty(name = MAX_RESULTS_PER_PAGE, defaultValue = "1000") int maxResultsPerPage; - @ConfigProperty(name = "notifications.recipients-resolver.retry.max-attempts", defaultValue = "3") - int maxRetryAttempts; - - @ConfigProperty(name = "notifications.recipients-resolver.retry.initial-backoff", defaultValue = "0.1S") + @ConfigProperty(name = RETRY_INITIAL_BACKOFF, defaultValue = "0.1S") Duration initialRetryBackoff; - @ConfigProperty(name = "notifications.recipients-resolver.retry.max-backoff", defaultValue = "1S") + @ConfigProperty(name = RETRY_MAX_ATTEMPTS, defaultValue = "3") + int maxRetryAttempts; + + @ConfigProperty(name = RETRY_MAX_BACKOFF, defaultValue = "1S") Duration maxRetryBackoff; - @ConfigProperty(name = "notifications.recipients-resolver.warn-if-request-duration-exceeds", defaultValue = "30S") + @ConfigProperty(name = WARN_IF_DURATION_EXCEEDS, defaultValue = "30S") Duration logTooLongRequestLimit; @ConfigProperty(name = "mbop.apitoken", defaultValue = "na") @@ -37,36 +68,45 @@ public class RecipientsResolverConfig { @ConfigProperty(name = "mbop.client_id", defaultValue = "na") String mbopClientId; - @ConfigProperty(name = "mbop.env", defaultValue = "na") + @ConfigProperty(name = MBOP_ENV, defaultValue = "na") String mbopEnv; - void logFeaturesStatusAtStartup(@Observes StartupEvent event) { - Log.infof("=== %s startup status ===", RecipientsResolverConfig.class.getSimpleName()); - Log.infof("The fetching users with Rbac is %s", fetchUsersWithRBAC ? "enabled" : "disabled"); - Log.infof("The fetching users with Mbop is %s", fetchUsersWithMbop ? "enabled" : "disabled"); - Log.infof("The fetching users with IT Service is %s", !(fetchUsersWithRBAC || fetchUsersWithMbop) ? "enabled" : "disabled"); - Log.infof("The max result per page is %s", maxResultsPerPage); - Log.infof("The max retry attempts is %s", maxRetryAttempts); - Log.infof("The retry back-off initial value is %s", initialRetryBackoff); - Log.infof("The max retry back-off value is %s", maxRetryBackoff); - Log.infof("The MBOP env is %s", mbopEnv); - Log.infof("The requests to external services will be logged it they exceed %s", logTooLongRequestLimit); - } - - public boolean isFetchUsersWithMbop() { - return fetchUsersWithMbop; - } - - public boolean isFetchUsersWithRBAC() { - return this.fetchUsersWithRBAC; + @Inject + Unleash unleash; + + void logConfigAtStartup(@Observes Startup event) { + + Map config = new TreeMap<>(); + config.put(FETCH_USERS_WITH_MBOP, isFetchUsersWithMbopEnabled()); + config.put(FETCH_USERS_WITH_RBAC, isFetchUsersWithRbacEnabled()); + config.put(MAX_RESULTS_PER_PAGE, getMaxResultsPerPage()); + config.put(MBOP_ENV, getMbopEnv()); + config.put(RETRY_INITIAL_BACKOFF, getInitialRetryBackoff()); + config.put(RETRY_MAX_ATTEMPTS, getMaxRetryAttempts()); + config.put(RETRY_MAX_BACKOFF, getMaxRetryBackoff()); + config.put(WARN_IF_DURATION_EXCEEDS, getLogTooLongRequestLimit()); + config.put(UNLEASH, unleashEnabled); + + Log.info("=== Startup configuration ==="); + config.forEach((key, value) -> { + Log.infof("%s=%s", key, value); + }); } - public void setFetchUsersWithRBAC(boolean fetchUsersWithRBAC) { - this.fetchUsersWithRBAC = fetchUsersWithRBAC; + public boolean isFetchUsersWithMbopEnabled() { + if (unleashEnabled) { + return unleash.isEnabled(FETCH_USERS_WITH_MBOP, false); + } else { + return fetchUsersWithMbopEnabled; + } } - public void setFetchUsersWithMbop(boolean fetchUsersWithMbop) { - this.fetchUsersWithMbop = fetchUsersWithMbop; + public boolean isFetchUsersWithRbacEnabled() { + if (unleashEnabled) { + return unleash.isEnabled(FETCH_USERS_WITH_RBAC, false); + } else { + return fetchUsersWithRbacEnabled; + } } public int getMaxResultsPerPage() { diff --git a/recipients-resolver/src/main/java/com/redhat/cloud/notifications/recipients/resolver/FetchUsersFromExternalServices.java b/recipients-resolver/src/main/java/com/redhat/cloud/notifications/recipients/resolver/FetchUsersFromExternalServices.java index 87ae57ee0f..34c32e2910 100644 --- a/recipients-resolver/src/main/java/com/redhat/cloud/notifications/recipients/resolver/FetchUsersFromExternalServices.java +++ b/recipients-resolver/src/main/java/com/redhat/cloud/notifications/recipients/resolver/FetchUsersFromExternalServices.java @@ -82,9 +82,9 @@ public class FetchUsersFromExternalServices { @PostConstruct public void postConstruct() { - if (recipientsResolverConfig.isFetchUsersWithRBAC()) { + if (recipientsResolverConfig.isFetchUsersWithRbacEnabled()) { failuresCounter = meterRegistry.counter("rbac.failures"); - } else if (recipientsResolverConfig.isFetchUsersWithMbop()) { + } else if (recipientsResolverConfig.isFetchUsersWithMbopEnabled()) { failuresCounter = meterRegistry.counter("mbop.failures"); } else { failuresCounter = meterRegistry.counter("it.failures"); @@ -106,7 +106,7 @@ public List getUsers(String orgId, boolean adminsOnly) { Timer.Sample getUsersTotalTimer = Timer.start(meterRegistry); List users; - if (recipientsResolverConfig.isFetchUsersWithRBAC()) { + if (recipientsResolverConfig.isFetchUsersWithRbacEnabled()) { users = getWithPagination( page -> retryOnError(() -> { LocalDateTime startTime = LocalDateTime.now(); @@ -117,7 +117,7 @@ public List getUsers(String orgId, boolean adminsOnly) { } return rbacUserPage; })); - } else if (recipientsResolverConfig.isFetchUsersWithMbop()) { + } else if (recipientsResolverConfig.isFetchUsersWithMbopEnabled()) { users = fetchUsersWithMbop(orgId, adminsOnly); } else { users = fetchUsersWithItUserService(orgId, adminsOnly); diff --git a/recipients-resolver/src/main/resources/application.properties b/recipients-resolver/src/main/resources/application.properties index 5a0c1a51f5..f353a156f4 100644 --- a/recipients-resolver/src/main/resources/application.properties +++ b/recipients-resolver/src/main/resources/application.properties @@ -51,3 +51,7 @@ quarkus.cache.caffeine.recipients-users-provider-get-users.expire-after-write=PT quarkus.cache.caffeine.recipients-users-provider-get-group-users.expire-after-write=PT10M quarkus.cache.caffeine.find-recipients.expire-after-write=PT10M quarkus.cache.caffeine.find-recipients.metrics-enabled=true + +quarkus.unleash.active=false +quarkus.unleash.synchronous-fetch-on-initialisation=true +quarkus.unleash.url=http://localhost:4242 diff --git a/recipients-resolver/src/test/java/com/redhat/cloud/notifications/recipients/resolver/FetchUsersFromExternalServicesTest.java b/recipients-resolver/src/test/java/com/redhat/cloud/notifications/recipients/resolver/FetchUsersFromExternalServicesTest.java index f9eb103477..5301c27311 100644 --- a/recipients-resolver/src/test/java/com/redhat/cloud/notifications/recipients/resolver/FetchUsersFromExternalServicesTest.java +++ b/recipients-resolver/src/test/java/com/redhat/cloud/notifications/recipients/resolver/FetchUsersFromExternalServicesTest.java @@ -27,6 +27,8 @@ import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; import org.mockito.Mockito; + +import java.time.Duration; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -38,6 +40,9 @@ import static org.junit.jupiter.api.Assertions.assertIterableEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.when; @QuarkusTest @@ -60,9 +65,21 @@ public class FetchUsersFromExternalServicesTest { @RestClient MBOPService mbopService; - @Inject + @InjectMock RecipientsResolverConfig recipientsResolverConfig; + @BeforeEach + void beforeEach() { + // This is the default config. It has to be set because we're mocking RecipientsResolverConfig. + when(recipientsResolverConfig.getInitialRetryBackoff()).thenReturn(Duration.ofMillis(100)); + when(recipientsResolverConfig.getMaxResultsPerPage()).thenReturn(1000); + when(recipientsResolverConfig.getMaxRetryAttempts()).thenReturn(3); + when(recipientsResolverConfig.getMaxRetryBackoff()).thenReturn(Duration.ofSeconds(1)); + when(recipientsResolverConfig.getMbopApiToken()).thenReturn("na"); + when(recipientsResolverConfig.getMbopClientId()).thenReturn("na"); + when(recipientsResolverConfig.getMbopEnv()).thenReturn("na"); + } + @Test void getGroupUsersShouldOnlyContainActiveUsers() { RbacGroup defaultGroup = new RbacGroup(); @@ -168,24 +185,20 @@ public void getAllUsersFromDefaultGroup() { @Test public void getAllUsersFromDefaultGroupRBAC() { - try { - recipientsResolverConfig.setFetchUsersWithRBAC(true); - RbacGroup defaultGroup = new RbacGroup(); - defaultGroup.setPlatformDefault(true); - defaultGroup.setUuid(UUID.randomUUID()); + when(recipientsResolverConfig.isFetchUsersWithRbacEnabled()).thenReturn(true); + RbacGroup defaultGroup = new RbacGroup(); + defaultGroup.setPlatformDefault(true); + defaultGroup.setUuid(UUID.randomUUID()); - int elements = 133; + int elements = 133; - mockGetGroup(defaultGroup); - mockGetUsersRBAC(elements, false); + mockGetGroup(defaultGroup); + mockGetUsersRBAC(elements, false); - List users = fetchUsersFromExternalServices.getGroupUsers(DEFAULT_ORG_ID, false, defaultGroup.getUuid()); - assertEquals(elements, users.size()); - for (int i = 0; i < elements; ++i) { - assertEquals(String.format("username-%d", i), users.get(i).getUsername()); - } - } finally { - recipientsResolverConfig.setFetchUsersWithRBAC(false); + List users = fetchUsersFromExternalServices.getGroupUsers(DEFAULT_ORG_ID, false, defaultGroup.getUuid()); + assertEquals(elements, users.size()); + for (int i = 0; i < elements; ++i) { + assertEquals(String.format("username-%d", i), users.get(i).getUsername()); } } @@ -223,31 +236,27 @@ public void getAllUsersCache() { @Test public void getAllUsersCacheRBAC() { - try { - recipientsResolverConfig.setFetchUsersWithRBAC(true); - int initialSize = 1095; - int updatedSize = 1323; - mockGetUsersRBAC(initialSize, false); - - List users = fetchUsersFromExternalServices.getUsers(DEFAULT_ORG_ID, false); - assertEquals(initialSize, users.size()); - for (int i = 0; i < initialSize; ++i) { - assertEquals(String.format("username-%d", i), users.get(i).getUsername()); - } - - mockGetUsersRBAC(updatedSize, false); + when(recipientsResolverConfig.isFetchUsersWithRbacEnabled()).thenReturn(true); - users = fetchUsersFromExternalServices.getUsers(DEFAULT_ORG_ID, false); - // Should still have the initial size because of the cache - assertEquals(initialSize, users.size()); - clearCached(); + int initialSize = 1095; + int updatedSize = 1323; + mockGetUsersRBAC(initialSize, false); - users = fetchUsersFromExternalServices.getUsers(DEFAULT_ORG_ID, false); - assertEquals(updatedSize, users.size()); - } finally { - recipientsResolverConfig.setFetchUsersWithRBAC(false); + List users = fetchUsersFromExternalServices.getUsers(DEFAULT_ORG_ID, false); + assertEquals(initialSize, users.size()); + for (int i = 0; i < initialSize; ++i) { + assertEquals(String.format("username-%d", i), users.get(i).getUsername()); } + mockGetUsersRBAC(updatedSize, false); + + users = fetchUsersFromExternalServices.getUsers(DEFAULT_ORG_ID, false); + // Should still have the initial size because of the cache + assertEquals(initialSize, users.size()); + clearCached(); + + users = fetchUsersFromExternalServices.getUsers(DEFAULT_ORG_ID, false); + assertEquals(updatedSize, users.size()); } @Test @@ -284,52 +293,48 @@ public void getAllGroupsCache() { */ @Test void testGetUsersMBOP() { - try { - this.recipientsResolverConfig.setFetchUsersWithMbop(true); - - // Fake a REST call to MBOP. - final List firstPageMBOPUsers = this.mockGetMBOPUsers(recipientsResolverConfig.getMaxResultsPerPage()); - final List secondPageMBOPUsers = this.mockGetMBOPUsers(recipientsResolverConfig.getMaxResultsPerPage()); - final List thirdPageMBOPUsers = this.mockGetMBOPUsers(recipientsResolverConfig.getMaxResultsPerPage() / 2); - - final boolean adminsOnly = false; - - // Return a few pages of results, with the last one being less than - // the configured maximum limit, so that we can test that the loop - // is working as expected. - Mockito - .when(this.mbopService.getUsersByOrgId(Mockito.eq(recipientsResolverConfig.getMbopApiToken()), Mockito.eq(recipientsResolverConfig.getMbopClientId()), Mockito.eq(recipientsResolverConfig.getMbopEnv()), Mockito.eq(DEFAULT_ORG_ID), Mockito.eq(adminsOnly), Mockito.eq(FetchUsersFromExternalServices.MBOP_SORT_ORDER), Mockito.eq(recipientsResolverConfig.getMaxResultsPerPage()), Mockito.anyInt())) - .thenReturn(firstPageMBOPUsers, secondPageMBOPUsers, thirdPageMBOPUsers); - - // Call the function under test. - final List result = this.fetchUsersFromExternalServices.getUsers(DEFAULT_ORG_ID, adminsOnly); - - // Verify that the offset argument, which is the one that changes - // on each iteration, has been correctly incremented. - final ArgumentCaptor capturedOffset = ArgumentCaptor.forClass(Integer.class); - Mockito.verify(this.mbopService, Mockito.times(3)).getUsersByOrgId(Mockito.eq(recipientsResolverConfig.getMbopApiToken()), Mockito.eq(recipientsResolverConfig.getMbopClientId()), Mockito.eq(recipientsResolverConfig.getMbopEnv()), Mockito.eq(DEFAULT_ORG_ID), Mockito.eq(adminsOnly), Mockito.eq(FetchUsersFromExternalServices.MBOP_SORT_ORDER), Mockito.eq(recipientsResolverConfig.getMaxResultsPerPage()), capturedOffset.capture()); - - final List capturedValues = capturedOffset.getAllValues(); - assertIterableEquals( - List.of( - 0, - recipientsResolverConfig.getMaxResultsPerPage(), - recipientsResolverConfig.getMaxResultsPerPage() * 2 - ), - capturedValues, - "unexpected offset values used when calling MBOP" - ); - - // Transform the generated MBOP users in order to check that the - // function under test did the transformations as expected. - final List mockUsers = this.fetchUsersFromExternalServices.transformMBOPUserToUser(firstPageMBOPUsers); - mockUsers.addAll(this.fetchUsersFromExternalServices.transformMBOPUserToUser(secondPageMBOPUsers)); - mockUsers.addAll(this.fetchUsersFromExternalServices.transformMBOPUserToUser(thirdPageMBOPUsers)); - - assertIterableEquals(mockUsers, result, "the list of users returned by the function under test is not correct"); - } finally { - this.recipientsResolverConfig.setFetchUsersWithMbop(false); - } + when(recipientsResolverConfig.isFetchUsersWithMbopEnabled()).thenReturn(true); + + // Fake a REST call to MBOP. + final List firstPageMBOPUsers = this.mockGetMBOPUsers(recipientsResolverConfig.getMaxResultsPerPage()); + final List secondPageMBOPUsers = this.mockGetMBOPUsers(recipientsResolverConfig.getMaxResultsPerPage()); + final List thirdPageMBOPUsers = this.mockGetMBOPUsers(recipientsResolverConfig.getMaxResultsPerPage() / 2); + + final boolean adminsOnly = false; + + // Return a few pages of results, with the last one being less than + // the configured maximum limit, so that we can test that the loop + // is working as expected. + Mockito + .when(this.mbopService.getUsersByOrgId(anyString(), anyString(), anyString(), anyString(), anyBoolean(), anyString(), anyInt(), anyInt())) + .thenReturn(firstPageMBOPUsers, secondPageMBOPUsers, thirdPageMBOPUsers); + + // Call the function under test. + final List result = this.fetchUsersFromExternalServices.getUsers(DEFAULT_ORG_ID, adminsOnly); + + // Verify that the offset argument, which is the one that changes + // on each iteration, has been correctly incremented. + final ArgumentCaptor capturedOffset = ArgumentCaptor.forClass(Integer.class); + Mockito.verify(this.mbopService, Mockito.times(3)).getUsersByOrgId(anyString(), anyString(), anyString(), anyString(), anyBoolean(), anyString(), anyInt(), capturedOffset.capture()); + + final List capturedValues = capturedOffset.getAllValues(); + assertIterableEquals( + List.of( + 0, + recipientsResolverConfig.getMaxResultsPerPage(), + recipientsResolverConfig.getMaxResultsPerPage() * 2 + ), + capturedValues, + "unexpected offset values used when calling MBOP" + ); + + // Transform the generated MBOP users in order to check that the + // function under test did the transformations as expected. + final List mockUsers = this.fetchUsersFromExternalServices.transformMBOPUserToUser(firstPageMBOPUsers); + mockUsers.addAll(this.fetchUsersFromExternalServices.transformMBOPUserToUser(secondPageMBOPUsers)); + mockUsers.addAll(this.fetchUsersFromExternalServices.transformMBOPUserToUser(thirdPageMBOPUsers)); + + assertIterableEquals(mockUsers, result, "the list of users returned by the function under test is not correct"); } private void mockGetUsers(int elements, boolean adminsOnly) {