package com.talentcard.server.config; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.ResponseEntity; import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; import org.springframework.test.context.web.WebAppConfiguration; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.filter.CorsFilter; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.options; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @SpringJUnitConfig({ApiCorsConfig.class, ApiCorsConfigTests.TestMvcConfig.class}) @WebAppConfiguration @TestPropertySource(properties = "app.cors.allowed-origins=http://localhost:7894,http://127.0.0.1:7893") class ApiCorsConfigTests { @Autowired private WebApplicationContext context; private MockMvc mvc; @BeforeEach void setup() { mvc = MockMvcBuilders.webAppContextSetup(context) .addFilters(context.getBeansOfType(CorsFilter.class).values().toArray(CorsFilter[]::new)) .build(); } @Test void merchantPreflightAllowsLoginAndAuthenticatedHeaders() throws Exception { mvc.perform(options("/api/merchant/auth/login") .header("Origin", "http://localhost:7894") .header("Access-Control-Request-Method", "POST") .header("Access-Control-Request-Headers", "content-type,x-request-id,x-client-platform,x-client-version,authorization,x-merchant-store-id,x-idempotency-key")) .andExpect(status().isOk()) .andExpect(header().string("Access-Control-Allow-Origin", "http://localhost:7894")); } @Test void authenticationErrorsRemainReadableByAllowedFrontend() throws Exception { mvc.perform(post("/api/merchant/auth/login").header("Origin", "http://localhost:7894")) .andExpect(status().isUnauthorized()) .andExpect(header().string("Access-Control-Allow-Origin", "http://localhost:7894")); } @Test void talentFrontendCanPreflightProtectedEndpoint() throws Exception { mvc.perform(options("/api/merchant/auth/login") .header("Origin", "http://127.0.0.1:7893") .header("Access-Control-Request-Method", "POST")) .andExpect(status().isOk()) .andExpect(header().string("Access-Control-Allow-Origin", "http://127.0.0.1:7893")); } @Test void unlistedOriginIsRejected() throws Exception { mvc.perform(post("/api/merchant/auth/login").header("Origin", "https://untrusted.example")) .andExpect(status().isForbidden()) .andExpect(header().doesNotExist("Access-Control-Allow-Origin")); } @Test void nativeClientStillReachesAuthenticationWithoutOrigin() throws Exception { mvc.perform(post("/api/merchant/auth/login")) .andExpect(status().isUnauthorized()); } @Configuration @EnableWebMvc static class TestMvcConfig { @Bean ProtectedEndpoint endpoint() { return new ProtectedEndpoint(); } } @RestController static class ProtectedEndpoint { @PostMapping("/api/merchant/auth/login") ResponseEntity login() { return ResponseEntity.status(401).build(); } } }