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: + *

+ * 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"); + } +}