chore(gateway, ping-service, security): streamline configurations, remove redundancies, and improve resilience

- Removed `MdcCorrelationFilter` and simplified correlation ID management using Micrometer Tracing.
- Updated `SecurityConfig` in `gateway` with enhanced role-based access and standardized JWT validation.
- Added new `@Profile` annotations in `ping-service` to exclude certain components during testing.
- Refactored and removed legacy `application-keycloak.yaml` and consolidated settings into the primary `application.yaml`.
- Adjusted Gradle scripts to clean up dependency declarations and improve modularity.
- Simplified CORS and Gateway route configurations for better maintainability.
This commit is contained in:
2026-01-16 21:31:56 +01:00
parent 05962487e7
commit 18f7794a90
19 changed files with 282 additions and 375 deletions
@@ -3,6 +3,7 @@ package at.mocode.ping.application
import at.mocode.ping.domain.Ping
import at.mocode.ping.domain.PingRepository
import org.slf4j.LoggerFactory
import org.springframework.context.annotation.Profile
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import kotlin.uuid.ExperimentalUuidApi
@@ -14,6 +15,7 @@ import kotlin.uuid.Uuid
* Hier darf Spring (@Service, @Transactional) verwendet werden, da es "Application Logic" ist.
*/
@Service
@Profile("!test") // Nicht im Test-Profil laden, damit wir Mocks nutzen können
@OptIn(ExperimentalUuidApi::class)
class PingService(
private val repository: PingRepository
@@ -14,6 +14,10 @@ class PingJpaEntity(
val message: String,
val createdAt: Instant
) {
// Default constructor for JPA
protected constructor() : this(UUID.randomUUID(), "", Instant.now())
// The default constructor for JPA
// Protected is fine for Hibernate, but the Kotlin compiler might complain about visibility.
// We can make it private or internal if needed, but protected is standard.
// To suppress the warning "effectively private", we can just leave it as is or make it public/internal.
// Let's try making it internal to satisfy Kotlin while keeping it hidden from public API.
internal constructor() : this(UUID.randomUUID(), "", Instant.now())
}
@@ -2,6 +2,7 @@ package at.mocode.ping.infrastructure.persistence
import at.mocode.ping.domain.Ping
import at.mocode.ping.domain.PingRepository
import org.springframework.context.annotation.Profile
import org.springframework.stereotype.Repository
import kotlin.uuid.ExperimentalUuidApi
import kotlin.uuid.Uuid
@@ -10,6 +11,7 @@ import kotlin.uuid.toKotlinUuid
@OptIn(ExperimentalUuidApi::class)
@Repository
@Profile("!test") // Nicht im Test-Profil laden, damit wir Mocks nutzen können
class PingRepositoryAdapter(
private val jpaRepository: SpringDataPingRepository
) : PingRepository {
@@ -83,6 +83,7 @@ class PingController(
)
// Fallback
@Suppress("unused", "UNUSED_PARAMETER")
fun fallbackPing(simulate: Boolean, ex: Exception): EnhancedPingResponse {
logger.warn("Circuit breaker fallback triggered: {}", ex.message)
return EnhancedPingResponse(
@@ -2,16 +2,22 @@ package at.mocode.ping.service
import at.mocode.ping.application.PingUseCase
import at.mocode.ping.domain.Ping
import at.mocode.ping.infrastructure.persistence.PingRepositoryAdapter
import at.mocode.ping.infrastructure.web.PingController
import at.mocode.ping.test.TestPingServiceApplication
import io.mockk.every
import io.mockk.mockk
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
import org.springframework.boot.test.context.TestConfiguration
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.context.annotation.Import
import org.springframework.context.annotation.Primary
import org.springframework.test.context.ActiveProfiles
import org.springframework.test.context.ContextConfiguration
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
@@ -22,21 +28,29 @@ import java.time.Instant
*/
@WebMvcTest(
controllers = [PingController::class],
excludeAutoConfiguration = [
org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration::class,
org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration::class
]
properties = ["spring.aop.proxy-target-class=true"]
)
@Import(PingControllerIntegrationTest.TestConfig::class)
@ContextConfiguration(classes = [TestPingServiceApplication::class])
@ActiveProfiles("test")
@Import(PingControllerIntegrationTest.PingControllerIntegrationTestConfig::class)
class PingControllerIntegrationTest {
@Autowired
private lateinit var mockMvc: MockMvc
@TestConfiguration
class TestConfig {
@Bean
@Autowired
@Qualifier("pingUseCaseIntegrationMock")
private lateinit var pingUseCase: PingUseCase
@Configuration
class PingControllerIntegrationTestConfig {
@Bean("pingUseCaseIntegrationMock")
@Primary
fun pingUseCase(): PingUseCase = mockk(relaxed = true)
@Bean
@Primary
fun pingRepositoryAdapter(): PingRepositoryAdapter = mockk(relaxed = true)
}
@Test
@@ -46,8 +60,7 @@ class PingControllerIntegrationTest {
// For endpoints that require the use-case, the relaxed mock is sufficient,
// but we still provide deterministic ping data.
val useCase = TestConfig().pingUseCase()
every { useCase.executePing(any()) } returns Ping(
every { pingUseCase.executePing(any()) } returns Ping(
message = "Simple Ping",
timestamp = Instant.parse("2023-10-01T10:00:00Z")
)
@@ -1,8 +1,10 @@
package at.mocode.ping.service
import at.mocode.ping.domain.Ping
import at.mocode.ping.infrastructure.web.PingController
import at.mocode.ping.application.PingUseCase
import at.mocode.ping.domain.Ping
import at.mocode.ping.infrastructure.persistence.PingRepositoryAdapter
import at.mocode.ping.infrastructure.web.PingController
import at.mocode.ping.test.TestPingServiceApplication
import com.fasterxml.jackson.databind.ObjectMapper
import io.mockk.every
import io.mockk.mockk
@@ -10,12 +12,16 @@ import io.mockk.verify
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
import org.springframework.boot.test.context.TestConfiguration
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.context.annotation.Import
import org.springframework.context.annotation.Primary
import org.springframework.test.context.ActiveProfiles
import org.springframework.test.context.ContextConfiguration
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.MvcResult
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.asyncDispatch
@@ -30,12 +36,11 @@ import java.time.Instant
*/
@WebMvcTest(
controllers = [PingController::class],
excludeAutoConfiguration = [
org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration::class,
org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration::class
]
properties = ["spring.aop.proxy-target-class=true"]
)
@Import(PingControllerTest.TestConfig::class)
@ContextConfiguration(classes = [TestPingServiceApplication::class])
@ActiveProfiles("test")
@Import(PingControllerTest.PingControllerTestConfig::class)
@AutoConfigureMockMvc
class PingControllerTest {
@@ -43,15 +48,21 @@ class PingControllerTest {
private lateinit var mockMvc: MockMvc
@Autowired
@Qualifier("pingUseCaseMock")
private lateinit var pingUseCase: PingUseCase
@Autowired
private lateinit var objectMapper: ObjectMapper
@TestConfiguration
class TestConfig {
@Bean
@Configuration
class PingControllerTestConfig {
@Bean("pingUseCaseMock")
@Primary
fun pingUseCase(): PingUseCase = mockk(relaxed = true)
@Bean
@Primary
fun pingRepositoryAdapter(): PingRepositoryAdapter = mockk(relaxed = true)
}
@BeforeEach
@@ -77,10 +88,7 @@ class PingControllerTest {
.andExpect(status().isOk)
.andReturn()
// In some environments the JSONPath matcher fails to parse the response body.
// We still validate the serialized output contains the expected fields.
val body = result.response.contentAsString
System.out.println("[DEBUG_LOG] /ping/simple response status=${result.response.status} contentType=${result.response.contentType} body=$body")
val json = objectMapper.readTree(body)
assertThat(json.has("status")).isTrue
assertThat(json["status"].asText()).isEqualTo("pong")
@@ -107,7 +115,6 @@ class PingControllerTest {
.andReturn()
val body = result.response.contentAsString
System.out.println("[DEBUG_LOG] /ping/enhanced response status=${result.response.status} contentType=${result.response.contentType} body=$body")
val json = objectMapper.readTree(body)
assertThat(json.has("status")).isTrue
assertThat(json["status"].asText()).isEqualTo("pong")
@@ -128,7 +135,6 @@ class PingControllerTest {
.andReturn()
val body = result.response.contentAsString
System.out.println("[DEBUG_LOG] /ping/health response status=${result.response.status} contentType=${result.response.contentType} body=$body")
val json = objectMapper.readTree(body)
assertThat(json.has("status")).isTrue
assertThat(json["status"].asText()).isEqualTo("up")
@@ -0,0 +1,26 @@
package at.mocode.ping.test
import at.mocode.ping.infrastructure.web.PingController
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.context.annotation.ComponentScan
import org.springframework.context.annotation.EnableAspectJAutoProxy
import org.springframework.context.annotation.Import
/**
* Eine spezielle Application-Klasse für Tests.
* Sie liegt in einem separaten Package, damit sie nicht automatisch von @WebMvcTest gefunden wird,
* sondern explizit importiert werden muss.
*
* WICHTIG: Wir scannen HIER NICHT das 'at.mocode.ping' Package!
* Das verhindert, dass echte Services und Repositories geladen werden.
* Wir scannen nur die Security-Infrastruktur.
*
* Den Controller importieren wir explizit, damit er verfügbar ist.
*/
@SpringBootApplication
@ComponentScan(
basePackages = ["at.mocode.infrastructure.security"]
)
@Import(PingController::class)
@EnableAspectJAutoProxy(proxyTargetClass = true) // Erzwingt CGLIB Proxies für Controller
class TestPingServiceApplication
@@ -1,6 +1,8 @@
spring:
application:
name: ping-service-test
main:
allow-bean-definition-overriding: true
cloud:
consul:
enabled: false