From 2853add9e809c022471beb4740a49d4215a47d73 Mon Sep 17 00:00:00 2001 From: barissayli Date: Sun, 14 Sep 2025 15:53:17 -0600 Subject: [PATCH] test: add LicenseControllerIT with MockMvc integration - Introduce LicenseControllerIT covering /v1/licenses/access endpoint - Provide happy-path (200) and validation error (400) scenarios - Use TestWebMvcSecurityConfig to disable security in tests - Mock LicenseOrchestrationService and LocalizedMessageResolver --- licensing-service-sdk/pom.xml | 31 +++++- .../api/controller/LicenseControllerIT.java | 94 +++++++++++++++++++ .../testconfig/TestControllerMocksConfig.java | 30 ++++++ .../sdk/testconfig/TestRestClientConfig.java | 16 ++++ .../testconfig/TestWebMvcSecurityConfig.java | 22 +++++ .../src/test/resources/application-test.yml | 65 +++++++++++++ .../testconfig/TestSecurityConfig.java | 15 ++- 7 files changed, 264 insertions(+), 9 deletions(-) create mode 100644 licensing-service-sdk/src/test/java/io/github/bsayli/licensing/sdk/api/controller/LicenseControllerIT.java create mode 100644 licensing-service-sdk/src/test/java/io/github/bsayli/licensing/sdk/testconfig/TestControllerMocksConfig.java create mode 100644 licensing-service-sdk/src/test/java/io/github/bsayli/licensing/sdk/testconfig/TestRestClientConfig.java create mode 100644 licensing-service-sdk/src/test/java/io/github/bsayli/licensing/sdk/testconfig/TestWebMvcSecurityConfig.java create mode 100644 licensing-service-sdk/src/test/resources/application-test.yml diff --git a/licensing-service-sdk/pom.xml b/licensing-service-sdk/pom.xml index 752dd5d..bac77d5 100644 --- a/licensing-service-sdk/pom.xml +++ b/licensing-service-sdk/pom.xml @@ -109,6 +109,20 @@ + + org.apache.maven.plugins + maven-failsafe-plugin + ${maven-failsafe-plugin.version} + + + + integration-test + verify + + + + + org.jacoco jacoco-maven-plugin @@ -120,6 +134,14 @@ prepare-agent + + + prepare-agent-integration + + prepare-agent-integration + + + report verify @@ -127,9 +149,16 @@ report + + + report-integration + verify + + report-integration + + - diff --git a/licensing-service-sdk/src/test/java/io/github/bsayli/licensing/sdk/api/controller/LicenseControllerIT.java b/licensing-service-sdk/src/test/java/io/github/bsayli/licensing/sdk/api/controller/LicenseControllerIT.java new file mode 100644 index 0000000..7563f0d --- /dev/null +++ b/licensing-service-sdk/src/test/java/io/github/bsayli/licensing/sdk/api/controller/LicenseControllerIT.java @@ -0,0 +1,94 @@ +package io.github.bsayli.licensing.sdk.api.controller; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +import com.fasterxml.jackson.databind.ObjectMapper; +import io.github.bsayli.licensing.sdk.api.dto.LicenseAccessRequest; +import io.github.bsayli.licensing.sdk.api.dto.LicenseToken; +import io.github.bsayli.licensing.sdk.api.exception.LicenseControllerAdvice; +import io.github.bsayli.licensing.sdk.common.i18n.LocalizedMessageResolver; +import io.github.bsayli.licensing.sdk.service.LicenseOrchestrationService; +import io.github.bsayli.licensing.sdk.testconfig.TestControllerMocksConfig; +import io.github.bsayli.licensing.sdk.testconfig.TestRestClientConfig; +import io.github.bsayli.licensing.sdk.testconfig.TestWebMvcSecurityConfig; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.FilterType; +import org.springframework.context.annotation.Import; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; + +@WebMvcTest( + controllers = LicenseController.class, + excludeFilters = { + @ComponentScan.Filter( + type = FilterType.ASSIGNABLE_TYPE, + classes = io.github.bsayli.licensing.sdk.config.security.SecurityConfig.class), + @ComponentScan.Filter( + type = FilterType.REGEX, + pattern = "io\\.github\\.bsayli\\.licensing\\.client\\..*") + }) +@Import({ + LicenseControllerAdvice.class, + TestWebMvcSecurityConfig.class, + TestControllerMocksConfig.class, + TestRestClientConfig.class +}) +@Tag("integration") +class LicenseControllerIT { + + @Autowired private MockMvc mvc; + @Autowired private ObjectMapper om; + @Autowired private LicenseOrchestrationService service; + @Autowired private LocalizedMessageResolver messageResolver; + + @Test + @DisplayName("POST /v1/licenses/access -> 200 returns token") + void obtainToken_ok() throws Exception { + var req = + new LicenseAccessRequest( + "L".repeat(100) + "~rnd~" + "A".repeat(64), + "licensing-service~demo~00:11:22:33:44:55", + "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b8a5", + "crm", + "1.5.0"); + + Mockito.when(service.getLicenseToken(req)).thenReturn(new LicenseToken("jwt-token")); + Mockito.when(messageResolver.getMessage("license.validation.success")) + .thenReturn("License is valid"); + + mvc.perform( + post("/v1/licenses/access") + .contentType(MediaType.APPLICATION_JSON) + .content(om.writeValueAsBytes(req))) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.status").value(200)) + .andExpect(jsonPath("$.message").value("License is valid")) + .andExpect(jsonPath("$.data.licenseToken").value("jwt-token")); + } + + @Test + @DisplayName("POST /v1/licenses/access -> 400 validation error") + void obtainToken_validationError() throws Exception { + var bad = new LicenseAccessRequest("x", "short", null, "c", "1"); + + Mockito.when(messageResolver.getMessage("request.validation.failed")) + .thenReturn("Request validation failed"); + + mvc.perform( + post("/v1/licenses/access") + .contentType(MediaType.APPLICATION_JSON) + .content(om.writeValueAsBytes(bad))) + .andExpect(status().isBadRequest()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.message").value("Request validation failed")) + .andExpect(jsonPath("$.errors").isArray()); + } +} diff --git a/licensing-service-sdk/src/test/java/io/github/bsayli/licensing/sdk/testconfig/TestControllerMocksConfig.java b/licensing-service-sdk/src/test/java/io/github/bsayli/licensing/sdk/testconfig/TestControllerMocksConfig.java new file mode 100644 index 0000000..fb3c76b --- /dev/null +++ b/licensing-service-sdk/src/test/java/io/github/bsayli/licensing/sdk/testconfig/TestControllerMocksConfig.java @@ -0,0 +1,30 @@ +package io.github.bsayli.licensing.sdk.testconfig; + +import io.github.bsayli.licensing.sdk.common.i18n.LocalizedMessageResolver; +import io.github.bsayli.licensing.sdk.service.LicenseOrchestrationService; +import org.mockito.Mockito; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Primary; + +@TestConfiguration +public class TestControllerMocksConfig { + + @Bean + @Primary + public LicenseOrchestrationService licenseOrchestrationService() { + return Mockito.mock(LicenseOrchestrationService.class); + } + + @Bean + @Primary + public LocalizedMessageResolver messageResolver() { + var mr = Mockito.mock(LocalizedMessageResolver.class); + Mockito.when(mr.getMessage("license.validation.success")).thenReturn("License is valid"); + Mockito.when(mr.getMessage("request.validation.failed")) + .thenReturn("Request validation failed"); + Mockito.when(mr.getMessage("license.validation.error")) + .thenReturn("An unexpected error occurred"); + return mr; + } +} diff --git a/licensing-service-sdk/src/test/java/io/github/bsayli/licensing/sdk/testconfig/TestRestClientConfig.java b/licensing-service-sdk/src/test/java/io/github/bsayli/licensing/sdk/testconfig/TestRestClientConfig.java new file mode 100644 index 0000000..5fc6f39 --- /dev/null +++ b/licensing-service-sdk/src/test/java/io/github/bsayli/licensing/sdk/testconfig/TestRestClientConfig.java @@ -0,0 +1,16 @@ +package io.github.bsayli.licensing.sdk.testconfig; + +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Primary; +import org.springframework.web.client.RestClient; + +@TestConfiguration +public class TestRestClientConfig { + + @Bean + @Primary + public RestClient.Builder restClientBuilder() { + return RestClient.builder(); + } +} diff --git a/licensing-service-sdk/src/test/java/io/github/bsayli/licensing/sdk/testconfig/TestWebMvcSecurityConfig.java b/licensing-service-sdk/src/test/java/io/github/bsayli/licensing/sdk/testconfig/TestWebMvcSecurityConfig.java new file mode 100644 index 0000000..d494a45 --- /dev/null +++ b/licensing-service-sdk/src/test/java/io/github/bsayli/licensing/sdk/testconfig/TestWebMvcSecurityConfig.java @@ -0,0 +1,22 @@ +package io.github.bsayli.licensing.sdk.testconfig; + +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; +import org.springframework.security.web.SecurityFilterChain; + +@TestConfiguration +public class TestWebMvcSecurityConfig { + + @Bean + @Order(Ordered.HIGHEST_PRECEDENCE) + SecurityFilterChain testSecurity(HttpSecurity http) throws Exception { + return http.securityMatcher("/v1/**") + .csrf(AbstractHttpConfigurer::disable) + .authorizeHttpRequests(reg -> reg.anyRequest().permitAll()) + .build(); + } +} diff --git a/licensing-service-sdk/src/test/resources/application-test.yml b/licensing-service-sdk/src/test/resources/application-test.yml new file mode 100644 index 0000000..8bd1a75 --- /dev/null +++ b/licensing-service-sdk/src/test/resources/application-test.yml @@ -0,0 +1,65 @@ +spring: + config: + activate: + on-profile: test + + main: + allow-bean-definition-overriding: true + autoconfigure: + exclude: + - org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration + - org.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfiguration + + messages: + basename: messages + encoding: UTF-8 + fallback-to-system-locale: false + +server: + port: 0 + servlet: + context-path: /licensing-service-sdk + error: + include-message: always + include-binding-errors: always + include-stacktrace: never + include-exception: false + +logging: + level: + root: INFO + org.springframework: WARN + org.hibernate: WARN + org.springframework.security: OFF + io.github.bsayli.licensing: DEBUG + +app: + openapi: + base-url: "http://localhost:${server.port}${server.servlet.context-path:}" + +caching: + spring: + licenseTokenTTL: 30m + +licensing: + sdk: + api: + basic: + username: "testSdkUser" + password: "testSdkPass" + realm: "LicensingServiceSDK-Test" + +licensing-service-api: + base-url: "http://localhost:8081/licensing-service" + basic: + username: "licensingUser" + password: "licensingPass" + connect-timeout-seconds: 5 + connection-request-timeout-seconds: 5 + read-timeout-seconds: 8 + max-connections-total: 16 + max-connections-per-route: 8 + +signature: + private: + key: "MC4CAQAwBQYDK2VwBCIEIG265cW0Yrh583IXT/Hmfd2A1hFnHXViCZHpJu83OBFj" \ No newline at end of file diff --git a/licensing-service/src/test/java/io/github/bsayli/licensing/testconfig/TestSecurityConfig.java b/licensing-service/src/test/java/io/github/bsayli/licensing/testconfig/TestSecurityConfig.java index 6fa1c0a..8ff770f 100644 --- a/licensing-service/src/test/java/io/github/bsayli/licensing/testconfig/TestSecurityConfig.java +++ b/licensing-service/src/test/java/io/github/bsayli/licensing/testconfig/TestSecurityConfig.java @@ -13,11 +13,10 @@ @Profile("test") public class TestSecurityConfig { - @Bean - SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { - return http - .csrf(AbstractHttpConfigurer::disable) - .authorizeHttpRequests(reg -> reg.anyRequest().permitAll()) - .build(); - } -} \ No newline at end of file + @Bean + SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + return http.csrf(AbstractHttpConfigurer::disable) + .authorizeHttpRequests(reg -> reg.anyRequest().permitAll()) + .build(); + } +}