From e5d953dee88729bdbdd3004cc1ba5807be38cde7 Mon Sep 17 00:00:00 2001 From: Marcel Date: Mon, 11 May 2026 13:01:06 +0200 Subject: [PATCH] test(config): rewrite ForwardHeadersConfigurationTest as context-less binder test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Drops @SpringBootTest + PostgresContainerConfig + @MockitoBean S3Client in favour of Spring's Binder API against application.yaml. The new test binds the property into the typed ServerProperties.ForwardHeadersStrategy enum, so typos (`nativ`, `Native`, `framework `) and future enum renames fail the build with BindException — addresses the silent-coercion concern that the YAML-string assertion missed. Verified the test goes red on a typo (BindException: Failed to convert "nativ" → ForwardHeadersStrategy) and green on `native`. Co-Authored-By: Claude Opus 4.7 --- .../ForwardHeadersConfigurationTest.java | 63 +++++++++++-------- 1 file changed, 37 insertions(+), 26 deletions(-) diff --git a/backend/src/test/java/org/raddatz/familienarchiv/config/ForwardHeadersConfigurationTest.java b/backend/src/test/java/org/raddatz/familienarchiv/config/ForwardHeadersConfigurationTest.java index b97f5ff0..755dad83 100644 --- a/backend/src/test/java/org/raddatz/familienarchiv/config/ForwardHeadersConfigurationTest.java +++ b/backend/src/test/java/org/raddatz/familienarchiv/config/ForwardHeadersConfigurationTest.java @@ -1,37 +1,48 @@ package org.raddatz.familienarchiv.config; import org.junit.jupiter.api.Test; -import org.raddatz.familienarchiv.PostgresContainerConfig; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.context.annotation.Import; -import org.springframework.test.context.ActiveProfiles; -import org.springframework.test.context.bean.override.mockito.MockitoBean; -import software.amazon.awssdk.services.s3.S3Client; +import org.springframework.beans.factory.config.YamlPropertiesFactoryBean; +import org.springframework.boot.web.server.autoconfigure.ServerProperties.ForwardHeadersStrategy; +import org.springframework.boot.context.properties.bind.Binder; +import org.springframework.boot.context.properties.source.ConfigurationPropertySources; +import org.springframework.core.env.PropertiesPropertySource; +import org.springframework.core.io.ClassPathResource; + +import java.util.Properties; import static org.assertj.core.api.Assertions.assertThat; -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE) -@ActiveProfiles("test") -@Import(PostgresContainerConfig.class) +/** + * Binds {@code server.forward-headers-strategy} from {@code application.yaml} into + * Spring Boot's typed {@link ForwardHeadersStrategy} enum. The binder rejects any + * value that is not a valid enum constant ({@code BindException}), so a typo + * ({@code "nativ"}, {@code "Native"}, {@code "framework "}) or a future Spring + * rename of the property fails the test, not silently degrades to {@code NONE}. + * + *

No Spring context, no embedded server, no Testcontainers — this is the + * cheapest test that pins the contract "Caddy's X-Forwarded-Proto is trusted". + */ class ForwardHeadersConfigurationTest { - @MockitoBean - S3Client s3Client; - - @Autowired - @Value("${server.forward-headers-strategy:}") - String forwardHeadersStrategy; - @Test - void forward_headers_strategy_is_native_for_reverse_proxy_deployment() { - // Caddy terminates TLS and forwards X-Forwarded-Proto: https. - // Spring must trust those headers so that AppUser-facing redirect URLs, - // Spring Session cookies (Secure flag), and HttpServletRequest.getScheme() - // reflect the original client-facing scheme rather than the internal http hop. - assertThat(forwardHeadersStrategy) - .as("server.forward-headers-strategy must be 'native' so Jetty honours X-Forwarded-Proto behind Caddy") - .isEqualTo("native"); + void forward_headers_strategy_binds_to_NATIVE() { + YamlPropertiesFactoryBean yaml = new YamlPropertiesFactoryBean(); + yaml.setResources(new ClassPathResource("application.yaml")); + Properties props = yaml.getObject(); + assertThat(props).as("application.yaml must be on the classpath").isNotNull(); + + Binder binder = new Binder(ConfigurationPropertySources.from( + new PropertiesPropertySource("application", props))); + + ForwardHeadersStrategy strategy = binder + .bind("server.forward-headers-strategy", ForwardHeadersStrategy.class) + .orElseThrow(() -> new AssertionError( + "server.forward-headers-strategy is missing from application.yaml")); + + assertThat(strategy) + .as("Spring must trust X-Forwarded-Proto from Caddy so that " + + "request.getScheme(), redirect URLs, and the Spring Session " + + "'Secure' cookie reflect the original https client request.") + .isEqualTo(ForwardHeadersStrategy.NATIVE); } }