From 3b3ddb1317644c1f7cc0cf51a2ee6fbaa6f99795 Mon Sep 17 00:00:00 2001 From: Ksiona Date: Thu, 14 May 2026 14:03:28 +0400 Subject: [PATCH 1/6] fix: documentation mismatch --- core-context-propagation-quarkus/README.md | 22 +++++++++------------- core-context-propagation/README.md | 2 +- 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/core-context-propagation-quarkus/README.md b/core-context-propagation-quarkus/README.md index 198fa4944..7d2a2f34c 100644 --- a/core-context-propagation-quarkus/README.md +++ b/core-context-propagation-quarkus/README.md @@ -29,6 +29,7 @@ Design overview: [context-propagation diagram](./design.png) - [Allowed headers](#allowed-headers) - [API version](#api-version) - [X-Request-Id](#x-request-id) + - [X-Channel-Request-Id](#x-channel-request-id) - [X-Version](#x-version) - [X-Version-Name](#x-version-name) - [X-Nc-Client-Ip](#x-nc-client-ip) @@ -130,7 +131,7 @@ String xRequestId = xRequestIdContextObject.getRequestId(); Propagates and allows to get `X-Channel-Request-Id` value. If an incoming request does not contain the `X-Channel-Request-Id` header then a random value is not generated and the value defaults to placeholder "-". This context is **blocked by default** and will not be propagated to outgoing requests. -**Default behavior:** `X-Channel-Request-Id` is NOT propagated to outgoing responses. +**Default behavior:** `X-Channel-Request-Id` is NOT propagated to outgoing requests. **Enabling propagation:** To allow `X-Channel-Request-Id` to be propagated to outgoing requests, remove it from the blacklist using one of the following methods: @@ -140,23 +141,18 @@ blacklist using one of the following methods: HEADERS_BLOCKED= ``` -2. **Via system property:** -```text --Dheaders.blocked= -``` - -3. **Via application.properties (Quarkus):** +2. **Via application.properties (Quarkus):** ```properties -headers.blocked= +quarkus.headers.blocked ``` -**`headers.blocked` rules and limitations** +**`quarkus.headers.blocked` rules and limitations** -- Source priority: system property `headers.blocked` overrides environment variable `HEADERS_BLOCKED`. +- Source priority: system property `quarkus.headers.blocked` overrides environment variable `HEADERS_BLOCKED`. - Default when not configured at all: `X-Channel-Request-Id` is blocked. -- Explicit empty value (`headers.blocked=` / `HEADERS_BLOCKED=`): blacklist is empty (nothing is blocked). -- Explicit non-empty value with valid headers (for example `headers.blocked=Some-Header`): only listed headers are blocked. -- `X-Request-Id` is non-blockable: if it is listed in `headers.blocked`/`HEADERS_BLOCKED`, it is ignored. +- Explicit empty value (`quarkus.headers.blocked=` / `HEADERS_BLOCKED=`): blacklist is empty (nothing is blocked). +- Explicit non-empty value with valid headers (for example `quarkus.headers.blocked=Some-Header`): only listed headers are blocked. +- `X-Request-Id` is non-blockable: if it is listed in `quarkus.headers.blocked`/`HEADERS_BLOCKED`, it is ignored. - If configured value contains only non-blockable entries (for example only `X-Request-Id`), default block is applied and `X-Channel-Request-Id` remains blocked. **MDC Integration:** The channel request ID is automatically stored in MDC under the key `x_channel_request_id` for use in diff --git a/core-context-propagation/README.md b/core-context-propagation/README.md index c7a8c6301..40f550846 100644 --- a/core-context-propagation/README.md +++ b/core-context-propagation/README.md @@ -145,7 +145,7 @@ Access: Propagates and allows to get `X-Channel-Request-Id` value. If an incoming request does not contain the `X-Channel-Request-Id` header then a random value is not generated and the value defaults to placeholder "-". This context is **blocked by default** and will not be propagated to outgoing requests. -**Default behavior:** `X-Channel-Request-Id` is NOT propagated to outgoing responses. +**Default behavior:** `X-Channel-Request-Id` is NOT propagated to outgoing requests. **Enabling propagation:** To allow `X-Channel-Request-Id` to be propagated to outgoing requests, remove it from the blacklist using one of the following methods: From 3bba46267d00c0ac668e057e16e9026d0f1cdb75 Mon Sep 17 00:00:00 2001 From: Ksiona Date: Thu, 14 May 2026 16:00:46 +0400 Subject: [PATCH 2/6] fix: documentation mismatch --- core-context-propagation-quarkus/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core-context-propagation-quarkus/README.md b/core-context-propagation-quarkus/README.md index 7d2a2f34c..d156f5320 100644 --- a/core-context-propagation-quarkus/README.md +++ b/core-context-propagation-quarkus/README.md @@ -143,7 +143,7 @@ HEADERS_BLOCKED= 2. **Via application.properties (Quarkus):** ```properties -quarkus.headers.blocked +quarkus.headers.blocked= ``` **`quarkus.headers.blocked` rules and limitations** From 39c9234130cd30172a850d26eb31527fa3157d93 Mon Sep 17 00:00:00 2001 From: Ksiona Date: Thu, 14 May 2026 16:29:47 +0400 Subject: [PATCH 3/6] fix: documentation mismatch --- core-context-propagation-quarkus/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core-context-propagation-quarkus/README.md b/core-context-propagation-quarkus/README.md index d156f5320..6ab77d25c 100644 --- a/core-context-propagation-quarkus/README.md +++ b/core-context-propagation-quarkus/README.md @@ -84,7 +84,7 @@ String acceptLanguage = acceptLanguageContextObject.getAcceptedLanguages(); #### Allowed headers Allows propagating any specified headers. To set a list of headers you should put either -`HEADERS_ALLOWED` environment or set the `headers.allowed` property. Property has more precedence than env. +`HEADERS_ALLOWED` environment or set the `quarkus.headers.allowed` property. Property has more precedence than env. Access: @@ -94,10 +94,10 @@ Map allowedHeaders = allowedHeadersContextObject.getHeaders(); ``` You just need to specify a list of headers in `application.properties` -in the `headers.allowed` property. For example: +in the `quarkus.headers.allowed` property. For example: ```properties -headers.allowed=myheader1,myheader2,... +quarkus.headers.allowed=myheader1,myheader2,... ``` Otherwise, you need to take care that this parameter is in System#property or environment. From e384b69ec8d85d2066fd67f541942a3ed1603ef3 Mon Sep 17 00:00:00 2001 From: Ksiona Date: Fri, 15 May 2026 23:54:41 +0400 Subject: [PATCH 4/6] fix: empty value is equal to "not set" for quarkus. --- core-context-propagation-quarkus/README.md | 4 +- .../allowedheaders/HeadersAllowedConfig.java | 32 ++++++++- .../HeadersAllowedRecorder.java | 5 +- .../allowedheaders/RawStringConverter.java | 10 +++ .../HeadersAllowedConfigTest.java | 9 ++- .../src/test/resources/application.properties | 2 + core-context-propagation/README.md | 4 +- .../HeaderPropagationConfiguration.java | 24 ++++--- .../HeaderPropagationConfigurationTest.java | 34 ++++++++-- .../context-propagation-spring-common/pom.xml | 6 ++ ...ContextProviderConfigurationEmptyTest.java | 52 +++++++++++++++ ...xtProviderConfigurationFromEnvVarTest.java | 66 +++++++++++++++++++ ...roviderConfigurationNotConfiguredTest.java | 48 ++++++++++++++ ...iderConfigurationOnlyNonBlockableTest.java | 54 +++++++++++++++ ...extProviderConfigurationWithValueTest.java | 52 +++++++++++++++ 15 files changed, 378 insertions(+), 24 deletions(-) create mode 100644 core-context-propagation-quarkus/framework-contexts/runtime/src/main/java/com/netcracker/cloud/framework/quarkus/contexts/allowedheaders/RawStringConverter.java create mode 100644 core-context-propagation/spring-context-aggregator/context-propagation-spring-common/src/test/java/com/netcracker/cloud/context/propagation/spring/common/configuration/SpringContextProviderConfigurationEmptyTest.java create mode 100644 core-context-propagation/spring-context-aggregator/context-propagation-spring-common/src/test/java/com/netcracker/cloud/context/propagation/spring/common/configuration/SpringContextProviderConfigurationFromEnvVarTest.java create mode 100644 core-context-propagation/spring-context-aggregator/context-propagation-spring-common/src/test/java/com/netcracker/cloud/context/propagation/spring/common/configuration/SpringContextProviderConfigurationNotConfiguredTest.java create mode 100644 core-context-propagation/spring-context-aggregator/context-propagation-spring-common/src/test/java/com/netcracker/cloud/context/propagation/spring/common/configuration/SpringContextProviderConfigurationOnlyNonBlockableTest.java create mode 100644 core-context-propagation/spring-context-aggregator/context-propagation-spring-common/src/test/java/com/netcracker/cloud/context/propagation/spring/common/configuration/SpringContextProviderConfigurationWithValueTest.java diff --git a/core-context-propagation-quarkus/README.md b/core-context-propagation-quarkus/README.md index 6ab77d25c..4be422ff0 100644 --- a/core-context-propagation-quarkus/README.md +++ b/core-context-propagation-quarkus/README.md @@ -152,8 +152,8 @@ quarkus.headers.blocked= - Default when not configured at all: `X-Channel-Request-Id` is blocked. - Explicit empty value (`quarkus.headers.blocked=` / `HEADERS_BLOCKED=`): blacklist is empty (nothing is blocked). - Explicit non-empty value with valid headers (for example `quarkus.headers.blocked=Some-Header`): only listed headers are blocked. -- `X-Request-Id` is non-blockable: if it is listed in `quarkus.headers.blocked`/`HEADERS_BLOCKED`, it is ignored. -- If configured value contains only non-blockable entries (for example only `X-Request-Id`), default block is applied and `X-Channel-Request-Id` remains blocked. +- `X-Request-Id` is non-blockable: if it is listed in `quarkus.headers.blocked`/`HEADERS_BLOCKED`, it is silently dropped from the configured list. +- If the configured value contains only non-blockable entries (for example only `X-Request-Id`), the resulting blocked list is **empty** — the default is **not** restored. Any explicit configuration (even one that effectively blocks nothing) is treated as the user's deliberate override of the default. **MDC Integration:** The channel request ID is automatically stored in MDC under the key `x_channel_request_id` for use in logging. diff --git a/core-context-propagation-quarkus/framework-contexts/runtime/src/main/java/com/netcracker/cloud/framework/quarkus/contexts/allowedheaders/HeadersAllowedConfig.java b/core-context-propagation-quarkus/framework-contexts/runtime/src/main/java/com/netcracker/cloud/framework/quarkus/contexts/allowedheaders/HeadersAllowedConfig.java index ff956700c..c59ddce43 100644 --- a/core-context-propagation-quarkus/framework-contexts/runtime/src/main/java/com/netcracker/cloud/framework/quarkus/contexts/allowedheaders/HeadersAllowedConfig.java +++ b/core-context-propagation-quarkus/framework-contexts/runtime/src/main/java/com/netcracker/cloud/framework/quarkus/contexts/allowedheaders/HeadersAllowedConfig.java @@ -3,6 +3,8 @@ import io.quarkus.runtime.annotations.ConfigPhase; import io.quarkus.runtime.annotations.ConfigRoot; import io.smallrye.config.ConfigMapping; +import io.smallrye.config.WithConverter; +import io.smallrye.config.WithDefault; import io.smallrye.config.WithName; import java.util.Optional; @@ -10,15 +12,41 @@ @ConfigMapping(prefix = "quarkus") @ConfigRoot(phase = ConfigPhase.RUN_TIME) public interface HeadersAllowedConfig { + + /** + * Sentinel value used to distinguish "property not set" from "property explicitly set to empty". + * Starts with a NUL character so users cannot accidentally type it. + */ + String UNSET = "\0__unset__"; + /** - * Allowed headers to propagate in contexts + * Allowed headers to propagate in contexts. */ @WithName("headers.allowed") Optional allowedHeaders(); /** * Blocked headers for context propagation. X-Channel-Request-Id is blocked by default. + *

+ * Three distinct states are supported: + *

    + *
  • property not set — value equals {@link #UNSET}; the default blocked list applies.
  • + *
  • property set to empty — value equals "" (empty string); the default is erased.
  • + *
  • property set to a value — value is the raw configured string.
  • + *
+ * The custom {@link RawStringConverter} is required so that an empty value is preserved + * instead of being collapsed to {@code null} by SmallRye's default String converter. */ @WithName("headers.blocked") - Optional blockedHeaders(); + @WithConverter(RawStringConverter.class) + @WithDefault(UNSET) + String blockedHeaders(); + + /** + * @return {@code true} if the user explicitly configured {@code quarkus.headers.blocked} + * (including to an empty value); {@code false} if the property was not set at all. + */ + default boolean isBlockedHeadersSet() { + return !UNSET.equals(blockedHeaders()); + } } diff --git a/core-context-propagation-quarkus/framework-contexts/runtime/src/main/java/com/netcracker/cloud/framework/quarkus/contexts/allowedheaders/HeadersAllowedRecorder.java b/core-context-propagation-quarkus/framework-contexts/runtime/src/main/java/com/netcracker/cloud/framework/quarkus/contexts/allowedheaders/HeadersAllowedRecorder.java index e795f9919..99cec85df 100644 --- a/core-context-propagation-quarkus/framework-contexts/runtime/src/main/java/com/netcracker/cloud/framework/quarkus/contexts/allowedheaders/HeadersAllowedRecorder.java +++ b/core-context-propagation-quarkus/framework-contexts/runtime/src/main/java/com/netcracker/cloud/framework/quarkus/contexts/allowedheaders/HeadersAllowedRecorder.java @@ -9,6 +9,9 @@ public class HeadersAllowedRecorder { public void setAllowedHeadersToSystemProperty() { HeadersAllowedConfig allowedConfig = Arc.container().instance(HeadersAllowedConfig.class).get(); allowedConfig.allowedHeaders().ifPresent(allowedHeaders -> System.setProperty("headers.allowed", allowedHeaders)); - allowedConfig.blockedHeaders().ifPresent(blockedHeaders -> System.setProperty("headers.blocked", blockedHeaders)); + + if (allowedConfig.isBlockedHeadersSet()) { + System.setProperty("headers.blocked", allowedConfig.blockedHeaders()); + } } } diff --git a/core-context-propagation-quarkus/framework-contexts/runtime/src/main/java/com/netcracker/cloud/framework/quarkus/contexts/allowedheaders/RawStringConverter.java b/core-context-propagation-quarkus/framework-contexts/runtime/src/main/java/com/netcracker/cloud/framework/quarkus/contexts/allowedheaders/RawStringConverter.java new file mode 100644 index 000000000..12ac744ff --- /dev/null +++ b/core-context-propagation-quarkus/framework-contexts/runtime/src/main/java/com/netcracker/cloud/framework/quarkus/contexts/allowedheaders/RawStringConverter.java @@ -0,0 +1,10 @@ +package com.netcracker.cloud.framework.quarkus.contexts.allowedheaders; + +import org.eclipse.microprofile.config.spi.Converter; + +public class RawStringConverter implements Converter { + @Override + public String convert(String value) { + return value; + } +} \ No newline at end of file diff --git a/core-context-propagation-quarkus/integration-tests/context-propagation-reactive-test/src/test/java/com/netcracker/cloud/framework/quarkus/contexts/allowedheaders/HeadersAllowedConfigTest.java b/core-context-propagation-quarkus/integration-tests/context-propagation-reactive-test/src/test/java/com/netcracker/cloud/framework/quarkus/contexts/allowedheaders/HeadersAllowedConfigTest.java index 0d5618251..fcece6b33 100644 --- a/core-context-propagation-quarkus/integration-tests/context-propagation-reactive-test/src/test/java/com/netcracker/cloud/framework/quarkus/contexts/allowedheaders/HeadersAllowedConfigTest.java +++ b/core-context-propagation-quarkus/integration-tests/context-propagation-reactive-test/src/test/java/com/netcracker/cloud/framework/quarkus/contexts/allowedheaders/HeadersAllowedConfigTest.java @@ -10,6 +10,7 @@ @QuarkusTest class HeadersAllowedConfigTest { + @Inject HeadersAllowedConfig headersAllowedConfig; @@ -21,5 +22,11 @@ void shouldReadHeadersAllowedFromProperty() { assertEquals("test-quarkus.headers.allowed", value.get()); } + @Test + void shouldReadHeadersBlockedAsExplicitlyEmpty() { + assertTrue(headersAllowedConfig.isBlockedHeadersSet(), + "quarkus.headers.blocked must be considered explicitly set"); + assertEquals("", headersAllowedConfig.blockedHeaders(), + "quarkus.headers.blocked must be preserved as an empty string"); + } } - diff --git a/core-context-propagation-quarkus/integration-tests/context-propagation-reactive-test/src/test/resources/application.properties b/core-context-propagation-quarkus/integration-tests/context-propagation-reactive-test/src/test/resources/application.properties index 6fecc5866..209ea103f 100644 --- a/core-context-propagation-quarkus/integration-tests/context-propagation-reactive-test/src/test/resources/application.properties +++ b/core-context-propagation-quarkus/integration-tests/context-propagation-reactive-test/src/test/resources/application.properties @@ -4,4 +4,6 @@ quarkus.devservices.enabled=false quarkus.rest-client.my-client.url=http://localhost:${quarkus.http.test-port} quarkus.headers.allowed=test-quarkus.headers.allowed +quarkus.headers.blocked= headers.allowed=test-headers.allowed +headers.blocked=test-headers.blocked diff --git a/core-context-propagation/README.md b/core-context-propagation/README.md index 40f550846..2e968cb92 100644 --- a/core-context-propagation/README.md +++ b/core-context-propagation/README.md @@ -171,8 +171,8 @@ headers.blocked= - Default when not configured at all: `X-Channel-Request-Id` is blocked. - Explicit empty value (`headers.blocked=` / `HEADERS_BLOCKED=`): blacklist is empty (nothing is blocked). - Explicit non-empty value with valid headers (for example `headers.blocked=Some-Header`): only listed headers are blocked. -- `X-Request-Id` is non-blockable: if it is listed in `headers.blocked`/`HEADERS_BLOCKED`, it is ignored. -- If configured value contains only non-blockable entries (for example only `X-Request-Id`), default block is applied and `X-Channel-Request-Id` remains blocked. +- `X-Request-Id` is non-blockable: if it is listed in `headers.blocked`/`HEADERS_BLOCKED`, it is silently dropped from the configured list. +- If the configured value contains only non-blockable entries (for example only `X-Request-Id`), the resulting blocked list is **empty** — the default is **not** restored. Any explicit configuration (even one that effectively blocks nothing) is treated as the user's deliberate override of the default. **MDC Integration:** The `X-Channel-Request-Id` is automatically integrated with SLF4J's Mapped Diagnostic Context (MDC) for seamless logging. diff --git a/core-context-propagation/framework-contexts/src/main/java/com/netcracker/cloud/framework/contexts/allowedheaders/HeaderPropagationConfiguration.java b/core-context-propagation/framework-contexts/src/main/java/com/netcracker/cloud/framework/contexts/allowedheaders/HeaderPropagationConfiguration.java index aa9de6028..e3d9a301a 100644 --- a/core-context-propagation/framework-contexts/src/main/java/com/netcracker/cloud/framework/contexts/allowedheaders/HeaderPropagationConfiguration.java +++ b/core-context-propagation/framework-contexts/src/main/java/com/netcracker/cloud/framework/contexts/allowedheaders/HeaderPropagationConfiguration.java @@ -69,23 +69,29 @@ private static List readBlockedHeaders() { String envValue = System.getenv(HEADERS_BLOCKED_ENV); boolean envSpecified = envValue != null; - String blockedHeaders = propertySpecified + // No source set at all → fall back to the built-in default. + if (!propertySpecified && !envSpecified) { + return DEFAULT_BLOCKED_HEADERS; + } + + // Property wins over env when both are set. + String raw = propertySpecified ? System.getProperty(HEADERS_BLOCKED_PROPERTY) : envValue; - boolean anySourceSpecified = propertySpecified || envSpecified; - - if (blockedHeaders == null || blockedHeaders.isBlank()) { - return anySourceSpecified ? Collections.emptyList() : DEFAULT_BLOCKED_HEADERS; + // Source is set but empty/blank → explicit "erase the default". + if (raw == null || raw.isBlank()) { + return Collections.emptyList(); } - - List configured = Arrays.stream(blockedHeaders.split(",")) + + // Source is set with a value → parse, trim, drop non-blockable entries. + // If everything filters out, we still respect the user's explicit override + // and return an empty list — we do NOT silently restore the default. + return Arrays.stream(raw.split(",")) .map(String::trim) .filter(s -> !s.isEmpty()) .filter(s -> NON_BLOCKABLE_HEADERS.stream() .noneMatch(s::equalsIgnoreCase)) .toList(); - - return configured.isEmpty() ? DEFAULT_BLOCKED_HEADERS : configured; } } diff --git a/core-context-propagation/framework-contexts/src/test/java/com/netcracker/cloud/framework/contexts/allowedheaders/HeaderPropagationConfigurationTest.java b/core-context-propagation/framework-contexts/src/test/java/com/netcracker/cloud/framework/contexts/allowedheaders/HeaderPropagationConfigurationTest.java index cf5944adf..fed20c41a 100644 --- a/core-context-propagation/framework-contexts/src/test/java/com/netcracker/cloud/framework/contexts/allowedheaders/HeaderPropagationConfigurationTest.java +++ b/core-context-propagation/framework-contexts/src/test/java/com/netcracker/cloud/framework/contexts/allowedheaders/HeaderPropagationConfigurationTest.java @@ -67,31 +67,51 @@ void shouldNotBlacklistXChannelRequestIdWhenBlockedHeadersExplicitlyEmpty() { @Test void shouldNotBlacklistXChannelRequestIdWhenOtherHeadersExplicitlyBlocked() { + // The property is explicitly set to a non-blockable header; the default + // blocked list must NOT silently kick in. System.setProperty(HeaderPropagationConfiguration.HEADERS_BLOCKED_PROPERTY, "X-Request-Id"); HeaderPropagationConfiguration.resetCache(); Assertions.assertFalse(HeaderPropagationConfiguration.isBlacklisted("X-Request-Id")); - Assertions.assertTrue(HeaderPropagationConfiguration.isBlacklisted("X-Channel-Request-Id")); + Assertions.assertFalse(HeaderPropagationConfiguration.isBlacklisted("X-Channel-Request-Id")); } @Test - void shouldApplyDefaultBlacklistWhenOnlyNonBlockableHeadersConfigured() { + void shouldReturnEmptyListWhenOnlyNonBlockableHeadersConfigured() { System.setProperty(HeaderPropagationConfiguration.HEADERS_BLOCKED_PROPERTY, "X-Request-Id, x-request-id"); HeaderPropagationConfiguration.resetCache(); Assertions.assertFalse(HeaderPropagationConfiguration.isBlacklisted("X-Request-Id")); - Assertions.assertTrue(HeaderPropagationConfiguration.isBlacklisted("X-Channel-Request-Id")); - Assertions.assertEquals(HeaderPropagationConfiguration.DEFAULT_BLOCKED_HEADERS, - HeaderPropagationConfiguration.blockedHeaders()); + Assertions.assertFalse(HeaderPropagationConfiguration.isBlacklisted("X-Channel-Request-Id")); + Assertions.assertTrue(HeaderPropagationConfiguration.blockedHeaders().isEmpty()); } @Test - void shouldBlacklistHeaderByEnvWhenPropertyNotSet() throws Exception { + void shouldReadEnvWhenPropertyNotSet() throws Exception { + // Env value is read when no system property is set. The configured value + // is only X-Request-Id (non-blockable), so the resulting list must be empty — + // we deliberately do NOT fall back to the default blocked list here. environmentVariables.set(HeaderPropagationConfiguration.HEADERS_BLOCKED_ENV, "X-Request-Id"); try { HeaderPropagationConfiguration.resetCache(); Assertions.assertFalse(HeaderPropagationConfiguration.isBlacklisted("X-Request-Id")); - Assertions.assertTrue(HeaderPropagationConfiguration.isBlacklisted("X-Channel-Request-Id")); + Assertions.assertFalse(HeaderPropagationConfiguration.isBlacklisted("X-Channel-Request-Id")); + Assertions.assertTrue(HeaderPropagationConfiguration.blockedHeaders().isEmpty()); + } finally { + environmentVariables.remove(HeaderPropagationConfiguration.HEADERS_BLOCKED_ENV); + HeaderPropagationConfiguration.resetCache(); + } + } + + @Test + void shouldBlacklistHeaderByEnvWhenPropertyNotSet() throws Exception { + // Sanity check that env-sourced configuration actually drives the blocked list + // when the system property is absent. + environmentVariables.set(HeaderPropagationConfiguration.HEADERS_BLOCKED_ENV, "Custom-Header"); + try { + HeaderPropagationConfiguration.resetCache(); + Assertions.assertTrue(HeaderPropagationConfiguration.isBlacklisted("Custom-Header")); + Assertions.assertFalse(HeaderPropagationConfiguration.isBlacklisted("X-Channel-Request-Id")); } finally { environmentVariables.remove(HeaderPropagationConfiguration.HEADERS_BLOCKED_ENV); HeaderPropagationConfiguration.resetCache(); diff --git a/core-context-propagation/spring-context-aggregator/context-propagation-spring-common/pom.xml b/core-context-propagation/spring-context-aggregator/context-propagation-spring-common/pom.xml index 3694ec174..c208c3358 100644 --- a/core-context-propagation/spring-context-aggregator/context-propagation-spring-common/pom.xml +++ b/core-context-propagation/spring-context-aggregator/context-propagation-spring-common/pom.xml @@ -56,6 +56,12 @@ spring-test test + + uk.org.webcompere + system-stubs-jupiter + 2.1.8 + test + diff --git a/core-context-propagation/spring-context-aggregator/context-propagation-spring-common/src/test/java/com/netcracker/cloud/context/propagation/spring/common/configuration/SpringContextProviderConfigurationEmptyTest.java b/core-context-propagation/spring-context-aggregator/context-propagation-spring-common/src/test/java/com/netcracker/cloud/context/propagation/spring/common/configuration/SpringContextProviderConfigurationEmptyTest.java new file mode 100644 index 000000000..ce5a5721b --- /dev/null +++ b/core-context-propagation/spring-context-aggregator/context-propagation-spring-common/src/test/java/com/netcracker/cloud/context/propagation/spring/common/configuration/SpringContextProviderConfigurationEmptyTest.java @@ -0,0 +1,52 @@ +package com.netcracker.cloud.context.propagation.spring.common.configuration; + +import com.netcracker.cloud.framework.contexts.allowedheaders.HeaderPropagationConfiguration; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Spring integration scenario: {@code headers.blocked=} (set explicitly to empty). + * Verifies that {@link SpringContextProviderConfiguration#init()} propagates the + * empty value to the system property, which downstream code interprets as + * "erase the default blocked list". + */ +@SpringJUnitConfig(classes = SpringContextProviderConfiguration.class) +@TestPropertySource(properties = { + "headers.allowed=custom-header", + "headers.blocked=" +}) +@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS) +class SpringContextProviderConfigurationEmptyTest { + + @BeforeAll + static void setup() { + System.clearProperty("headers.blocked"); + HeaderPropagationConfiguration.resetCache(); + } + + @AfterAll + static void teardown() { + System.clearProperty("headers.blocked"); + HeaderPropagationConfiguration.resetCache(); + } + + @Test + void shouldSetEmptySystemPropertyAndEraseDefaultBlockedList() { + assertEquals("", System.getProperty("headers.blocked"), + "Spring init() must propagate explicit empty value to the system property"); + + HeaderPropagationConfiguration.resetCache(); + assertTrue(HeaderPropagationConfiguration.blockedHeaders().isEmpty(), + "explicit empty value must erase the default blocked list"); + assertFalse(HeaderPropagationConfiguration.isBlacklisted("X-Channel-Request-Id"), + "X-Channel-Request-Id must no longer be blocked"); + } +} diff --git a/core-context-propagation/spring-context-aggregator/context-propagation-spring-common/src/test/java/com/netcracker/cloud/context/propagation/spring/common/configuration/SpringContextProviderConfigurationFromEnvVarTest.java b/core-context-propagation/spring-context-aggregator/context-propagation-spring-common/src/test/java/com/netcracker/cloud/context/propagation/spring/common/configuration/SpringContextProviderConfigurationFromEnvVarTest.java new file mode 100644 index 000000000..96851f99c --- /dev/null +++ b/core-context-propagation/spring-context-aggregator/context-propagation-spring-common/src/test/java/com/netcracker/cloud/context/propagation/spring/common/configuration/SpringContextProviderConfigurationFromEnvVarTest.java @@ -0,0 +1,66 @@ +package com.netcracker.cloud.context.propagation.spring.common.configuration; + +import com.netcracker.cloud.framework.contexts.allowedheaders.HeaderPropagationConfiguration; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import uk.org.webcompere.systemstubs.environment.EnvironmentVariables; +import uk.org.webcompere.systemstubs.jupiter.SystemStub; +import uk.org.webcompere.systemstubs.jupiter.SystemStubsExtension; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Spring integration scenario: {@code headers.blocked} is configured only via the + * {@code HEADERS_BLOCKED} environment variable. Verifies that Spring's relaxed + * binding picks up the env var as the {@code headers.blocked} property, that + * {@link SpringContextProviderConfiguration#init()} propagates it to the system + * property, and that the downstream blocked list reflects the env-sourced value. + * + *

{@link SystemStubsExtension} must be registered before {@link SpringExtension} + * so that the env var is set before Spring's {@code SystemEnvironmentPropertySource} + * is consulted during context initialization.

+ */ +@ExtendWith({SystemStubsExtension.class, SpringExtension.class}) +@ContextConfiguration(classes = SpringContextProviderConfiguration.class) +@TestPropertySource(properties = { + // headers.blocked deliberately not declared here — it must come from the env var + "headers.allowed=custom-header" +}) +@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS) +class SpringContextProviderConfigurationFromEnvVarTest { + + @SystemStub + static EnvironmentVariables envVars = new EnvironmentVariables("HEADERS_BLOCKED", "Custom-Header"); + + @BeforeAll + static void setup() { + System.clearProperty("headers.blocked"); + HeaderPropagationConfiguration.resetCache(); + } + + @AfterAll + static void teardown() { + System.clearProperty("headers.blocked"); + HeaderPropagationConfiguration.resetCache(); + } + + @Test + void shouldReadHeadersBlockedFromEnvVar() { + assertEquals("Custom-Header", System.getProperty("headers.blocked"), + "Spring init() must propagate env-sourced value to the system property"); + + HeaderPropagationConfiguration.resetCache(); + assertTrue(HeaderPropagationConfiguration.isBlacklisted("Custom-Header"), + "env-sourced header must be blocked"); + assertFalse(HeaderPropagationConfiguration.isBlacklisted("X-Channel-Request-Id"), + "explicit env-sourced configuration overrides the default"); + } +} diff --git a/core-context-propagation/spring-context-aggregator/context-propagation-spring-common/src/test/java/com/netcracker/cloud/context/propagation/spring/common/configuration/SpringContextProviderConfigurationNotConfiguredTest.java b/core-context-propagation/spring-context-aggregator/context-propagation-spring-common/src/test/java/com/netcracker/cloud/context/propagation/spring/common/configuration/SpringContextProviderConfigurationNotConfiguredTest.java new file mode 100644 index 000000000..664859f6a --- /dev/null +++ b/core-context-propagation/spring-context-aggregator/context-propagation-spring-common/src/test/java/com/netcracker/cloud/context/propagation/spring/common/configuration/SpringContextProviderConfigurationNotConfiguredTest.java @@ -0,0 +1,48 @@ +package com.netcracker.cloud.context.propagation.spring.common.configuration; + +import com.netcracker.cloud.framework.contexts.allowedheaders.HeaderPropagationConfiguration; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; + +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Spring integration scenario: {@code headers.blocked} is not set anywhere. + * Verifies that {@link SpringContextProviderConfiguration#init()} does NOT touch + * the {@code headers.blocked} system property, so downstream code falls back to + * the built-in default blocked list (which contains {@code X-Channel-Request-Id}). + */ +@SpringJUnitConfig(classes = SpringContextProviderConfiguration.class) +@TestPropertySource(properties = { + "headers.allowed=custom-header" +}) +@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS) +class SpringContextProviderConfigurationNotConfiguredTest { + + @BeforeAll + static void setup() { + System.clearProperty("headers.blocked"); + HeaderPropagationConfiguration.resetCache(); + } + + @AfterAll + static void teardown() { + System.clearProperty("headers.blocked"); + HeaderPropagationConfiguration.resetCache(); + } + + @Test + void shouldNotSetSystemPropertyAndApplyDefaultBlockedList() { + assertNull(System.getProperty("headers.blocked"), + "headers.blocked must remain unset when no source configures it"); + + HeaderPropagationConfiguration.resetCache(); + assertTrue(HeaderPropagationConfiguration.isBlacklisted("X-Channel-Request-Id"), + "default blocked list must apply when nothing is configured"); + } +} diff --git a/core-context-propagation/spring-context-aggregator/context-propagation-spring-common/src/test/java/com/netcracker/cloud/context/propagation/spring/common/configuration/SpringContextProviderConfigurationOnlyNonBlockableTest.java b/core-context-propagation/spring-context-aggregator/context-propagation-spring-common/src/test/java/com/netcracker/cloud/context/propagation/spring/common/configuration/SpringContextProviderConfigurationOnlyNonBlockableTest.java new file mode 100644 index 000000000..e97020668 --- /dev/null +++ b/core-context-propagation/spring-context-aggregator/context-propagation-spring-common/src/test/java/com/netcracker/cloud/context/propagation/spring/common/configuration/SpringContextProviderConfigurationOnlyNonBlockableTest.java @@ -0,0 +1,54 @@ +package com.netcracker.cloud.context.propagation.spring.common.configuration; + +import com.netcracker.cloud.framework.contexts.allowedheaders.HeaderPropagationConfiguration; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Spring integration scenario: {@code headers.blocked=X-Request-Id} — the configured + * value consists exclusively of a non-blockable header. The resulting blocked list + * must be empty, NOT the built-in default. This locks in the behavior change made + * to {@link HeaderPropagationConfiguration} (no silent fallback to default when the + * user explicitly configured something). + */ +@SpringJUnitConfig(classes = SpringContextProviderConfiguration.class) +@TestPropertySource(properties = { + "headers.allowed=custom-header", + "headers.blocked=X-Request-Id" +}) +@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS) +class SpringContextProviderConfigurationOnlyNonBlockableTest { + + @BeforeAll + static void setup() { + System.clearProperty("headers.blocked"); + HeaderPropagationConfiguration.resetCache(); + } + + @AfterAll + static void teardown() { + System.clearProperty("headers.blocked"); + HeaderPropagationConfiguration.resetCache(); + } + + @Test + void shouldRespectExplicitOverrideEvenWhenItFiltersToEmpty() { + assertEquals("X-Request-Id", System.getProperty("headers.blocked")); + + HeaderPropagationConfiguration.resetCache(); + assertTrue(HeaderPropagationConfiguration.blockedHeaders().isEmpty(), + "the resulting blocked list must be empty — the default must NOT be restored"); + assertFalse(HeaderPropagationConfiguration.isBlacklisted("X-Request-Id"), + "X-Request-Id is non-blockable and must never be blocked"); + assertFalse(HeaderPropagationConfiguration.isBlacklisted("X-Channel-Request-Id"), + "explicit (even if filter-emptied) configuration overrides the default"); + } +} diff --git a/core-context-propagation/spring-context-aggregator/context-propagation-spring-common/src/test/java/com/netcracker/cloud/context/propagation/spring/common/configuration/SpringContextProviderConfigurationWithValueTest.java b/core-context-propagation/spring-context-aggregator/context-propagation-spring-common/src/test/java/com/netcracker/cloud/context/propagation/spring/common/configuration/SpringContextProviderConfigurationWithValueTest.java new file mode 100644 index 000000000..45480ca94 --- /dev/null +++ b/core-context-propagation/spring-context-aggregator/context-propagation-spring-common/src/test/java/com/netcracker/cloud/context/propagation/spring/common/configuration/SpringContextProviderConfigurationWithValueTest.java @@ -0,0 +1,52 @@ +package com.netcracker.cloud.context.propagation.spring.common.configuration; + +import com.netcracker.cloud.framework.contexts.allowedheaders.HeaderPropagationConfiguration; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Spring integration scenario: {@code headers.blocked} is set to a concrete blockable header. + * Verifies that the listed header is blocked and the default's + * {@code X-Channel-Request-Id} entry no longer applies. + */ +@SpringJUnitConfig(classes = SpringContextProviderConfiguration.class) +@TestPropertySource(properties = { + "headers.allowed=custom-header", + "headers.blocked=Custom-Header" +}) +@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS) +class SpringContextProviderConfigurationWithValueTest { + + @BeforeAll + static void setup() { + System.clearProperty("headers.blocked"); + HeaderPropagationConfiguration.resetCache(); + } + + @AfterAll + static void teardown() { + System.clearProperty("headers.blocked"); + HeaderPropagationConfiguration.resetCache(); + } + + @Test + void shouldSetSystemPropertyAndBlockConfiguredHeader() { + assertEquals("Custom-Header", System.getProperty("headers.blocked")); + + HeaderPropagationConfiguration.resetCache(); + assertTrue(HeaderPropagationConfiguration.isBlacklisted("Custom-Header"), + "configured header must be blocked"); + assertTrue(HeaderPropagationConfiguration.isBlacklisted("custom-header"), + "blocking must be case-insensitive"); + assertFalse(HeaderPropagationConfiguration.isBlacklisted("X-Channel-Request-Id"), + "explicit configuration overrides the default blocked list"); + } +} From 934ad6392675eb5d6e2de3742563e0e85b8aa1fc Mon Sep 17 00:00:00 2001 From: Ksiona Date: Mon, 18 May 2026 10:40:55 +0400 Subject: [PATCH 5/6] chore: review improvements --- .../HeaderPropagationConfigurationTest.java | 2 +- .../HeaderPropagationStateReset.java | 18 ++++++++++++++++ ...ContextProviderConfigurationEmptyTest.java | 21 +++++-------------- ...xtProviderConfigurationFromEnvVarTest.java | 16 +------------- ...roviderConfigurationNotConfiguredTest.java | 21 +++++-------------- ...iderConfigurationOnlyNonBlockableTest.java | 21 +++++-------------- ...extProviderConfigurationWithValueTest.java | 21 +++++-------------- 7 files changed, 40 insertions(+), 80 deletions(-) create mode 100644 core-context-propagation/spring-context-aggregator/context-propagation-spring-common/src/test/java/com/netcracker/cloud/context/propagation/spring/common/configuration/HeaderPropagationStateReset.java diff --git a/core-context-propagation/framework-contexts/src/test/java/com/netcracker/cloud/framework/contexts/allowedheaders/HeaderPropagationConfigurationTest.java b/core-context-propagation/framework-contexts/src/test/java/com/netcracker/cloud/framework/contexts/allowedheaders/HeaderPropagationConfigurationTest.java index fed20c41a..68247fa37 100644 --- a/core-context-propagation/framework-contexts/src/test/java/com/netcracker/cloud/framework/contexts/allowedheaders/HeaderPropagationConfigurationTest.java +++ b/core-context-propagation/framework-contexts/src/test/java/com/netcracker/cloud/framework/contexts/allowedheaders/HeaderPropagationConfigurationTest.java @@ -87,7 +87,7 @@ void shouldReturnEmptyListWhenOnlyNonBlockableHeadersConfigured() { } @Test - void shouldReadEnvWhenPropertyNotSet() throws Exception { + void shouldReadEnvWhenPropertyNotSet() { // Env value is read when no system property is set. The configured value // is only X-Request-Id (non-blockable), so the resulting list must be empty — // we deliberately do NOT fall back to the default blocked list here. diff --git a/core-context-propagation/spring-context-aggregator/context-propagation-spring-common/src/test/java/com/netcracker/cloud/context/propagation/spring/common/configuration/HeaderPropagationStateReset.java b/core-context-propagation/spring-context-aggregator/context-propagation-spring-common/src/test/java/com/netcracker/cloud/context/propagation/spring/common/configuration/HeaderPropagationStateReset.java new file mode 100644 index 000000000..8c0a30297 --- /dev/null +++ b/core-context-propagation/spring-context-aggregator/context-propagation-spring-common/src/test/java/com/netcracker/cloud/context/propagation/spring/common/configuration/HeaderPropagationStateReset.java @@ -0,0 +1,18 @@ +package com.netcracker.cloud.context.propagation.spring.common.configuration; + +import org.junit.jupiter.api.extension.AfterAllCallback; +import org.junit.jupiter.api.extension.BeforeAllCallback; +import org.junit.jupiter.api.extension.ExtensionContext; + +import com.netcracker.cloud.framework.contexts.allowedheaders.HeaderPropagationConfiguration; + +public class HeaderPropagationStateReset implements BeforeAllCallback, AfterAllCallback { + @Override public void beforeAll(ExtensionContext ctx) { reset(); } + @Override public void afterAll(ExtensionContext ctx) { reset(); } + + private static void reset() { + System.clearProperty("headers.blocked"); + System.clearProperty("headers.allowed"); + HeaderPropagationConfiguration.resetCache(); + } +} diff --git a/core-context-propagation/spring-context-aggregator/context-propagation-spring-common/src/test/java/com/netcracker/cloud/context/propagation/spring/common/configuration/SpringContextProviderConfigurationEmptyTest.java b/core-context-propagation/spring-context-aggregator/context-propagation-spring-common/src/test/java/com/netcracker/cloud/context/propagation/spring/common/configuration/SpringContextProviderConfigurationEmptyTest.java index ce5a5721b..f9c0905ad 100644 --- a/core-context-propagation/spring-context-aggregator/context-propagation-spring-common/src/test/java/com/netcracker/cloud/context/propagation/spring/common/configuration/SpringContextProviderConfigurationEmptyTest.java +++ b/core-context-propagation/spring-context-aggregator/context-propagation-spring-common/src/test/java/com/netcracker/cloud/context/propagation/spring/common/configuration/SpringContextProviderConfigurationEmptyTest.java @@ -1,12 +1,12 @@ package com.netcracker.cloud.context.propagation.spring.common.configuration; import com.netcracker.cloud.framework.contexts.allowedheaders.HeaderPropagationConfiguration; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.TestPropertySource; -import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; +import org.springframework.test.context.junit.jupiter.SpringExtension; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -18,7 +18,8 @@ * empty value to the system property, which downstream code interprets as * "erase the default blocked list". */ -@SpringJUnitConfig(classes = SpringContextProviderConfiguration.class) +@ExtendWith({HeaderPropagationStateReset.class, SpringExtension.class}) +@ContextConfiguration(classes = SpringContextProviderConfiguration.class) @TestPropertySource(properties = { "headers.allowed=custom-header", "headers.blocked=" @@ -26,18 +27,6 @@ @DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS) class SpringContextProviderConfigurationEmptyTest { - @BeforeAll - static void setup() { - System.clearProperty("headers.blocked"); - HeaderPropagationConfiguration.resetCache(); - } - - @AfterAll - static void teardown() { - System.clearProperty("headers.blocked"); - HeaderPropagationConfiguration.resetCache(); - } - @Test void shouldSetEmptySystemPropertyAndEraseDefaultBlockedList() { assertEquals("", System.getProperty("headers.blocked"), diff --git a/core-context-propagation/spring-context-aggregator/context-propagation-spring-common/src/test/java/com/netcracker/cloud/context/propagation/spring/common/configuration/SpringContextProviderConfigurationFromEnvVarTest.java b/core-context-propagation/spring-context-aggregator/context-propagation-spring-common/src/test/java/com/netcracker/cloud/context/propagation/spring/common/configuration/SpringContextProviderConfigurationFromEnvVarTest.java index 96851f99c..4b56a696f 100644 --- a/core-context-propagation/spring-context-aggregator/context-propagation-spring-common/src/test/java/com/netcracker/cloud/context/propagation/spring/common/configuration/SpringContextProviderConfigurationFromEnvVarTest.java +++ b/core-context-propagation/spring-context-aggregator/context-propagation-spring-common/src/test/java/com/netcracker/cloud/context/propagation/spring/common/configuration/SpringContextProviderConfigurationFromEnvVarTest.java @@ -1,8 +1,6 @@ package com.netcracker.cloud.context.propagation.spring.common.configuration; import com.netcracker.cloud.framework.contexts.allowedheaders.HeaderPropagationConfiguration; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.test.annotation.DirtiesContext; @@ -28,7 +26,7 @@ * so that the env var is set before Spring's {@code SystemEnvironmentPropertySource} * is consulted during context initialization.

*/ -@ExtendWith({SystemStubsExtension.class, SpringExtension.class}) +@ExtendWith({HeaderPropagationStateReset.class, SystemStubsExtension.class, SpringExtension.class}) @ContextConfiguration(classes = SpringContextProviderConfiguration.class) @TestPropertySource(properties = { // headers.blocked deliberately not declared here — it must come from the env var @@ -40,18 +38,6 @@ class SpringContextProviderConfigurationFromEnvVarTest { @SystemStub static EnvironmentVariables envVars = new EnvironmentVariables("HEADERS_BLOCKED", "Custom-Header"); - @BeforeAll - static void setup() { - System.clearProperty("headers.blocked"); - HeaderPropagationConfiguration.resetCache(); - } - - @AfterAll - static void teardown() { - System.clearProperty("headers.blocked"); - HeaderPropagationConfiguration.resetCache(); - } - @Test void shouldReadHeadersBlockedFromEnvVar() { assertEquals("Custom-Header", System.getProperty("headers.blocked"), diff --git a/core-context-propagation/spring-context-aggregator/context-propagation-spring-common/src/test/java/com/netcracker/cloud/context/propagation/spring/common/configuration/SpringContextProviderConfigurationNotConfiguredTest.java b/core-context-propagation/spring-context-aggregator/context-propagation-spring-common/src/test/java/com/netcracker/cloud/context/propagation/spring/common/configuration/SpringContextProviderConfigurationNotConfiguredTest.java index 664859f6a..1d4ba15fa 100644 --- a/core-context-propagation/spring-context-aggregator/context-propagation-spring-common/src/test/java/com/netcracker/cloud/context/propagation/spring/common/configuration/SpringContextProviderConfigurationNotConfiguredTest.java +++ b/core-context-propagation/spring-context-aggregator/context-propagation-spring-common/src/test/java/com/netcracker/cloud/context/propagation/spring/common/configuration/SpringContextProviderConfigurationNotConfiguredTest.java @@ -1,12 +1,12 @@ package com.netcracker.cloud.context.propagation.spring.common.configuration; import com.netcracker.cloud.framework.contexts.allowedheaders.HeaderPropagationConfiguration; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.TestPropertySource; -import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; +import org.springframework.test.context.junit.jupiter.SpringExtension; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -17,25 +17,14 @@ * the {@code headers.blocked} system property, so downstream code falls back to * the built-in default blocked list (which contains {@code X-Channel-Request-Id}). */ -@SpringJUnitConfig(classes = SpringContextProviderConfiguration.class) +@ExtendWith({HeaderPropagationStateReset.class, SpringExtension.class}) +@ContextConfiguration(classes = SpringContextProviderConfiguration.class) @TestPropertySource(properties = { "headers.allowed=custom-header" }) @DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS) class SpringContextProviderConfigurationNotConfiguredTest { - @BeforeAll - static void setup() { - System.clearProperty("headers.blocked"); - HeaderPropagationConfiguration.resetCache(); - } - - @AfterAll - static void teardown() { - System.clearProperty("headers.blocked"); - HeaderPropagationConfiguration.resetCache(); - } - @Test void shouldNotSetSystemPropertyAndApplyDefaultBlockedList() { assertNull(System.getProperty("headers.blocked"), diff --git a/core-context-propagation/spring-context-aggregator/context-propagation-spring-common/src/test/java/com/netcracker/cloud/context/propagation/spring/common/configuration/SpringContextProviderConfigurationOnlyNonBlockableTest.java b/core-context-propagation/spring-context-aggregator/context-propagation-spring-common/src/test/java/com/netcracker/cloud/context/propagation/spring/common/configuration/SpringContextProviderConfigurationOnlyNonBlockableTest.java index e97020668..b6649f58b 100644 --- a/core-context-propagation/spring-context-aggregator/context-propagation-spring-common/src/test/java/com/netcracker/cloud/context/propagation/spring/common/configuration/SpringContextProviderConfigurationOnlyNonBlockableTest.java +++ b/core-context-propagation/spring-context-aggregator/context-propagation-spring-common/src/test/java/com/netcracker/cloud/context/propagation/spring/common/configuration/SpringContextProviderConfigurationOnlyNonBlockableTest.java @@ -1,12 +1,12 @@ package com.netcracker.cloud.context.propagation.spring.common.configuration; import com.netcracker.cloud.framework.contexts.allowedheaders.HeaderPropagationConfiguration; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.TestPropertySource; -import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; +import org.springframework.test.context.junit.jupiter.SpringExtension; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -19,7 +19,8 @@ * to {@link HeaderPropagationConfiguration} (no silent fallback to default when the * user explicitly configured something). */ -@SpringJUnitConfig(classes = SpringContextProviderConfiguration.class) +@ExtendWith({HeaderPropagationStateReset.class, SpringExtension.class}) +@ContextConfiguration(classes = SpringContextProviderConfiguration.class) @TestPropertySource(properties = { "headers.allowed=custom-header", "headers.blocked=X-Request-Id" @@ -27,18 +28,6 @@ @DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS) class SpringContextProviderConfigurationOnlyNonBlockableTest { - @BeforeAll - static void setup() { - System.clearProperty("headers.blocked"); - HeaderPropagationConfiguration.resetCache(); - } - - @AfterAll - static void teardown() { - System.clearProperty("headers.blocked"); - HeaderPropagationConfiguration.resetCache(); - } - @Test void shouldRespectExplicitOverrideEvenWhenItFiltersToEmpty() { assertEquals("X-Request-Id", System.getProperty("headers.blocked")); diff --git a/core-context-propagation/spring-context-aggregator/context-propagation-spring-common/src/test/java/com/netcracker/cloud/context/propagation/spring/common/configuration/SpringContextProviderConfigurationWithValueTest.java b/core-context-propagation/spring-context-aggregator/context-propagation-spring-common/src/test/java/com/netcracker/cloud/context/propagation/spring/common/configuration/SpringContextProviderConfigurationWithValueTest.java index 45480ca94..88e48709d 100644 --- a/core-context-propagation/spring-context-aggregator/context-propagation-spring-common/src/test/java/com/netcracker/cloud/context/propagation/spring/common/configuration/SpringContextProviderConfigurationWithValueTest.java +++ b/core-context-propagation/spring-context-aggregator/context-propagation-spring-common/src/test/java/com/netcracker/cloud/context/propagation/spring/common/configuration/SpringContextProviderConfigurationWithValueTest.java @@ -1,12 +1,12 @@ package com.netcracker.cloud.context.propagation.spring.common.configuration; import com.netcracker.cloud.framework.contexts.allowedheaders.HeaderPropagationConfiguration; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.TestPropertySource; -import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; +import org.springframework.test.context.junit.jupiter.SpringExtension; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -17,7 +17,8 @@ * Verifies that the listed header is blocked and the default's * {@code X-Channel-Request-Id} entry no longer applies. */ -@SpringJUnitConfig(classes = SpringContextProviderConfiguration.class) +@ExtendWith({HeaderPropagationStateReset.class, SpringExtension.class}) +@ContextConfiguration(classes = SpringContextProviderConfiguration.class) @TestPropertySource(properties = { "headers.allowed=custom-header", "headers.blocked=Custom-Header" @@ -25,18 +26,6 @@ @DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS) class SpringContextProviderConfigurationWithValueTest { - @BeforeAll - static void setup() { - System.clearProperty("headers.blocked"); - HeaderPropagationConfiguration.resetCache(); - } - - @AfterAll - static void teardown() { - System.clearProperty("headers.blocked"); - HeaderPropagationConfiguration.resetCache(); - } - @Test void shouldSetSystemPropertyAndBlockConfiguredHeader() { assertEquals("Custom-Header", System.getProperty("headers.blocked")); From 85ae957b7daba24e26a394503c5538a3008f044c Mon Sep 17 00:00:00 2001 From: Ksiona Date: Mon, 18 May 2026 19:38:30 +0400 Subject: [PATCH 6/6] fix: x-channel-request-id feature redesign --- core-context-propagation-quarkus/README.md | 89 ++++++++++-- .../allowedheaders/HeadersAllowedConfig.java | 51 +++---- .../HeadersAllowedRecorder.java | 10 +- .../allowedheaders/RawStringConverter.java | 10 -- ...HeadersAllowBlockedRecorderEffectTest.java | 33 +++++ .../HeadersAllowedConfigNotSetTest.java | 34 +++++ .../HeadersAllowedConfigTest.java | 16 ++- .../src/test/resources/application.properties | 4 +- core-context-propagation/README.md | 84 ++++++++--- .../HeaderPropagationConfiguration.java | 73 ++++++---- .../HeaderPropagationConfigurationTest.java | 133 ++++-------------- ...RequestIdContextObjectPropagationTest.java | 23 ++- .../RequestPropagationSpringCommonTest.java | 2 +- .../requests/RequestPropagationTest.java | 10 +- ...opagationXChannelRequestIdAllowedTest.java | 6 +- ...pagationXChannelRequestIdResponseTest.java | 6 +- .../SpringContextProviderConfiguration.java | 14 +- .../HeaderPropagationStateReset.java | 14 +- ...ContextProviderConfigurationEmptyTest.java | 20 +-- ...xtProviderConfigurationFromEnvVarTest.java | 25 +--- ...roviderConfigurationNotConfiguredTest.java | 15 +- ...iderConfigurationOnlyNonBlockableTest.java | 43 ------ ...iderConfigurationUnknownExemptionTest.java | 34 +++++ ...extProviderConfigurationWithValueTest.java | 19 +-- .../spring/kafka/ContextPropagationTest.java | 10 +- .../spring/rabbit/PropagationTest.java | 24 +++- .../kafka/KafkaContextPropagationTest.java | 6 +- 27 files changed, 447 insertions(+), 361 deletions(-) delete mode 100644 core-context-propagation-quarkus/framework-contexts/runtime/src/main/java/com/netcracker/cloud/framework/quarkus/contexts/allowedheaders/RawStringConverter.java create mode 100644 core-context-propagation-quarkus/integration-tests/context-propagation-reactive-test/src/test/java/com/netcracker/cloud/framework/quarkus/contexts/allowedheaders/HeadersAllowBlockedRecorderEffectTest.java create mode 100644 core-context-propagation-quarkus/integration-tests/context-propagation-reactive-test/src/test/java/com/netcracker/cloud/framework/quarkus/contexts/allowedheaders/HeadersAllowedConfigNotSetTest.java delete mode 100644 core-context-propagation/spring-context-aggregator/context-propagation-spring-common/src/test/java/com/netcracker/cloud/context/propagation/spring/common/configuration/SpringContextProviderConfigurationOnlyNonBlockableTest.java create mode 100644 core-context-propagation/spring-context-aggregator/context-propagation-spring-common/src/test/java/com/netcracker/cloud/context/propagation/spring/common/configuration/SpringContextProviderConfigurationUnknownExemptionTest.java diff --git a/core-context-propagation-quarkus/README.md b/core-context-propagation-quarkus/README.md index 4be422ff0..28a01dcf2 100644 --- a/core-context-propagation-quarkus/README.md +++ b/core-context-propagation-quarkus/README.md @@ -133,27 +133,86 @@ Propagates and allows to get `X-Channel-Request-Id` value. If an incoming reques **Default behavior:** `X-Channel-Request-Id` is NOT propagated to outgoing requests. -**Enabling propagation:** To allow `X-Channel-Request-Id` to be propagated to outgoing requests, remove it from the -blacklist using one of the following methods: +#### Internal blocklist -1. **Via environment variable:** -```text -HEADERS_BLOCKED= -``` +The framework owns a hard-coded internal blocklist of headers that are not propagated to outgoing +requests. It currently contains: + +- `X-Channel-Request-Id` + +The blocklist itself cannot be changed from configuration. The only externally visible knob is the +`quarkus.context.propagation.allow-blocked-headers` property, which lists header names that should be **exempted** from +this blocklist (i.e. allowed to propagate). + +#### `quarkus.context.propagation.allow-blocked-headers` property + +The property carries a comma-separated list of header names. Every name that matches an entry of +the internal blocklist is removed from the effective blocklist. Names that are not in the +internal blocklist have no effect. + +Examples: + +| Property value | Effect | +|---|---| +| not set / empty | Internal blocklist applies in full. `X-Channel-Request-Id` is not propagated. | +| `X-Channel-Request-Id` | `X-Channel-Request-Id` is propagated to outgoing requests. | +| `Some-Other-Header` | No effect — the header is not in the internal blocklist. | +| `X-Channel-Request-Id, Some-Other-Header` | `X-Channel-Request-Id` is propagated; the second entry is ignored. | + +Comparison is case-insensitive. Whitespace around comma-separated entries is trimmed. + +#### How to set the property + +**Via `application.properties`:** -2. **Via application.properties (Quarkus):** ```properties -quarkus.headers.blocked= +quarkus.context.propagation.allow-blocked-headers=X-Channel-Request-Id +``` + +**Via `application.yaml`:** + +```yaml +quarkus: + context: + propagation: + allow-blocked-headers: X-Channel-Request-Id +``` + +**Via JVM system property:** + +```text +-Dquarkus.context.propagation.allow-blocked-headers=X-Channel-Request-Id ``` -**`quarkus.headers.blocked` rules and limitations** +**Via container ENV (recommended, Qubership pattern)** + +In Qubership deployments configuration values are typically wired from container ENV variables +through `application.yaml` placeholders. The full chain looks like this: + +1. The container declares an ENV variable, for example in the deployment manifest: + + ```yaml + env: + - name: CONTEXT_PROPAGATION_ALLOW_BLOCKED_HEADERS + value: "X-Channel-Request-Id" + ``` + +2. The application's `application.yaml` reads it via a `${VAR}` placeholder: + + ```yaml + quarkus: + context: + propagation: + allow-blocked-headers: ${CONTEXT_PROPAGATION_ALLOW_BLOCKED_HEADERS:} + ``` + + The trailing `:` (empty default) lets the property gracefully fall through to "not set" when + the ENV variable is absent — the internal blocklist then applies in full. -- Source priority: system property `quarkus.headers.blocked` overrides environment variable `HEADERS_BLOCKED`. -- Default when not configured at all: `X-Channel-Request-Id` is blocked. -- Explicit empty value (`quarkus.headers.blocked=` / `HEADERS_BLOCKED=`): blacklist is empty (nothing is blocked). -- Explicit non-empty value with valid headers (for example `quarkus.headers.blocked=Some-Header`): only listed headers are blocked. -- `X-Request-Id` is non-blockable: if it is listed in `quarkus.headers.blocked`/`HEADERS_BLOCKED`, it is silently dropped from the configured list. -- If the configured value contains only non-blockable entries (for example only `X-Request-Id`), the resulting blocked list is **empty** — the default is **not** restored. Any explicit configuration (even one that effectively blocks nothing) is treated as the user's deliberate override of the default. +3. Quarkus also recognises the ENV variable directly under the canonical name + `CONTEXT_PROPAGATION_ALLOW_BLOCKED_HEADERS` (via MicroProfile Config relaxed env-to-property mapping), so + the same ENV variable works even without the `application.yaml` indirection. The placeholder + form is preferred because it makes the dependency explicit in source control. **MDC Integration:** The channel request ID is automatically stored in MDC under the key `x_channel_request_id` for use in logging. diff --git a/core-context-propagation-quarkus/framework-contexts/runtime/src/main/java/com/netcracker/cloud/framework/quarkus/contexts/allowedheaders/HeadersAllowedConfig.java b/core-context-propagation-quarkus/framework-contexts/runtime/src/main/java/com/netcracker/cloud/framework/quarkus/contexts/allowedheaders/HeadersAllowedConfig.java index c59ddce43..68e2a77b6 100644 --- a/core-context-propagation-quarkus/framework-contexts/runtime/src/main/java/com/netcracker/cloud/framework/quarkus/contexts/allowedheaders/HeadersAllowedConfig.java +++ b/core-context-propagation-quarkus/framework-contexts/runtime/src/main/java/com/netcracker/cloud/framework/quarkus/contexts/allowedheaders/HeadersAllowedConfig.java @@ -3,22 +3,15 @@ import io.quarkus.runtime.annotations.ConfigPhase; import io.quarkus.runtime.annotations.ConfigRoot; import io.smallrye.config.ConfigMapping; -import io.smallrye.config.WithConverter; -import io.smallrye.config.WithDefault; import io.smallrye.config.WithName; +import java.util.List; import java.util.Optional; @ConfigMapping(prefix = "quarkus") @ConfigRoot(phase = ConfigPhase.RUN_TIME) public interface HeadersAllowedConfig { - /** - * Sentinel value used to distinguish "property not set" from "property explicitly set to empty". - * Starts with a NUL character so users cannot accidentally type it. - */ - String UNSET = "\0__unset__"; - /** * Allowed headers to propagate in contexts. */ @@ -26,27 +19,29 @@ public interface HeadersAllowedConfig { Optional allowedHeaders(); /** - * Blocked headers for context propagation. X-Channel-Request-Id is blocked by default. - *

- * Three distinct states are supported: + * Headers that should be allowed to propagate even though they appear in the framework's + * internal blocklist. The blocklist itself is hard-coded and cannot be changed from + * configuration. + * + *

Semantics: *

    - *
  • property not set — value equals {@link #UNSET}; the default blocked list applies.
  • - *
  • property set to empty — value equals "" (empty string); the default is erased.
  • - *
  • property set to a value — value is the raw configured string.
  • + *
  • Property not set or empty: the internal blocklist is applied as-is. Any header + * in the blocklist (for example {@code X-Channel-Request-Id}) is not propagated.
  • + *
  • Property contains a comma-separated list of header names: each listed name is + * removed from the effective blocklist and is allowed to propagate. Names that + * are not in the internal blocklist have no effect.
  • *
- * The custom {@link RawStringConverter} is required so that an empty value is preserved - * instead of being collapsed to {@code null} by SmallRye's default String converter. - */ - @WithName("headers.blocked") - @WithConverter(RawStringConverter.class) - @WithDefault(UNSET) - String blockedHeaders(); - - /** - * @return {@code true} if the user explicitly configured {@code quarkus.headers.blocked} - * (including to an empty value); {@code false} if the property was not set at all. + * + *

Example {@code application.yaml} with a container ENV indirection: + *

+     * quarkus:
+     *   context:
+     *     propagation:
+     *       allow-blocked-headers: ${CONTEXT_PROPAGATION_ALLOW_BLOCKED_HEADERS:}
+     * 
+ * The container then sets {@code CONTEXT_PROPAGATION_ALLOW_BLOCKED_HEADERS=X-Channel-Request-Id} when it needs + * that header to be propagated. */ - default boolean isBlockedHeadersSet() { - return !UNSET.equals(blockedHeaders()); - } + @WithName("context.propagation.allow-blocked-headers") + Optional> allowedHeadersFromBlocklist(); } diff --git a/core-context-propagation-quarkus/framework-contexts/runtime/src/main/java/com/netcracker/cloud/framework/quarkus/contexts/allowedheaders/HeadersAllowedRecorder.java b/core-context-propagation-quarkus/framework-contexts/runtime/src/main/java/com/netcracker/cloud/framework/quarkus/contexts/allowedheaders/HeadersAllowedRecorder.java index 99cec85df..c0dabdb9e 100644 --- a/core-context-propagation-quarkus/framework-contexts/runtime/src/main/java/com/netcracker/cloud/framework/quarkus/contexts/allowedheaders/HeadersAllowedRecorder.java +++ b/core-context-propagation-quarkus/framework-contexts/runtime/src/main/java/com/netcracker/cloud/framework/quarkus/contexts/allowedheaders/HeadersAllowedRecorder.java @@ -8,10 +8,12 @@ public class HeadersAllowedRecorder { public void setAllowedHeadersToSystemProperty() { HeadersAllowedConfig allowedConfig = Arc.container().instance(HeadersAllowedConfig.class).get(); - allowedConfig.allowedHeaders().ifPresent(allowedHeaders -> System.setProperty("headers.allowed", allowedHeaders)); - if (allowedConfig.isBlockedHeadersSet()) { - System.setProperty("headers.blocked", allowedConfig.blockedHeaders()); - } + allowedConfig.allowedHeaders() + .ifPresent(allowedHeaders -> System.setProperty("headers.allowed", allowedHeaders)); + + allowedConfig.allowedHeadersFromBlocklist() + .filter(list -> !list.isEmpty()) + .ifPresent(list -> System.setProperty("context.propagation.allow-blocked-headers", String.join(",", list))); } } diff --git a/core-context-propagation-quarkus/framework-contexts/runtime/src/main/java/com/netcracker/cloud/framework/quarkus/contexts/allowedheaders/RawStringConverter.java b/core-context-propagation-quarkus/framework-contexts/runtime/src/main/java/com/netcracker/cloud/framework/quarkus/contexts/allowedheaders/RawStringConverter.java deleted file mode 100644 index 12ac744ff..000000000 --- a/core-context-propagation-quarkus/framework-contexts/runtime/src/main/java/com/netcracker/cloud/framework/quarkus/contexts/allowedheaders/RawStringConverter.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.netcracker.cloud.framework.quarkus.contexts.allowedheaders; - -import org.eclipse.microprofile.config.spi.Converter; - -public class RawStringConverter implements Converter { - @Override - public String convert(String value) { - return value; - } -} \ No newline at end of file diff --git a/core-context-propagation-quarkus/integration-tests/context-propagation-reactive-test/src/test/java/com/netcracker/cloud/framework/quarkus/contexts/allowedheaders/HeadersAllowBlockedRecorderEffectTest.java b/core-context-propagation-quarkus/integration-tests/context-propagation-reactive-test/src/test/java/com/netcracker/cloud/framework/quarkus/contexts/allowedheaders/HeadersAllowBlockedRecorderEffectTest.java new file mode 100644 index 000000000..0d6f17fb1 --- /dev/null +++ b/core-context-propagation-quarkus/integration-tests/context-propagation-reactive-test/src/test/java/com/netcracker/cloud/framework/quarkus/contexts/allowedheaders/HeadersAllowBlockedRecorderEffectTest.java @@ -0,0 +1,33 @@ +package com.netcracker.cloud.framework.quarkus.contexts.allowedheaders; + +import com.netcracker.cloud.framework.contexts.allowedheaders.HeaderPropagationConfiguration; +import io.quarkus.test.junit.QuarkusTest; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@QuarkusTest +class HeadersAllowBlockedRecorderEffectTest { + + @Test + void shouldExposeAllowBlockedValueAsSystemProperty() { + assertEquals("X-Channel-Request-Id", + System.getProperty("context.propagation.allow-blocked-headers"), + "Recorder must propagate the quarkus.context.propagation.allow-blocked-headers value " + + "to the context.propagation.allow-blocked-headers system property."); + } + + @Test + void shouldRemoveExemptedHeaderFromInternalBlocklist() { + // The blocked list is cached on first access; ensure we read the post-recorder state. + HeaderPropagationConfiguration.resetCache(); + + assertTrue(HeaderPropagationConfiguration.blockedHeaders().isEmpty(), + "The only entry of the internal blocklist (X-Channel-Request-Id) must be removed " + + "by the exemption configured in application.properties."); + assertFalse(HeaderPropagationConfiguration.isBlacklisted("X-Channel-Request-Id"), + "X-Channel-Request-Id must not be blocked when explicitly exempted."); + } +} diff --git a/core-context-propagation-quarkus/integration-tests/context-propagation-reactive-test/src/test/java/com/netcracker/cloud/framework/quarkus/contexts/allowedheaders/HeadersAllowedConfigNotSetTest.java b/core-context-propagation-quarkus/integration-tests/context-propagation-reactive-test/src/test/java/com/netcracker/cloud/framework/quarkus/contexts/allowedheaders/HeadersAllowedConfigNotSetTest.java new file mode 100644 index 000000000..61f016c4d --- /dev/null +++ b/core-context-propagation-quarkus/integration-tests/context-propagation-reactive-test/src/test/java/com/netcracker/cloud/framework/quarkus/contexts/allowedheaders/HeadersAllowedConfigNotSetTest.java @@ -0,0 +1,34 @@ +package com.netcracker.cloud.framework.quarkus.contexts.allowedheaders; + +import io.quarkus.test.junit.QuarkusTest; +import io.quarkus.test.junit.QuarkusTestProfile; +import io.quarkus.test.junit.TestProfile; +import jakarta.inject.Inject; +import org.junit.jupiter.api.Test; + +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertFalse; + +@QuarkusTest +@TestProfile(HeadersAllowedConfigNotSetTest.NotSetProfile.class) +class HeadersAllowedConfigNotSetTest { + + @Inject + HeadersAllowedConfig headersAllowedConfig; + + @Test + void shouldReportAllowedFromBlocklistAsEmptyWhenNotConfigured() { + assertFalse(headersAllowedConfig.allowedHeadersFromBlocklist().isPresent(), + "quarkus.context.propagation.allow-blocked-headers must resolve to Optional.empty() " + + "when no exemption value is configured"); + } + + public static class NotSetProfile implements QuarkusTestProfile { + + @Override + public Map getConfigOverrides() { + return Map.of("quarkus.context.propagation.allow-blocked-headers", ""); + } + } +} diff --git a/core-context-propagation-quarkus/integration-tests/context-propagation-reactive-test/src/test/java/com/netcracker/cloud/framework/quarkus/contexts/allowedheaders/HeadersAllowedConfigTest.java b/core-context-propagation-quarkus/integration-tests/context-propagation-reactive-test/src/test/java/com/netcracker/cloud/framework/quarkus/contexts/allowedheaders/HeadersAllowedConfigTest.java index fcece6b33..cf03260d5 100644 --- a/core-context-propagation-quarkus/integration-tests/context-propagation-reactive-test/src/test/java/com/netcracker/cloud/framework/quarkus/contexts/allowedheaders/HeadersAllowedConfigTest.java +++ b/core-context-propagation-quarkus/integration-tests/context-propagation-reactive-test/src/test/java/com/netcracker/cloud/framework/quarkus/contexts/allowedheaders/HeadersAllowedConfigTest.java @@ -4,9 +4,11 @@ import jakarta.inject.Inject; import org.junit.jupiter.api.Test; +import java.util.List; import java.util.Optional; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; @QuarkusTest class HeadersAllowedConfigTest { @@ -23,10 +25,12 @@ void shouldReadHeadersAllowedFromProperty() { } @Test - void shouldReadHeadersBlockedAsExplicitlyEmpty() { - assertTrue(headersAllowedConfig.isBlockedHeadersSet(), - "quarkus.headers.blocked must be considered explicitly set"); - assertEquals("", headersAllowedConfig.blockedHeaders(), - "quarkus.headers.blocked must be preserved as an empty string"); + void shouldReadAllowedHeadersFromBlocklist() { + Optional> value = headersAllowedConfig.allowedHeadersFromBlocklist(); + + assertTrue(value.isPresent(), + "quarkus.context.propagation.allow-blocked-headers must be present when configured"); + assertEquals(List.of("X-Channel-Request-Id"), value.get(), + "SmallRye must parse the comma-separated value into a list"); } } diff --git a/core-context-propagation-quarkus/integration-tests/context-propagation-reactive-test/src/test/resources/application.properties b/core-context-propagation-quarkus/integration-tests/context-propagation-reactive-test/src/test/resources/application.properties index 209ea103f..544d085e0 100644 --- a/core-context-propagation-quarkus/integration-tests/context-propagation-reactive-test/src/test/resources/application.properties +++ b/core-context-propagation-quarkus/integration-tests/context-propagation-reactive-test/src/test/resources/application.properties @@ -4,6 +4,6 @@ quarkus.devservices.enabled=false quarkus.rest-client.my-client.url=http://localhost:${quarkus.http.test-port} quarkus.headers.allowed=test-quarkus.headers.allowed -quarkus.headers.blocked= + +quarkus.context.propagation.allow-blocked-headers=X-Channel-Request-Id headers.allowed=test-headers.allowed -headers.blocked=test-headers.blocked diff --git a/core-context-propagation/README.md b/core-context-propagation/README.md index 2e968cb92..ff5d3e453 100644 --- a/core-context-propagation/README.md +++ b/core-context-propagation/README.md @@ -147,32 +147,82 @@ Propagates and allows to get `X-Channel-Request-Id` value. If an incoming reques **Default behavior:** `X-Channel-Request-Id` is NOT propagated to outgoing requests. -**Enabling propagation:** To allow `X-Channel-Request-Id` to be propagated to outgoing requests, remove it from the -blacklist using one of the following methods: +#### Internal blocklist -1. **Via environment variable:** -```text -HEADERS_BLOCKED= +The framework owns a hard-coded internal blocklist of headers that are not propagated to outgoing +requests. It currently contains: + +- `X-Channel-Request-Id` + +The blocklist itself cannot be changed from configuration. The only externally visible knob is the +`context.propagation.allow-blocked-headers` property, which lists header names that should be **exempted** from this +blocklist (i.e. allowed to propagate). + +#### `context.propagation.allow-blocked-headers` property + +The property carries a comma-separated list of header names. Every name that matches an entry of +the internal blocklist is removed from the effective blocklist. Names that are not in the +internal blocklist have no effect. + +Examples: + +| Property value | Effect | +|---|---| +| not set / empty | Internal blocklist applies in full. `X-Channel-Request-Id` is not propagated. | +| `X-Channel-Request-Id` | `X-Channel-Request-Id` is propagated to outgoing requests. | +| `Some-Other-Header` | No effect — the header is not in the internal blocklist. | +| `X-Channel-Request-Id, Some-Other-Header` | `X-Channel-Request-Id` is propagated; the second entry is ignored. | + +Comparison is case-insensitive. Whitespace around comma-separated entries is trimmed. + +#### How to set the property + +**Spring (`application.properties` / `application.yml`):** + +```properties +context.propagation.allow-blocked-headers=X-Channel-Request-Id ``` -2. **Via system property:** -```text --Dheaders.blocked= +```yaml +context: + propagation: + allow-blocked-headers: X-Channel-Request-Id ``` -3. **Via application.properties (Spring):** +**Via JVM system property:** + ```text -headers.blocked= +-Dcontext.propagation.allow-blocked-headers=X-Channel-Request-Id ``` -**`headers.blocked` rules and limitations** +**Via container ENV (recommended, Qubership pattern)** + +In Qubership deployments configuration values are typically wired from container ENV variables +through `application.yaml` placeholders. The full chain looks like this: + +1. The container declares an ENV variable, for example in the deployment manifest: + + ```yaml + env: + - name: CONTEXT_PROPAGATION_ALLOW_BLOCKED_HEADERS + value: "X-Channel-Request-Id" + ``` + +2. The application's `application.yaml` reads it via a `${VAR}` placeholder: + + ```yaml + context: + propagation: + allow-blocked-headers: ${CONTEXT_PROPAGATION_ALLOW_BLOCKED_HEADERS:} + ``` + + The trailing `:` (empty default) lets the property gracefully fall through to "not set" when + the ENV variable is absent — the internal blocklist then applies in full. -- Source priority: system property `headers.blocked` overrides environment variable `HEADERS_BLOCKED`. -- Default when not configured at all: `X-Channel-Request-Id` is blocked. -- Explicit empty value (`headers.blocked=` / `HEADERS_BLOCKED=`): blacklist is empty (nothing is blocked). -- Explicit non-empty value with valid headers (for example `headers.blocked=Some-Header`): only listed headers are blocked. -- `X-Request-Id` is non-blockable: if it is listed in `headers.blocked`/`HEADERS_BLOCKED`, it is silently dropped from the configured list. -- If the configured value contains only non-blockable entries (for example only `X-Request-Id`), the resulting blocked list is **empty** — the default is **not** restored. Any explicit configuration (even one that effectively blocks nothing) is treated as the user's deliberate override of the default. +3. Spring's relaxed binding also recognises the ENV variable directly under the property name + `context.propagation.allow-blocked-headers` (i.e. without an explicit placeholder), so the same ENV variable works + even without the `application.yaml` indirection. The placeholder form is preferred because it + makes the dependency explicit in source control. **MDC Integration:** The `X-Channel-Request-Id` is automatically integrated with SLF4J's Mapped Diagnostic Context (MDC) for seamless logging. diff --git a/core-context-propagation/framework-contexts/src/main/java/com/netcracker/cloud/framework/contexts/allowedheaders/HeaderPropagationConfiguration.java b/core-context-propagation/framework-contexts/src/main/java/com/netcracker/cloud/framework/contexts/allowedheaders/HeaderPropagationConfiguration.java index e3d9a301a..6e2291cb5 100644 --- a/core-context-propagation/framework-contexts/src/main/java/com/netcracker/cloud/framework/contexts/allowedheaders/HeaderPropagationConfiguration.java +++ b/core-context-propagation/framework-contexts/src/main/java/com/netcracker/cloud/framework/contexts/allowedheaders/HeaderPropagationConfiguration.java @@ -1,21 +1,43 @@ package com.netcracker.cloud.framework.contexts.allowedheaders; import java.util.Arrays; -import java.util.Collections; import java.util.List; import java.util.Locale; import java.util.Set; import java.util.concurrent.atomic.AtomicReference; import java.util.stream.Collectors; +/** + * Computes the effective set of headers that must not be propagated to outgoing requests. + * + *

Model

+ *
    + *
  • The framework owns an internal blocklist ({@link #INTERNAL_BLOCKED_HEADERS}). It is + * hard-coded and cannot be changed from configuration. By default it blocks + * {@code X-Channel-Request-Id}.
  • + *
  • A user-facing system property {@value #ALLOW_BLOCKED_PROPERTY} carries a + * comma-separated list of header names that should be exempted from the internal + * blocklist (i.e. allowed to propagate). Names that are not in the internal + * blocklist have no effect.
  • + *
+ * + *

How the property is supplied

+ * The Quarkus extension copies {@code quarkus.context.propagation.allow-blocked-headers} into this system property + * at {@code RUNTIME_INIT}; the Spring configuration does the same for the Spring-style + * {@code context.propagation.allow-blocked-headers} property. In both cases standard env-to-property mapping + * applies, so a container ENV variable like {@code CONTEXT_PROPAGATION_ALLOW_BLOCKED_HEADERS=X-Channel-Request-Id} + * propagates all the way through to this class. + */ public final class HeaderPropagationConfiguration { - public static final String HEADERS_BLOCKED_PROPERTY = "headers.blocked"; - public static final String HEADERS_BLOCKED_ENV = "HEADERS_BLOCKED"; - public static final List DEFAULT_BLOCKED_HEADERS = - List.of("X-Channel-Request-Id"); - public static final List NON_BLOCKABLE_HEADERS = - List.of("X-Request-Id"); + /** System property carrying exempted-from-blocklist header names, comma-separated. */ + public static final String ALLOW_BLOCKED_PROPERTY = "context.propagation.allow-blocked-headers"; + + /** + * Hard-coded internal blocklist of headers that the framework refuses to propagate + * unless explicitly exempted via {@link #ALLOW_BLOCKED_PROPERTY}. + */ + public static final List INTERNAL_BLOCKED_HEADERS = List.of("X-Channel-Request-Id"); private static final AtomicReference cachedHeaders = new AtomicReference<>(null); @@ -41,7 +63,7 @@ private static CachedHeaders getOrInit() { synchronized (HeaderPropagationConfiguration.class) { local = cachedHeaders.get(); if (local == null) { - local = new CachedHeaders(readBlockedHeaders()); + local = new CachedHeaders(computeEffectiveBlocklist()); cachedHeaders.set(local); } } @@ -64,34 +86,25 @@ public static boolean isBlacklisted(String headerName) { return getOrInit().lowerSet.contains(headerName.toLowerCase(Locale.ROOT)); } - private static List readBlockedHeaders() { - boolean propertySpecified = System.getProperties().containsKey(HEADERS_BLOCKED_PROPERTY); - String envValue = System.getenv(HEADERS_BLOCKED_ENV); - boolean envSpecified = envValue != null; - - // No source set at all → fall back to the built-in default. - if (!propertySpecified && !envSpecified) { - return DEFAULT_BLOCKED_HEADERS; + private static List computeEffectiveBlocklist() { + Set exemptions = readExemptions(); + if (exemptions.isEmpty()) { + return INTERNAL_BLOCKED_HEADERS; } + return INTERNAL_BLOCKED_HEADERS.stream() + .filter(h -> !exemptions.contains(h.toLowerCase(Locale.ROOT))) + .toList(); + } - // Property wins over env when both are set. - String raw = propertySpecified - ? System.getProperty(HEADERS_BLOCKED_PROPERTY) - : envValue; - - // Source is set but empty/blank → explicit "erase the default". + private static Set readExemptions() { + String raw = System.getProperty(ALLOW_BLOCKED_PROPERTY); if (raw == null || raw.isBlank()) { - return Collections.emptyList(); + return Set.of(); } - - // Source is set with a value → parse, trim, drop non-blockable entries. - // If everything filters out, we still respect the user's explicit override - // and return an empty list — we do NOT silently restore the default. return Arrays.stream(raw.split(",")) .map(String::trim) .filter(s -> !s.isEmpty()) - .filter(s -> NON_BLOCKABLE_HEADERS.stream() - .noneMatch(s::equalsIgnoreCase)) - .toList(); + .map(s -> s.toLowerCase(Locale.ROOT)) + .collect(Collectors.toUnmodifiableSet()); } } diff --git a/core-context-propagation/framework-contexts/src/test/java/com/netcracker/cloud/framework/contexts/allowedheaders/HeaderPropagationConfigurationTest.java b/core-context-propagation/framework-contexts/src/test/java/com/netcracker/cloud/framework/contexts/allowedheaders/HeaderPropagationConfigurationTest.java index 68247fa37..de57edc09 100644 --- a/core-context-propagation/framework-contexts/src/test/java/com/netcracker/cloud/framework/contexts/allowedheaders/HeaderPropagationConfigurationTest.java +++ b/core-context-propagation/framework-contexts/src/test/java/com/netcracker/cloud/framework/contexts/allowedheaders/HeaderPropagationConfigurationTest.java @@ -1,157 +1,84 @@ package com.netcracker.cloud.framework.contexts.allowedheaders; -import java.util.List; - import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import uk.org.webcompere.systemstubs.environment.EnvironmentVariables; -import uk.org.webcompere.systemstubs.jupiter.SystemStub; -import uk.org.webcompere.systemstubs.jupiter.SystemStubsExtension; -@ExtendWith(SystemStubsExtension.class) class HeaderPropagationConfigurationTest { - @SystemStub - private EnvironmentVariables environmentVariables = new EnvironmentVariables("TEST_PROP_FOR_ENV_SETUP", "1"); @BeforeEach void setup() { - System.clearProperty(HeaderPropagationConfiguration.HEADERS_BLOCKED_PROPERTY); + System.clearProperty(HeaderPropagationConfiguration.ALLOW_BLOCKED_PROPERTY); HeaderPropagationConfiguration.resetCache(); } @AfterEach void cleanup() { - System.clearProperty(HeaderPropagationConfiguration.HEADERS_BLOCKED_PROPERTY); - HeaderPropagationConfiguration.resetCache(); - } - - @Test - void shouldBlacklistHeaderByPropertyName() { - System.setProperty(HeaderPropagationConfiguration.HEADERS_BLOCKED_PROPERTY, "X-Channel-Request-Id"); + System.clearProperty(HeaderPropagationConfiguration.ALLOW_BLOCKED_PROPERTY); HeaderPropagationConfiguration.resetCache(); - - Assertions.assertTrue(HeaderPropagationConfiguration.isBlacklisted("X-Channel-Request-Id")); - Assertions.assertTrue(HeaderPropagationConfiguration.isBlacklisted("x-channel-request-id")); - Assertions.assertFalse(HeaderPropagationConfiguration.isBlacklisted("X-Request-Id")); - } - - @Test - void shouldParseCommaSeparatedBlacklistedHeaders() { - System.setProperty(HeaderPropagationConfiguration.HEADERS_BLOCKED_PROPERTY, "X-Channel-Request-Id, X-Request-Id"); - HeaderPropagationConfiguration.resetCache(); - - Assertions.assertFalse(HeaderPropagationConfiguration.isBlacklisted("X-Request-Id")); - Assertions.assertTrue(HeaderPropagationConfiguration.isBlacklisted("x-channel-request-id")); - Assertions.assertFalse(HeaderPropagationConfiguration.isBlacklisted("Custom-Header")); } @Test - void shouldBlacklistXChannelRequestIdByDefault() { + void shouldBlockXChannelRequestIdByDefault() { Assertions.assertTrue(HeaderPropagationConfiguration.isBlacklisted("X-Channel-Request-Id")); Assertions.assertTrue(HeaderPropagationConfiguration.isBlacklisted("x-channel-request-id")); - Assertions.assertFalse(HeaderPropagationConfiguration.isBlacklisted("X-Request-Id")); + Assertions.assertEquals(HeaderPropagationConfiguration.INTERNAL_BLOCKED_HEADERS, + HeaderPropagationConfiguration.blockedHeaders()); } @Test - void shouldNotBlacklistXChannelRequestIdWhenBlockedHeadersExplicitlyEmpty() { - System.setProperty(HeaderPropagationConfiguration.HEADERS_BLOCKED_PROPERTY, ""); + void shouldNotBlockXChannelRequestIdWhenExempted() { + System.setProperty(HeaderPropagationConfiguration.ALLOW_BLOCKED_PROPERTY, "X-Channel-Request-Id"); HeaderPropagationConfiguration.resetCache(); Assertions.assertFalse(HeaderPropagationConfiguration.isBlacklisted("X-Channel-Request-Id")); Assertions.assertFalse(HeaderPropagationConfiguration.isBlacklisted("x-channel-request-id")); - Assertions.assertFalse(HeaderPropagationConfiguration.isBlacklisted("X-Request-Id")); + Assertions.assertTrue(HeaderPropagationConfiguration.blockedHeaders().isEmpty()); } @Test - void shouldNotBlacklistXChannelRequestIdWhenOtherHeadersExplicitlyBlocked() { - // The property is explicitly set to a non-blockable header; the default - // blocked list must NOT silently kick in. - System.setProperty(HeaderPropagationConfiguration.HEADERS_BLOCKED_PROPERTY, "X-Request-Id"); + void shouldApplyExemptionsCaseInsensitively() { + System.setProperty(HeaderPropagationConfiguration.ALLOW_BLOCKED_PROPERTY, "x-channel-request-id"); HeaderPropagationConfiguration.resetCache(); - Assertions.assertFalse(HeaderPropagationConfiguration.isBlacklisted("X-Request-Id")); Assertions.assertFalse(HeaderPropagationConfiguration.isBlacklisted("X-Channel-Request-Id")); } @Test - void shouldReturnEmptyListWhenOnlyNonBlockableHeadersConfigured() { - System.setProperty(HeaderPropagationConfiguration.HEADERS_BLOCKED_PROPERTY, "X-Request-Id, x-request-id"); + void shouldIgnoreUnknownExemptionEntries() { + // Names that are not in the internal blocklist must not change anything. + System.setProperty(HeaderPropagationConfiguration.ALLOW_BLOCKED_PROPERTY, "Custom-Header, X-Request-Id"); HeaderPropagationConfiguration.resetCache(); + Assertions.assertTrue(HeaderPropagationConfiguration.isBlacklisted("X-Channel-Request-Id"), + "Internal blocklist must remain unchanged when no exemption matches it"); + Assertions.assertFalse(HeaderPropagationConfiguration.isBlacklisted("Custom-Header")); Assertions.assertFalse(HeaderPropagationConfiguration.isBlacklisted("X-Request-Id")); - Assertions.assertFalse(HeaderPropagationConfiguration.isBlacklisted("X-Channel-Request-Id")); - Assertions.assertTrue(HeaderPropagationConfiguration.blockedHeaders().isEmpty()); } @Test - void shouldReadEnvWhenPropertyNotSet() { - // Env value is read when no system property is set. The configured value - // is only X-Request-Id (non-blockable), so the resulting list must be empty — - // we deliberately do NOT fall back to the default blocked list here. - environmentVariables.set(HeaderPropagationConfiguration.HEADERS_BLOCKED_ENV, "X-Request-Id"); - try { - HeaderPropagationConfiguration.resetCache(); - Assertions.assertFalse(HeaderPropagationConfiguration.isBlacklisted("X-Request-Id")); - Assertions.assertFalse(HeaderPropagationConfiguration.isBlacklisted("X-Channel-Request-Id")); - Assertions.assertTrue(HeaderPropagationConfiguration.blockedHeaders().isEmpty()); - } finally { - environmentVariables.remove(HeaderPropagationConfiguration.HEADERS_BLOCKED_ENV); - HeaderPropagationConfiguration.resetCache(); - } - } + void shouldTreatEmptyExemptionPropertyAsNoExemption() { + System.setProperty(HeaderPropagationConfiguration.ALLOW_BLOCKED_PROPERTY, ""); + HeaderPropagationConfiguration.resetCache(); - @Test - void shouldBlacklistHeaderByEnvWhenPropertyNotSet() throws Exception { - // Sanity check that env-sourced configuration actually drives the blocked list - // when the system property is absent. - environmentVariables.set(HeaderPropagationConfiguration.HEADERS_BLOCKED_ENV, "Custom-Header"); - try { - HeaderPropagationConfiguration.resetCache(); - Assertions.assertTrue(HeaderPropagationConfiguration.isBlacklisted("Custom-Header")); - Assertions.assertFalse(HeaderPropagationConfiguration.isBlacklisted("X-Channel-Request-Id")); - } finally { - environmentVariables.remove(HeaderPropagationConfiguration.HEADERS_BLOCKED_ENV); - HeaderPropagationConfiguration.resetCache(); - } + Assertions.assertTrue(HeaderPropagationConfiguration.isBlacklisted("X-Channel-Request-Id")); + Assertions.assertEquals(HeaderPropagationConfiguration.INTERNAL_BLOCKED_HEADERS, + HeaderPropagationConfiguration.blockedHeaders()); } @Test - void shouldNotBlockXRequestIdEvenWhenExplicitlyListed() { - System.setProperty(HeaderPropagationConfiguration.HEADERS_BLOCKED_PROPERTY, "X-Channel-Request-Id, X-Request-Id"); + void shouldTreatBlankAndCommaOnlyExemptionPropertyAsNoExemption() { + System.setProperty(HeaderPropagationConfiguration.ALLOW_BLOCKED_PROPERTY, " , ,, "); HeaderPropagationConfiguration.resetCache(); - - // X-Request-Id is never blocked - Assertions.assertFalse(HeaderPropagationConfiguration.isBlacklisted("X-Request-Id")); - // X-Channel-Request-Id blocked + Assertions.assertTrue(HeaderPropagationConfiguration.isBlacklisted("X-Channel-Request-Id")); - // there is no X-Request-Id in a list - Assertions.assertFalse(HeaderPropagationConfiguration.blockedHeaders() - .stream().anyMatch(h -> h.equalsIgnoreCase("X-Request-Id"))); } - - @Test - void shouldReturnEmptyListWhenEnvExplicitlyEmpty() throws Exception { - environmentVariables.set(HeaderPropagationConfiguration.HEADERS_BLOCKED_ENV, ""); - try { - HeaderPropagationConfiguration.resetCache(); - Assertions.assertFalse(HeaderPropagationConfiguration.isBlacklisted("X-Channel-Request-Id")); - Assertions.assertTrue(HeaderPropagationConfiguration.blockedHeaders().isEmpty()); - } finally { - environmentVariables.remove(HeaderPropagationConfiguration.HEADERS_BLOCKED_ENV); - HeaderPropagationConfiguration.resetCache(); - } - } - + @Test - void shouldBlockedHeadersReturnCorrectList() { - System.setProperty(HeaderPropagationConfiguration.HEADERS_BLOCKED_PROPERTY, "Custom-Header, X-Request-Id"); - HeaderPropagationConfiguration.resetCache(); - - List blocked = HeaderPropagationConfiguration.blockedHeaders(); - Assertions.assertTrue(blocked.contains("Custom-Header")); - Assertions.assertFalse(blocked.stream().anyMatch(h -> h.equalsIgnoreCase("X-Request-Id"))); + void isBlacklistedShouldReturnFalseForNullAndBlank() { + Assertions.assertFalse(HeaderPropagationConfiguration.isBlacklisted(null)); + Assertions.assertFalse(HeaderPropagationConfiguration.isBlacklisted("")); + Assertions.assertFalse(HeaderPropagationConfiguration.isBlacklisted(" ")); } } diff --git a/core-context-propagation/framework-contexts/src/test/java/com/netcracker/cloud/framework/contexts/xchannelrequestid/XChannelRequestIdContextObjectPropagationTest.java b/core-context-propagation/framework-contexts/src/test/java/com/netcracker/cloud/framework/contexts/xchannelrequestid/XChannelRequestIdContextObjectPropagationTest.java index e806c6573..c26e5c2a3 100644 --- a/core-context-propagation/framework-contexts/src/test/java/com/netcracker/cloud/framework/contexts/xchannelrequestid/XChannelRequestIdContextObjectPropagationTest.java +++ b/core-context-propagation/framework-contexts/src/test/java/com/netcracker/cloud/framework/contexts/xchannelrequestid/XChannelRequestIdContextObjectPropagationTest.java @@ -69,20 +69,17 @@ void testXChannelRequestIdPropagationWithResponsePropagatableData() { } @Test - void testXChannelRequestIdPropagationIsBlockedWhenConfigured() { - System.setProperty(HeaderPropagationConfiguration.HEADERS_BLOCKED_PROPERTY, X_CHANNEL_REQUEST_ID); + void testXChannelRequestIdPropagationIsBlockedByInternalBlocklist() { + System.clearProperty(HeaderPropagationConfiguration.ALLOW_BLOCKED_PROPERTY); HeaderPropagationConfiguration.resetCache(); - try { - RequestContextPropagation.initRequestContext(new ContextDataRequest()); // filter - ContextDataResponse responseContextData = new ContextDataResponse(); - RequestContextPropagation.setResponsePropagatableData(responseContextData); - Assertions.assertEquals("-", responseContextData.getResponseHeaders().get(X_CHANNEL_REQUEST_ID)); - - Map> serializableContextData = ContextManager.getSerializableContextData(); - Assertions.assertTrue(serializableContextData.getOrDefault(X_CHANNEL_REQUEST_ID_CONTEXT_NAME, Collections.emptyMap()).isEmpty()); - } finally { - System.clearProperty(HeaderPropagationConfiguration.HEADERS_BLOCKED_PROPERTY); - } + + RequestContextPropagation.initRequestContext(new ContextDataRequest()); // filter + ContextDataResponse responseContextData = new ContextDataResponse(); + RequestContextPropagation.setResponsePropagatableData(responseContextData); + Assertions.assertEquals("-", responseContextData.getResponseHeaders().get(X_CHANNEL_REQUEST_ID)); + + Map> serializableContextData = ContextManager.getSerializableContextData(); + Assertions.assertTrue(serializableContextData.getOrDefault(X_CHANNEL_REQUEST_ID_CONTEXT_NAME, Collections.emptyMap()).isEmpty()); } @Test diff --git a/core-context-propagation/sample-context-tests/src/test/java/com/netcracker/cloud/context/propagation/sample/requests/RequestPropagationSpringCommonTest.java b/core-context-propagation/sample-context-tests/src/test/java/com/netcracker/cloud/context/propagation/sample/requests/RequestPropagationSpringCommonTest.java index 50cfdc264..01bbb10e9 100644 --- a/core-context-propagation/sample-context-tests/src/test/java/com/netcracker/cloud/context/propagation/sample/requests/RequestPropagationSpringCommonTest.java +++ b/core-context-propagation/sample-context-tests/src/test/java/com/netcracker/cloud/context/propagation/sample/requests/RequestPropagationSpringCommonTest.java @@ -30,7 +30,7 @@ class RequestPropagationSpringCommonTest { @BeforeEach void setUp() { mockMvc = MockMvcBuilders.webAppContextSetup(context).addFilter(filter).build(); - System.clearProperty("headers.blocked"); + System.clearProperty("context.propagation.allow-blocked-headers"); } @Test diff --git a/core-context-propagation/sample-context-tests/src/test/java/com/netcracker/cloud/context/propagation/sample/requests/RequestPropagationTest.java b/core-context-propagation/sample-context-tests/src/test/java/com/netcracker/cloud/context/propagation/sample/requests/RequestPropagationTest.java index a9f9fb421..e62da4eba 100644 --- a/core-context-propagation/sample-context-tests/src/test/java/com/netcracker/cloud/context/propagation/sample/requests/RequestPropagationTest.java +++ b/core-context-propagation/sample-context-tests/src/test/java/com/netcracker/cloud/context/propagation/sample/requests/RequestPropagationTest.java @@ -34,7 +34,8 @@ TestController.class, RequestPropagationTestConfig.class}) @TestPropertySource(properties = { "headers.allowed=custom-header", - "headers.blocked=", + // context.propagation.allow-blocked-headers deliberately not set — the internal blocklist applies and + // X-Channel-Request-Id should not propagate to outgoing requests. "cloud-core.context-propagation.url=/test_url/v111/test" }) class RequestPropagationTest { @@ -64,7 +65,7 @@ class RequestPropagationTest { @BeforeEach void setUp() { - System.clearProperty("headers.blocked"); + System.clearProperty("context.propagation.allow-blocked-headers"); HeaderPropagationConfiguration.resetCache(); ContextManager.reinitialize(); mockMvc = MockMvcBuilders.webAppContextSetup(context).addFilters(preAuthnFilter, postAuthnFilter).build(); @@ -107,15 +108,14 @@ void testRequestPropagation() throws Exception { } @Test - void testXRequestIdStillPropagatesWhenAddedToBlockedList() throws Exception { - System.setProperty("headers.blocked", X_REQUEST_ID_NAME); + void testXRequestIdNotAffectedByExemptionConfig() throws Exception { + System.setProperty("context.propagation.allow-blocked-headers", X_REQUEST_ID_NAME); HeaderPropagationConfiguration.resetCache(); ContextManager.reinitialize(); MockRestServiceServer mockServer = MockRestServiceServer.bindTo(restTemplate).build(); mockServer.expect(requestTo("/chain_request")) .andExpect(header(HttpHeaders.ACCEPT_LANGUAGE, ACCEPT_LANGUAGE_VALUE)) - // X-Request-Id is non-blockable and must still be propagated. .andExpect(header(X_REQUEST_ID_NAME, X_REQUEST_ID_VALUE)) .andExpect(header(CUSTOM_NAME, CUSTOM_VALUE)) .andRespond(withSuccess()); diff --git a/core-context-propagation/sample-context-tests/src/test/java/com/netcracker/cloud/context/propagation/sample/requests/RequestPropagationXChannelRequestIdAllowedTest.java b/core-context-propagation/sample-context-tests/src/test/java/com/netcracker/cloud/context/propagation/sample/requests/RequestPropagationXChannelRequestIdAllowedTest.java index fa47551a4..3c567f1fb 100644 --- a/core-context-propagation/sample-context-tests/src/test/java/com/netcracker/cloud/context/propagation/sample/requests/RequestPropagationXChannelRequestIdAllowedTest.java +++ b/core-context-propagation/sample-context-tests/src/test/java/com/netcracker/cloud/context/propagation/sample/requests/RequestPropagationXChannelRequestIdAllowedTest.java @@ -33,7 +33,7 @@ TestController.class, RequestPropagationTestConfig.class}) @TestPropertySource(properties = { "headers.allowed=custom-header", - "headers.blocked=", + "context.propagation.allow-blocked-headers=X-Channel-Request-Id", "cloud-core.context-propagation.url=/test_url/v111/test" }) class RequestPropagationXChannelRequestIdAllowedTest { @@ -64,7 +64,7 @@ class RequestPropagationXChannelRequestIdAllowedTest { @BeforeAll static void beforeAll() { System.setProperty("headers.allowed", "custom-header"); - System.setProperty("headers.blocked", ""); + System.setProperty("context.propagation.allow-blocked-headers", "X-Channel-Request-Id"); HeaderPropagationConfiguration.resetCache(); ContextManager.reinitialize(); } @@ -72,7 +72,7 @@ static void beforeAll() { @AfterAll static void afterAll() { System.clearProperty("headers.allowed"); - System.clearProperty("headers.blocked"); + System.clearProperty("context.propagation.allow-blocked-headers"); HeaderPropagationConfiguration.resetCache(); ContextManager.reinitialize(); } diff --git a/core-context-propagation/sample-context-tests/src/test/java/com/netcracker/cloud/context/propagation/sample/requests/RequestPropagationXChannelRequestIdResponseTest.java b/core-context-propagation/sample-context-tests/src/test/java/com/netcracker/cloud/context/propagation/sample/requests/RequestPropagationXChannelRequestIdResponseTest.java index d60295141..19d649a2c 100644 --- a/core-context-propagation/sample-context-tests/src/test/java/com/netcracker/cloud/context/propagation/sample/requests/RequestPropagationXChannelRequestIdResponseTest.java +++ b/core-context-propagation/sample-context-tests/src/test/java/com/netcracker/cloud/context/propagation/sample/requests/RequestPropagationXChannelRequestIdResponseTest.java @@ -31,7 +31,7 @@ @TestPropertySource(properties = { "headers.allowed=custom-header", "cloud-core.context-propagation.url=/test_url/v111/test" - // headers.blocked is not set, X-Channel-Request-Id blocked for outgoing requests + // context.propagation.allow-blocked-headers is not set, internal blocklist applies → X-Channel-Request-Id blocked for outgoing requests }) class RequestPropagationXChannelRequestIdResponseTest { @@ -55,14 +55,14 @@ class RequestPropagationXChannelRequestIdResponseTest { @BeforeAll static void beforeAll() { - System.clearProperty("headers.blocked"); + System.clearProperty("context.propagation.allow-blocked-headers"); HeaderPropagationConfiguration.resetCache(); ContextManager.reinitialize(); } @AfterAll static void afterAll() { - System.clearProperty("headers.blocked"); + System.clearProperty("context.propagation.allow-blocked-headers"); HeaderPropagationConfiguration.resetCache(); ContextManager.reinitialize(); } diff --git a/core-context-propagation/spring-context-aggregator/context-propagation-spring-common/src/main/java/com/netcracker/cloud/context/propagation/spring/common/configuration/SpringContextProviderConfiguration.java b/core-context-propagation/spring-context-aggregator/context-propagation-spring-common/src/main/java/com/netcracker/cloud/context/propagation/spring/common/configuration/SpringContextProviderConfiguration.java index 3166e2397..b06fc1b31 100644 --- a/core-context-propagation/spring-context-aggregator/context-propagation-spring-common/src/main/java/com/netcracker/cloud/context/propagation/spring/common/configuration/SpringContextProviderConfiguration.java +++ b/core-context-propagation/spring-context-aggregator/context-propagation-spring-common/src/main/java/com/netcracker/cloud/context/propagation/spring/common/configuration/SpringContextProviderConfiguration.java @@ -13,12 +13,14 @@ @Configuration public class SpringContextProviderConfiguration { + @Bean - public SpringPostAuthnContextProviderFilter springPostAuthnContextProviderFilter(){ + public SpringPostAuthnContextProviderFilter springPostAuthnContextProviderFilter() { return new SpringPostAuthnContextProviderFilter(); } + @Bean - public SpringPreAuthnContextProviderFilter springPreAuthnContextProviderFilter(){ + public SpringPreAuthnContextProviderFilter springPreAuthnContextProviderFilter() { return new SpringPreAuthnContextProviderFilter(); } @@ -29,14 +31,14 @@ public SpringPreAuthnContextProviderFilter springPreAuthnContextProviderFilter() @Value("${headers.allowed:}") private String allowedHeaders; - @Value("${headers.blocked:}") - private String blockedHeaders; + @Value("${context.propagation.allow-blocked-headers:}") + private String allowedFromBlocklist; @PostConstruct public void init() { System.setProperty("headers.allowed", allowedHeaders); - if (environment.containsProperty("headers.blocked")) { - System.setProperty("headers.blocked", blockedHeaders); + if (environment.containsProperty("context.propagation.allow-blocked-headers")) { + System.setProperty("context.propagation.allow-blocked-headers", allowedFromBlocklist); } } } diff --git a/core-context-propagation/spring-context-aggregator/context-propagation-spring-common/src/test/java/com/netcracker/cloud/context/propagation/spring/common/configuration/HeaderPropagationStateReset.java b/core-context-propagation/spring-context-aggregator/context-propagation-spring-common/src/test/java/com/netcracker/cloud/context/propagation/spring/common/configuration/HeaderPropagationStateReset.java index 8c0a30297..3f92991b4 100644 --- a/core-context-propagation/spring-context-aggregator/context-propagation-spring-common/src/test/java/com/netcracker/cloud/context/propagation/spring/common/configuration/HeaderPropagationStateReset.java +++ b/core-context-propagation/spring-context-aggregator/context-propagation-spring-common/src/test/java/com/netcracker/cloud/context/propagation/spring/common/configuration/HeaderPropagationStateReset.java @@ -7,11 +7,19 @@ import com.netcracker.cloud.framework.contexts.allowedheaders.HeaderPropagationConfiguration; public class HeaderPropagationStateReset implements BeforeAllCallback, AfterAllCallback { - @Override public void beforeAll(ExtensionContext ctx) { reset(); } - @Override public void afterAll(ExtensionContext ctx) { reset(); } + + @Override + public void beforeAll(ExtensionContext ctx) { + reset(); + } + + @Override + public void afterAll(ExtensionContext ctx) { + reset(); + } private static void reset() { - System.clearProperty("headers.blocked"); + System.clearProperty("context.propagation.allow-blocked-headers"); System.clearProperty("headers.allowed"); HeaderPropagationConfiguration.resetCache(); } diff --git a/core-context-propagation/spring-context-aggregator/context-propagation-spring-common/src/test/java/com/netcracker/cloud/context/propagation/spring/common/configuration/SpringContextProviderConfigurationEmptyTest.java b/core-context-propagation/spring-context-aggregator/context-propagation-spring-common/src/test/java/com/netcracker/cloud/context/propagation/spring/common/configuration/SpringContextProviderConfigurationEmptyTest.java index f9c0905ad..b5fe96476 100644 --- a/core-context-propagation/spring-context-aggregator/context-propagation-spring-common/src/test/java/com/netcracker/cloud/context/propagation/spring/common/configuration/SpringContextProviderConfigurationEmptyTest.java +++ b/core-context-propagation/spring-context-aggregator/context-propagation-spring-common/src/test/java/com/netcracker/cloud/context/propagation/spring/common/configuration/SpringContextProviderConfigurationEmptyTest.java @@ -1,6 +1,7 @@ package com.netcracker.cloud.context.propagation.spring.common.configuration; import com.netcracker.cloud.framework.contexts.allowedheaders.HeaderPropagationConfiguration; + import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.test.annotation.DirtiesContext; @@ -9,33 +10,24 @@ import org.springframework.test.context.junit.jupiter.SpringExtension; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; -/** - * Spring integration scenario: {@code headers.blocked=} (set explicitly to empty). - * Verifies that {@link SpringContextProviderConfiguration#init()} propagates the - * empty value to the system property, which downstream code interprets as - * "erase the default blocked list". - */ @ExtendWith({HeaderPropagationStateReset.class, SpringExtension.class}) @ContextConfiguration(classes = SpringContextProviderConfiguration.class) @TestPropertySource(properties = { "headers.allowed=custom-header", - "headers.blocked=" + "context.propagation.allow-blocked-headers=" }) @DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS) class SpringContextProviderConfigurationEmptyTest { @Test - void shouldSetEmptySystemPropertyAndEraseDefaultBlockedList() { - assertEquals("", System.getProperty("headers.blocked"), + void shouldSetEmptySystemPropertyAndStillApplyInternalBlocklist() { + assertEquals("", System.getProperty("context.propagation.allow-blocked-headers"), "Spring init() must propagate explicit empty value to the system property"); HeaderPropagationConfiguration.resetCache(); - assertTrue(HeaderPropagationConfiguration.blockedHeaders().isEmpty(), - "explicit empty value must erase the default blocked list"); - assertFalse(HeaderPropagationConfiguration.isBlacklisted("X-Channel-Request-Id"), - "X-Channel-Request-Id must no longer be blocked"); + assertTrue(HeaderPropagationConfiguration.isBlacklisted("X-Channel-Request-Id"), + "Internal blocklist must still apply when the exemption property is blank"); } } diff --git a/core-context-propagation/spring-context-aggregator/context-propagation-spring-common/src/test/java/com/netcracker/cloud/context/propagation/spring/common/configuration/SpringContextProviderConfigurationFromEnvVarTest.java b/core-context-propagation/spring-context-aggregator/context-propagation-spring-common/src/test/java/com/netcracker/cloud/context/propagation/spring/common/configuration/SpringContextProviderConfigurationFromEnvVarTest.java index 4b56a696f..121acc73b 100644 --- a/core-context-propagation/spring-context-aggregator/context-propagation-spring-common/src/test/java/com/netcracker/cloud/context/propagation/spring/common/configuration/SpringContextProviderConfigurationFromEnvVarTest.java +++ b/core-context-propagation/spring-context-aggregator/context-propagation-spring-common/src/test/java/com/netcracker/cloud/context/propagation/spring/common/configuration/SpringContextProviderConfigurationFromEnvVarTest.java @@ -15,38 +15,27 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; -/** - * Spring integration scenario: {@code headers.blocked} is configured only via the - * {@code HEADERS_BLOCKED} environment variable. Verifies that Spring's relaxed - * binding picks up the env var as the {@code headers.blocked} property, that - * {@link SpringContextProviderConfiguration#init()} propagates it to the system - * property, and that the downstream blocked list reflects the env-sourced value. - * - *

{@link SystemStubsExtension} must be registered before {@link SpringExtension} - * so that the env var is set before Spring's {@code SystemEnvironmentPropertySource} - * is consulted during context initialization.

- */ @ExtendWith({HeaderPropagationStateReset.class, SystemStubsExtension.class, SpringExtension.class}) @ContextConfiguration(classes = SpringContextProviderConfiguration.class) @TestPropertySource(properties = { - // headers.blocked deliberately not declared here — it must come from the env var + // context.propagation.allow-blocked-headers deliberately NOT declared here — it must come from the env var "headers.allowed=custom-header" }) @DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS) class SpringContextProviderConfigurationFromEnvVarTest { @SystemStub - static EnvironmentVariables envVars = new EnvironmentVariables("HEADERS_BLOCKED", "Custom-Header"); + static EnvironmentVariables envVars = new EnvironmentVariables("CONTEXT_PROPAGATION_ALLOW_BLOCKED_HEADERS", "X-Channel-Request-Id"); @Test - void shouldReadHeadersBlockedFromEnvVar() { - assertEquals("Custom-Header", System.getProperty("headers.blocked"), + void shouldReadHeadersAllowBlockedFromEnvVar() { + assertEquals("X-Channel-Request-Id", System.getProperty("context.propagation.allow-blocked-headers"), "Spring init() must propagate env-sourced value to the system property"); HeaderPropagationConfiguration.resetCache(); - assertTrue(HeaderPropagationConfiguration.isBlacklisted("Custom-Header"), - "env-sourced header must be blocked"); assertFalse(HeaderPropagationConfiguration.isBlacklisted("X-Channel-Request-Id"), - "explicit env-sourced configuration overrides the default"); + "Env-sourced exemption must take effect"); + assertTrue(HeaderPropagationConfiguration.blockedHeaders().isEmpty(), + "The only entry of the internal blocklist must be removed by the exemption"); } } diff --git a/core-context-propagation/spring-context-aggregator/context-propagation-spring-common/src/test/java/com/netcracker/cloud/context/propagation/spring/common/configuration/SpringContextProviderConfigurationNotConfiguredTest.java b/core-context-propagation/spring-context-aggregator/context-propagation-spring-common/src/test/java/com/netcracker/cloud/context/propagation/spring/common/configuration/SpringContextProviderConfigurationNotConfiguredTest.java index 1d4ba15fa..a9da90895 100644 --- a/core-context-propagation/spring-context-aggregator/context-propagation-spring-common/src/test/java/com/netcracker/cloud/context/propagation/spring/common/configuration/SpringContextProviderConfigurationNotConfiguredTest.java +++ b/core-context-propagation/spring-context-aggregator/context-propagation-spring-common/src/test/java/com/netcracker/cloud/context/propagation/spring/common/configuration/SpringContextProviderConfigurationNotConfiguredTest.java @@ -11,27 +11,22 @@ import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; -/** - * Spring integration scenario: {@code headers.blocked} is not set anywhere. - * Verifies that {@link SpringContextProviderConfiguration#init()} does NOT touch - * the {@code headers.blocked} system property, so downstream code falls back to - * the built-in default blocked list (which contains {@code X-Channel-Request-Id}). - */ @ExtendWith({HeaderPropagationStateReset.class, SpringExtension.class}) @ContextConfiguration(classes = SpringContextProviderConfiguration.class) @TestPropertySource(properties = { + // context.propagation.allow-blocked-headers intentionally absent "headers.allowed=custom-header" }) @DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS) class SpringContextProviderConfigurationNotConfiguredTest { @Test - void shouldNotSetSystemPropertyAndApplyDefaultBlockedList() { - assertNull(System.getProperty("headers.blocked"), - "headers.blocked must remain unset when no source configures it"); + void shouldNotTouchSystemPropertyAndKeepInternalBlocklist() { + assertNull(System.getProperty("context.propagation.allow-blocked-headers"), + "context.propagation.allow-blocked-headers must remain unset when no source configures it"); HeaderPropagationConfiguration.resetCache(); assertTrue(HeaderPropagationConfiguration.isBlacklisted("X-Channel-Request-Id"), - "default blocked list must apply when nothing is configured"); + "Internal blocklist must apply when no exemption is configured"); } } diff --git a/core-context-propagation/spring-context-aggregator/context-propagation-spring-common/src/test/java/com/netcracker/cloud/context/propagation/spring/common/configuration/SpringContextProviderConfigurationOnlyNonBlockableTest.java b/core-context-propagation/spring-context-aggregator/context-propagation-spring-common/src/test/java/com/netcracker/cloud/context/propagation/spring/common/configuration/SpringContextProviderConfigurationOnlyNonBlockableTest.java deleted file mode 100644 index b6649f58b..000000000 --- a/core-context-propagation/spring-context-aggregator/context-propagation-spring-common/src/test/java/com/netcracker/cloud/context/propagation/spring/common/configuration/SpringContextProviderConfigurationOnlyNonBlockableTest.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.netcracker.cloud.context.propagation.spring.common.configuration; - -import com.netcracker.cloud.framework.contexts.allowedheaders.HeaderPropagationConfiguration; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.TestPropertySource; -import org.springframework.test.context.junit.jupiter.SpringExtension; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; - -/** - * Spring integration scenario: {@code headers.blocked=X-Request-Id} — the configured - * value consists exclusively of a non-blockable header. The resulting blocked list - * must be empty, NOT the built-in default. This locks in the behavior change made - * to {@link HeaderPropagationConfiguration} (no silent fallback to default when the - * user explicitly configured something). - */ -@ExtendWith({HeaderPropagationStateReset.class, SpringExtension.class}) -@ContextConfiguration(classes = SpringContextProviderConfiguration.class) -@TestPropertySource(properties = { - "headers.allowed=custom-header", - "headers.blocked=X-Request-Id" -}) -@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS) -class SpringContextProviderConfigurationOnlyNonBlockableTest { - - @Test - void shouldRespectExplicitOverrideEvenWhenItFiltersToEmpty() { - assertEquals("X-Request-Id", System.getProperty("headers.blocked")); - - HeaderPropagationConfiguration.resetCache(); - assertTrue(HeaderPropagationConfiguration.blockedHeaders().isEmpty(), - "the resulting blocked list must be empty — the default must NOT be restored"); - assertFalse(HeaderPropagationConfiguration.isBlacklisted("X-Request-Id"), - "X-Request-Id is non-blockable and must never be blocked"); - assertFalse(HeaderPropagationConfiguration.isBlacklisted("X-Channel-Request-Id"), - "explicit (even if filter-emptied) configuration overrides the default"); - } -} diff --git a/core-context-propagation/spring-context-aggregator/context-propagation-spring-common/src/test/java/com/netcracker/cloud/context/propagation/spring/common/configuration/SpringContextProviderConfigurationUnknownExemptionTest.java b/core-context-propagation/spring-context-aggregator/context-propagation-spring-common/src/test/java/com/netcracker/cloud/context/propagation/spring/common/configuration/SpringContextProviderConfigurationUnknownExemptionTest.java new file mode 100644 index 000000000..1a8a766c1 --- /dev/null +++ b/core-context-propagation/spring-context-aggregator/context-propagation-spring-common/src/test/java/com/netcracker/cloud/context/propagation/spring/common/configuration/SpringContextProviderConfigurationUnknownExemptionTest.java @@ -0,0 +1,34 @@ +package com.netcracker.cloud.context.propagation.spring.common.configuration; + +import com.netcracker.cloud.framework.contexts.allowedheaders.HeaderPropagationConfiguration; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@ExtendWith({HeaderPropagationStateReset.class, SpringExtension.class}) +@ContextConfiguration(classes = SpringContextProviderConfiguration.class) +@TestPropertySource(properties = { + "headers.allowed=custom-header", + "context.propagation.allow-blocked-headers=Custom-Header, X-Some-Other-Header" +}) +@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS) +class SpringContextProviderConfigurationUnknownExemptionTest { + + @Test + void shouldLeaveInternalBlocklistIntactWhenExemptionsDontMatch() { + assertEquals("Custom-Header, X-Some-Other-Header", + System.getProperty("context.propagation.allow-blocked-headers")); + + HeaderPropagationConfiguration.resetCache(); + assertTrue(HeaderPropagationConfiguration.isBlacklisted("X-Channel-Request-Id"), + "Internal blocklist must remain intact when no exemption matches it"); + assertEquals(HeaderPropagationConfiguration.INTERNAL_BLOCKED_HEADERS, + HeaderPropagationConfiguration.blockedHeaders()); + } +} diff --git a/core-context-propagation/spring-context-aggregator/context-propagation-spring-common/src/test/java/com/netcracker/cloud/context/propagation/spring/common/configuration/SpringContextProviderConfigurationWithValueTest.java b/core-context-propagation/spring-context-aggregator/context-propagation-spring-common/src/test/java/com/netcracker/cloud/context/propagation/spring/common/configuration/SpringContextProviderConfigurationWithValueTest.java index 88e48709d..b496265a1 100644 --- a/core-context-propagation/spring-context-aggregator/context-propagation-spring-common/src/test/java/com/netcracker/cloud/context/propagation/spring/common/configuration/SpringContextProviderConfigurationWithValueTest.java +++ b/core-context-propagation/spring-context-aggregator/context-propagation-spring-common/src/test/java/com/netcracker/cloud/context/propagation/spring/common/configuration/SpringContextProviderConfigurationWithValueTest.java @@ -12,30 +12,23 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; -/** - * Spring integration scenario: {@code headers.blocked} is set to a concrete blockable header. - * Verifies that the listed header is blocked and the default's - * {@code X-Channel-Request-Id} entry no longer applies. - */ @ExtendWith({HeaderPropagationStateReset.class, SpringExtension.class}) @ContextConfiguration(classes = SpringContextProviderConfiguration.class) @TestPropertySource(properties = { "headers.allowed=custom-header", - "headers.blocked=Custom-Header" + "context.propagation.allow-blocked-headers=X-Channel-Request-Id" }) @DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS) class SpringContextProviderConfigurationWithValueTest { @Test - void shouldSetSystemPropertyAndBlockConfiguredHeader() { - assertEquals("Custom-Header", System.getProperty("headers.blocked")); + void shouldExemptListedHeaderFromInternalBlocklist() { + assertEquals("X-Channel-Request-Id", System.getProperty("context.propagation.allow-blocked-headers")); HeaderPropagationConfiguration.resetCache(); - assertTrue(HeaderPropagationConfiguration.isBlacklisted("Custom-Header"), - "configured header must be blocked"); - assertTrue(HeaderPropagationConfiguration.isBlacklisted("custom-header"), - "blocking must be case-insensitive"); assertFalse(HeaderPropagationConfiguration.isBlacklisted("X-Channel-Request-Id"), - "explicit configuration overrides the default blocked list"); + "Exempted header must not be blocked"); + assertTrue(HeaderPropagationConfiguration.blockedHeaders().isEmpty(), + "The only entry of the internal blocklist (X-Channel-Request-Id) must be removed"); } } diff --git a/core-context-propagation/spring-context-aggregator/context-propagation-spring-kafka/src/test/java/com/netcracker/cloud/context/propagation/spring/kafka/ContextPropagationTest.java b/core-context-propagation/spring-context-aggregator/context-propagation-spring-kafka/src/test/java/com/netcracker/cloud/context/propagation/spring/kafka/ContextPropagationTest.java index afe9ff138..62a134218 100644 --- a/core-context-propagation/spring-context-aggregator/context-propagation-spring-kafka/src/test/java/com/netcracker/cloud/context/propagation/spring/kafka/ContextPropagationTest.java +++ b/core-context-propagation/spring-context-aggregator/context-propagation-spring-kafka/src/test/java/com/netcracker/cloud/context/propagation/spring/kafka/ContextPropagationTest.java @@ -61,7 +61,7 @@ public class ContextPropagationTest { @BeforeAll static void setup() { System.setProperty("headers.allowed", CUSTOM_HEADER.toLowerCase()); - System.clearProperty("headers.blocked"); + System.clearProperty("context.propagation.allow-blocked-headers"); HeaderPropagationConfiguration.resetCache(); } @@ -73,7 +73,7 @@ void beforeEach() { @AfterEach void afterEach() { - System.clearProperty("headers.blocked"); + System.clearProperty("context.propagation.allow-blocked-headers"); HeaderPropagationConfiguration.resetCache(); } @@ -100,8 +100,10 @@ void testContextPropagationBlocksXChannelRequestIdByDefault() throws Exception { @Test @Timeout(30) - public void testContextPropagationAllowsXChannelRequestIdWhenHeadersBlockedEmpty() throws Exception { - System.setProperty("headers.blocked", ""); + public void testContextPropagationAllowsXChannelRequestIdWhenExempted() throws Exception { + System.setProperty("context.propagation.allow-blocked-headers", X_CHANNEL_REQUEST_ID_NAME); + HeaderPropagationConfiguration.resetCache(); + ChannelRequestIdContext.set(X_CHANNEL_REQUEST_ID_VALUE); AcceptLanguageContext.set(TEST_LANG); AllowedHeadersContext.set(Map.of(CUSTOM_HEADER, CUSTOM_HEADER_VALUE)); diff --git a/core-context-propagation/spring-context-aggregator/context-propagation-spring-rabbit/src/test/java/com/netcracker/cloud/context/propagation/spring/rabbit/PropagationTest.java b/core-context-propagation/spring-context-aggregator/context-propagation-spring-rabbit/src/test/java/com/netcracker/cloud/context/propagation/spring/rabbit/PropagationTest.java index bc539c6e5..581b06fd8 100644 --- a/core-context-propagation/spring-context-aggregator/context-propagation-spring-rabbit/src/test/java/com/netcracker/cloud/context/propagation/spring/rabbit/PropagationTest.java +++ b/core-context-propagation/spring-context-aggregator/context-propagation-spring-rabbit/src/test/java/com/netcracker/cloud/context/propagation/spring/rabbit/PropagationTest.java @@ -87,7 +87,7 @@ public static void setup() throws Exception { channel.queueBind("orders", "orders", "invoice"); } System.setProperty("headers.allowed", CUSTOM_HEADER.toLowerCase()); - System.clearProperty("headers.blocked"); + System.clearProperty("context.propagation.allow-blocked-headers"); } @AfterAll @@ -103,7 +103,7 @@ void beforeEach() { @AfterEach void afterEach() { - System.clearProperty("headers.blocked"); + System.clearProperty("context.propagation.allow-blocked-headers"); HeaderPropagationConfiguration.resetCache(); } @@ -125,8 +125,11 @@ public void testXChannelRequestIdBlockedByDefault() throws InterruptedException @Test @Timeout(value = 20, unit = TimeUnit.SECONDS) - public void testXChannelRequestIdAllowedWhenHeadersBlockedEmpty() throws InterruptedException { - System.setProperty("headers.blocked", ""); + public void testXChannelRequestIdAllowedWhenExempted() throws InterruptedException { + // Exempt X-Channel-Request-Id from the internal blocklist — it must propagate. + System.setProperty("context.propagation.allow-blocked-headers", X_CHANNEL_REQUEST_ID_NAME); + HeaderPropagationConfiguration.resetCache(); + AcceptLanguageContext.set("ZULU"); AllowedHeadersContext.set(Map.of(CUSTOM_HEADER, CUSTOM_HEADER_VALUE)); ChannelRequestIdContext.set(X_CHANNEL_REQUEST_ID_VALUE); @@ -142,12 +145,17 @@ public void testXChannelRequestIdAllowedWhenHeadersBlockedEmpty() throws Interru @Test @Timeout(value = 20, unit = TimeUnit.SECONDS) - public void testCustomHeaderBlockedWhenConfiguredByProperty() throws InterruptedException { - System.setProperty("headers.blocked", ANOTHER_HEADER); + public void testUnknownExemptionDoesNotAffectInternalBlocklist() throws InterruptedException { + // Listing a header that is NOT in the internal blocklist has no effect — the + // internal blocklist (containing X-Channel-Request-Id) still applies. + System.setProperty("context.propagation.allow-blocked-headers", ANOTHER_HEADER); + HeaderPropagationConfiguration.resetCache(); + AcceptLanguageContext.set("ZULU"); AllowedHeadersContext.set(Map.of( CUSTOM_HEADER, CUSTOM_HEADER_VALUE, ANOTHER_HEADER, ANOTHER_HEADER_VALUE)); + ChannelRequestIdContext.set(X_CHANNEL_REQUEST_ID_VALUE); template.convertAndSend("orders", "invoice", "rye wheat"); ContextManager.clearAll(); @@ -155,8 +163,10 @@ public void testCustomHeaderBlockedWhenConfiguredByProperty() throws Interrupted fail("Message listener failed or message doesn't even arrived in 10 seconds"); } - assertNull(getHeaderIgnoreCase(receivedHeaders.get(), ANOTHER_HEADER)); + assertNull(getHeaderIgnoreCase(receivedHeaders.get(), X_CHANNEL_REQUEST_ID_NAME), + "Internal blocklist must remain intact when no exemption matches it"); assertEquals(CUSTOM_HEADER_VALUE, getHeaderIgnoreCase(receivedHeaders.get(), CUSTOM_HEADER)); + assertEquals(ANOTHER_HEADER_VALUE, getHeaderIgnoreCase(receivedHeaders.get(), ANOTHER_HEADER)); } diff --git a/maas-client/kafka-context-propagation/src/test/java/com/netcracker/cloud/maas/client/context/kafka/KafkaContextPropagationTest.java b/maas-client/kafka-context-propagation/src/test/java/com/netcracker/cloud/maas/client/context/kafka/KafkaContextPropagationTest.java index addb62f3b..a5ee8744c 100644 --- a/maas-client/kafka-context-propagation/src/test/java/com/netcracker/cloud/maas/client/context/kafka/KafkaContextPropagationTest.java +++ b/maas-client/kafka-context-propagation/src/test/java/com/netcracker/cloud/maas/client/context/kafka/KafkaContextPropagationTest.java @@ -103,8 +103,8 @@ void testDumpDoesNotContainXChannelRequestIdByDefault() { } @Test - void testDumpContainsXChannelRequestIdWhenNotBlocked() { - System.setProperty("headers.blocked", ""); + void testDumpContainsXChannelRequestIdWhenExempted() { + System.setProperty("context.propagation.allow-blocked-headers", X_CHANNEL_REQUEST_ID); HeaderPropagationConfiguration.resetCache(); ContextManager.reinitialize(); @@ -117,7 +117,7 @@ void testDumpContainsXChannelRequestIdWhenNotBlocked() { assertEquals("ch-456", dumped.get(X_CHANNEL_REQUEST_ID)); } finally { - System.clearProperty("headers.blocked"); + System.clearProperty("context.propagation.allow-blocked-headers"); HeaderPropagationConfiguration.resetCache(); ContextManager.reinitialize(); }