chore(tests, dependencies, build): optimize test assertions, fix mocking issues, and update dependencies

- Simplified test assertions by removing redundant type casting in `ResultTest`.
- Improved Redis mock behavior and verification leniency in `RedisDistributedCacheTest`.
- Updated dependency versions in `libs.versions.toml` (e.g., downgraded `ktor` and adjusted `composeMultiplatform`).
- Refined Gradle build scripts for consistent compiler args and testing configurations (e.g., disabled browser tests in frontend).
- Addressed mocking issues in `RedisDistributedCacheTest` to prevent `NoSuchMethodError`.
- Fixed package imports due to framework updates in Spring Boot and Micrometer-related components.
This commit is contained in:
2026-01-08 01:29:54 +01:00
parent ec616a7956
commit 82934804f3
10 changed files with 176 additions and 157 deletions
@@ -2,7 +2,7 @@ package at.mocode.infrastructure.gateway.error
import com.fasterxml.jackson.databind.ObjectMapper
import org.slf4j.LoggerFactory
import org.springframework.boot.webflux.error.ErrorWebExceptionHandler
import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler
import org.springframework.http.HttpStatus
import org.springframework.http.MediaType
import org.springframework.stereotype.Component
@@ -1,7 +1,7 @@
package at.mocode.infrastructure.gateway.health
import org.springframework.boot.health.contributor.Health
import org.springframework.boot.health.contributor.ReactiveHealthIndicator
import org.springframework.boot.actuate.health.Health
import org.springframework.boot.actuate.health.ReactiveHealthIndicator
import org.springframework.cloud.client.ServiceInstance
import org.springframework.cloud.client.discovery.DiscoveryClient
import org.springframework.core.env.Environment
@@ -68,6 +68,7 @@ class GatewayHealthIndicator(
val checkMono: Mono<String> = when {
CRITICAL_SERVICES.contains(serviceName) || OPTIONAL_SERVICES.contains(serviceName) ->
checkServiceHealthReactive(serviceName, instances)
else -> Mono.just("SKIPPED")
}
checkMono.map { status -> Triple(serviceName, status, instanceDetails) }
@@ -143,6 +144,7 @@ class GatewayHealthIndicator(
503 -> Mono.just("DOWN")
else -> Mono.just("ERROR")
}
is TimeoutException -> Mono.just("TIMEOUT")
else -> Mono.just("ERROR")
}
@@ -4,7 +4,7 @@ import io.micrometer.core.instrument.Counter
import io.micrometer.core.instrument.MeterRegistry
import io.micrometer.core.instrument.Timer
import io.micrometer.core.instrument.config.MeterFilter
import org.springframework.boot.micrometer.metrics.autoconfigure.MeterRegistryCustomizer
import org.springframework.boot.actuate.autoconfigure.metrics.MeterRegistryCustomizer
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.web.server.ServerWebExchange
@@ -27,88 +27,88 @@ import java.time.Duration
@Configuration
class GatewayMetricsConfig {
companion object {
// Metric Namen als Konstanten für bessere Wartbarkeit
const val GATEWAY_REQUEST_TIMER = "gateway_custom_request_duration"
const val GATEWAY_ERROR_COUNTER = "gateway_errors_total"
const val GATEWAY_REQUESTS_COUNTER = "gateway_requests_total"
const val GATEWAY_CIRCUIT_BREAKER_COUNTER = "gateway_circuit_breaker_events_total"
const val GATEWAY_DOWNSTREAM_HEALTH_GAUGE = "gateway_downstream_health_status"
}
companion object {
// Metric Namen als Konstanten für bessere Wartbarkeit
const val GATEWAY_REQUEST_TIMER = "gateway_custom_request_duration"
const val GATEWAY_ERROR_COUNTER = "gateway_errors_total"
const val GATEWAY_REQUESTS_COUNTER = "gateway_requests_total"
const val GATEWAY_CIRCUIT_BREAKER_COUNTER = "gateway_circuit_breaker_events_total"
const val GATEWAY_DOWNSTREAM_HEALTH_GAUGE = "gateway_downstream_health_status"
}
/**
* Konfiguriert globale Meter-Registry Einstellungen für das Gateway.
*/
@Bean
fun gatewayMeterRegistryCustomizer(): MeterRegistryCustomizer<MeterRegistry> {
return MeterRegistryCustomizer { registry ->
// Gemeinsame Tags für alle Gateway-Metriken
registry.config()
.commonTags("service", "gateway", "component", "infrastructure")
// Filterung von zu detaillierten Metriken
.meterFilter(MeterFilter.deny { id ->
val name = id.name
// Ausschluss von internen Spring/Netty Metriken, die zu viel Rauschen erzeugen
name.startsWith("reactor.netty.connection.provider") ||
name.startsWith("reactor.netty.bytebuf.allocator") ||
name.startsWith("jvm.gc.overhead")
})
// Histogram-Buckets für Request Duration optimieren
.meterFilter(MeterFilter.accept())
}
/**
* Konfiguriert globale Meter-Registry Einstellungen für das Gateway.
*/
@Bean
fun gatewayMeterRegistryCustomizer(): MeterRegistryCustomizer<MeterRegistry> {
return MeterRegistryCustomizer { registry ->
// Gemeinsame Tags für alle Gateway-Metriken
registry.config()
.commonTags("service", "gateway", "component", "infrastructure")
// Filterung von zu detaillierten Metriken
.meterFilter(MeterFilter.deny { id ->
val name = id.name
// Ausschluss von internen Spring/Netty Metriken, die zu viel Rauschen erzeugen
name.startsWith("reactor.netty.connection.provider") ||
name.startsWith("reactor.netty.bytebuf.allocator") ||
name.startsWith("jvm.gc.overhead")
})
// Histogram-Buckets für Request Duration optimieren
.meterFilter(MeterFilter.accept())
}
}
/**
* WebFilter für automatische Request/Response Zeit und Error Rate Tracking.
*
* Dieser Filter misst:
* - Gesamte Request-Verarbeitungszeit
* - Anzahl der Requests nach Status-Code kategorisiert
* - Error-Rate basierend auf HTTP Status Codes
*/
@Bean
fun gatewayMetricsWebFilter(meterRegistry: MeterRegistry): WebFilter {
return GatewayMetricsWebFilter(meterRegistry)
}
/**
* WebFilter für automatische Request/Response Zeit und Error Rate Tracking.
*
* Dieser Filter misst:
* - Gesamte Request-Verarbeitungszeit
* - Anzahl der Requests nach Status-Code kategorisiert
* - Error-Rate basierend auf HTTP Status Codes
*/
@Bean
fun gatewayMetricsWebFilter(meterRegistry: MeterRegistry): WebFilter {
return GatewayMetricsWebFilter(meterRegistry)
}
/**
* Bean für Request Duration Timer - entfernt um Konflikte mit dem WebFilter zu vermeiden.
* Die Request-Zeiten werden automatisch im GatewayMetricsWebFilter erfasst.
*/
// @Bean - entfernt, um Prometheus Meter-Konflikte zu vermeiden,
// fun requestTimer(meterRegistry: MeterRegistry): Timer { ... }
/**
* Bean für Request Duration Timer - entfernt um Konflikte mit dem WebFilter zu vermeiden.
* Die Request-Zeiten werden automatisch im GatewayMetricsWebFilter erfasst.
*/
// @Bean - entfernt, um Prometheus Meter-Konflikte zu vermeiden,
// fun requestTimer(meterRegistry: MeterRegistry): Timer { ... }
/**
* Bean für Error Counter - ermöglicht manuelles Error Tracking.
*/
@Bean
fun errorCounter(meterRegistry: MeterRegistry): Counter {
return Counter.builder(GATEWAY_ERROR_COUNTER)
.description("Gesamtanzahl der Gateway-Fehler")
.register(meterRegistry)
}
/**
* Bean für Error Counter - ermöglicht manuelles Error Tracking.
*/
@Bean
fun errorCounter(meterRegistry: MeterRegistry): Counter {
return Counter.builder(GATEWAY_ERROR_COUNTER)
.description("Gesamtanzahl der Gateway-Fehler")
.register(meterRegistry)
}
/**
* Bean für Request Counter - ermöglicht Request-Volumen Tracking.
* Hinweis: Dieser Counter wird nur als Fallback registriert.
* Die tatsächlichen Requests werden mit dynamischen Tags im WebFilter erfasst.
*/
@Bean
fun requestCounter(meterRegistry: MeterRegistry): Counter {
return Counter.builder("${GATEWAY_REQUESTS_COUNTER}_fallback")
.description("Gateway-Requests Fallback Counter")
.register(meterRegistry)
}
/**
* Bean für Request Counter - ermöglicht Request-Volumen Tracking.
* Hinweis: Dieser Counter wird nur als Fallback registriert.
* Die tatsächlichen Requests werden mit dynamischen Tags im WebFilter erfasst.
*/
@Bean
fun requestCounter(meterRegistry: MeterRegistry): Counter {
return Counter.builder("${GATEWAY_REQUESTS_COUNTER}_fallback")
.description("Gateway-Requests Fallback Counter")
.register(meterRegistry)
}
/**
* Bean für Circuit Breaker Events Counter.
*/
@Bean
fun circuitBreakerCounter(meterRegistry: MeterRegistry): Counter {
return Counter.builder(GATEWAY_CIRCUIT_BREAKER_COUNTER)
.description("Circuit Breaker Events im Gateway")
.register(meterRegistry)
}
/**
* Bean für Circuit Breaker Events Counter.
*/
@Bean
fun circuitBreakerCounter(meterRegistry: MeterRegistry): Counter {
return Counter.builder(GATEWAY_CIRCUIT_BREAKER_COUNTER)
.description("Circuit Breaker Events im Gateway")
.register(meterRegistry)
}
}
/**
@@ -116,69 +116,69 @@ class GatewayMetricsConfig {
*/
class GatewayMetricsWebFilter(private val meterRegistry: MeterRegistry) : WebFilter {
override fun filter(exchange: ServerWebExchange, chain: WebFilterChain): Mono<Void> {
val startTime = System.nanoTime()
val request = exchange.request
val path = request.path.value()
val method = request.method.toString()
override fun filter(exchange: ServerWebExchange, chain: WebFilterChain): Mono<Void> {
val startTime = System.nanoTime()
val request = exchange.request
val path = request.path.value()
val method = request.method.toString()
// Request Counter incrementer
Counter.builder(GatewayMetricsConfig.GATEWAY_REQUESTS_COUNTER)
// Request Counter incrementer
Counter.builder(GatewayMetricsConfig.GATEWAY_REQUESTS_COUNTER)
.tag("method", method)
.tag("path", normalizePath(path))
.description("Gateway-Requests gesamt")
.register(meterRegistry)
.increment()
return chain.filter(exchange)
.doFinally { _ ->
val duration = Duration.ofNanos(System.nanoTime() - startTime)
val response = exchange.response
val statusCode = response.statusCode?.value()?.toString() ?: "unknown"
val statusSeries = when {
statusCode.startsWith("2") -> "2xx"
statusCode.startsWith("3") -> "3xx"
statusCode.startsWith("4") -> "4xx"
statusCode.startsWith("5") -> "5xx"
else -> "unknown"
}
// Request Duration Timer
Timer.builder(GatewayMetricsConfig.GATEWAY_REQUEST_TIMER)
.tag("method", method)
.tag("path", normalizePath(path))
.tag("status", statusCode)
.tag("status_series", statusSeries)
.description("Gateway Request-Verarbeitungszeit")
.register(meterRegistry)
.record(duration)
// Error Counter für 4xx und 5xx Responses
if (statusCode.startsWith("4") || statusCode.startsWith("5")) {
Counter.builder(GatewayMetricsConfig.GATEWAY_ERROR_COUNTER)
.tag("method", method)
.tag("path", normalizePath(path))
.description("Gateway-Requests gesamt")
.tag("status", statusCode)
.tag("status_series", statusSeries)
.tag("error_type", if (statusCode.startsWith("4")) "client_error" else "server_error")
.description("Gateway-Fehleranzahl")
.register(meterRegistry)
.increment()
}
}
}
return chain.filter(exchange)
.doFinally { _ ->
val duration = Duration.ofNanos(System.nanoTime() - startTime)
val response = exchange.response
val statusCode = response.statusCode?.value()?.toString() ?: "unknown"
val statusSeries = when {
statusCode.startsWith("2") -> "2xx"
statusCode.startsWith("3") -> "3xx"
statusCode.startsWith("4") -> "4xx"
statusCode.startsWith("5") -> "5xx"
else -> "unknown"
}
// Request Duration Timer
Timer.builder(GatewayMetricsConfig.GATEWAY_REQUEST_TIMER)
.tag("method", method)
.tag("path", normalizePath(path))
.tag("status", statusCode)
.tag("status_series", statusSeries)
.description("Gateway Request-Verarbeitungszeit")
.register(meterRegistry)
.record(duration)
// Error Counter für 4xx und 5xx Responses
if (statusCode.startsWith("4") || statusCode.startsWith("5")) {
Counter.builder(GatewayMetricsConfig.GATEWAY_ERROR_COUNTER)
.tag("method", method)
.tag("path", normalizePath(path))
.tag("status", statusCode)
.tag("status_series", statusSeries)
.tag("error_type", if (statusCode.startsWith("4")) "client_error" else "server_error")
.description("Gateway-Fehleranzahl")
.register(meterRegistry)
.increment()
}
}
}
/**
* Normalisiert Pfade für Metriken, um Kardinalität-Explosion zu vermeiden.
* Beispiel: /api/horses/123 → /api/horses/{id}
*/
private fun normalizePath(path: String): String {
return path
// UUID pattern ersetzen
.replace(Regex("/[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}"), "/{uuid}")
// Numerische IDs ersetzen
.replace(Regex("/\\d+"), "/{id}")
// Sehr lange Pfade kürzen
.let { if (it.length > 100) "${it.substring(0, 97)}..." else it }
}
/**
* Normalisiert Pfade für Metriken, um Kardinalität-Explosion zu vermeiden.
* Beispiel: /api/horses/123 → /api/horses/{id}
*/
private fun normalizePath(path: String): String {
return path
// UUID pattern ersetzen
.replace(Regex("/[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}"), "/{uuid}")
// Numerische IDs ersetzen
.replace(Regex("/\\d+"), "/{id}")
// Sehr lange Pfade kürzen
.let { if (it.length > 100) "${it.substring(0, 97)}..." else it }
}
}