diff --git a/TODO.md b/TODO.md new file mode 100644 index 00000000..13f2162a --- /dev/null +++ b/TODO.md @@ -0,0 +1,23 @@ +Zusammengefasst ergibt sich daraus folgender, konkreter Fahrplan: + +1. **Schritt 0: Aufräumen (ca. 1-2 Stunden)** + * [ ] Entfernen Sie den auskommentierten Ktor-Code aus der `infrastructure:gateway:build.gradle.kts`. + * [ ] Refaktorieren Sie die Test-Route in `GatewayApplicationTests.kt` auf die Kotlin DSL von Spring Cloud Gateway. + * [ ] **(Optional)** Führen Sie `value class`es für stark typisierte IDs oder Konfigurationsparameter im `core`-Modul ein. + +2. **Schritt 1: Phase 2 - Den "Ping-Service" bauen** + * [ ] Erstellen Sie ein neues Gradle-Modul `:temp:ping-service`. + * [ ] Implementieren Sie eine simple Spring Boot Anwendung darin. + * [ ] Fügen Sie die Abhängigkeiten zu `spring-boot-starter-web`, `spring-cloud-starter-consul-discovery` und Ihrem `platform:platform-dependencies` hinzu. + * [ ] Erstellen Sie einen `RestController` mit einem `GET /ping` Endpunkt, der `mapOf("status" to "pong")` zurückgibt. + * [ ] Konfigurieren Sie die `application.yml` des Services, damit er sich bei Consul registriert und einen eindeutigen Namen (`spring.application.name=ping-service`) hat. + +3. **Schritt 2: Phase 3 - Gateway-Route konfigurieren** + * [ ] Fügen Sie in der `application.yml` Ihres Gateways eine Route hinzu, die Anfragen von `/api/ping` an den `ping-service` weiterleitet (Load Balanced via `lb://ping-service`). + +4. **Schritt 3: Phase 4 - Gesamtsystem testen** + * [ ] Starten Sie Consul, den Gateway und den Ping-Service. + * [ ] Rufen Sie die Gateway-URL (z.B. `http://localhost:8080/api/ping`) auf und verifizieren Sie, dass Sie die `{"status": "pong"}`-Antwort erhalten. + * [ ] Erstellen Sie den minimalen "Ping"-Button in Ihrer Client-Anwendung und testen Sie den gesamten Weg. + +Wenn Sie diesen Plan abarbeiten, haben Sie nicht nur Ihre Architektur validiert, sondern auch einige Stellen modernisiert und aufgeräumt. Sie sind auf einem exzellenten Weg diff --git a/infrastructure/gateway/build.gradle.kts b/infrastructure/gateway/build.gradle.kts index 28caae7f..bafc351a 100644 --- a/infrastructure/gateway/build.gradle.kts +++ b/infrastructure/gateway/build.gradle.kts @@ -1,56 +1,3 @@ -/*plugins { - alias(libs.plugins.kotlin.jvm) - alias(libs.plugins.kotlin.serialization) - alias(libs.plugins.ktor) - alias(libs.plugins.spring.dependencyManagement) - application -} - -application { - mainClass.set("at.mocode.infrastructure.gateway.ApplicationKt") -} - -dependencies { - api(platform(libs.spring.boot.dependencies)) - implementation(projects.platform.platformDependencies) - implementation(projects.core.coreDomain) - implementation(projects.core.coreUtils) - - implementation(projects.infrastructure.auth.authClient) - implementation(projects.infrastructure.monitoring.monitoringClient) - - // --- Ktor Server --- - implementation(libs.ktor.server.core) - implementation(libs.ktor.server.netty) - implementation(libs.ktor.server.contentNegotiation) - implementation(libs.ktor.server.serialization.kotlinx.json) - implementation(libs.ktor.server.cors) - implementation(libs.ktor.server.callLogging) - implementation(libs.ktor.server.defaultHeaders) - implementation(libs.ktor.server.statusPages) - implementation(libs.ktor.server.auth) - implementation(libs.ktor.server.authJwt) - implementation(libs.ktor.server.rateLimit) - implementation(libs.ktor.server.metrics.micrometer) - - // --- OpenAPI & Swagger for Ktor --- - implementation(libs.ktor.server.openapi) - implementation(libs.ktor.server.swagger) - - // --- Ktor Client (damit der Gateway Anfragen an die Backend-Services weiterleiten kann) --- - implementation(libs.ktor.client.core) - implementation(libs.ktor.client.cio) - implementation(libs.ktor.client.contentNegotiation) - implementation(libs.ktor.client.serialization.kotlinx.json) - - // --- Monitoring --- - implementation(libs.micrometer.prometheus) - - // --- Testing --- - testImplementation(projects.platform.platformTesting) - testImplementation(libs.ktor.server.tests) -}*/ - // Dieses Modul ist das API-Gateway und der einzige öffentliche Einstiegspunkt // für alle externen Anfragen an das Meldestelle-System. plugins { @@ -75,6 +22,8 @@ dependencies { // Stellt die Spring Cloud Gateway und Consul Discovery Abhängigkeiten bereit implementation(libs.bundles.spring.cloud.gateway) + // Sichert den reaktiven Webserver (Netty) explizit ab, um Test-/Kontext-Probleme zu vermeiden + implementation("org.springframework.boot:spring-boot-starter-webflux") // Bindet die wiederverwendbare Logik zur JWT-Validierung ein. implementation(projects.infrastructure.auth.authClient) @@ -85,5 +34,7 @@ dependencies { // Stellt alle Test-Abhängigkeiten gebündelt bereit. testImplementation(projects.platform.platformTesting) testImplementation(libs.bundles.testing.jvm) + // Security im Testkontext, um eine permissive Security-Konfiguration bereitstellen zu können + testImplementation(libs.spring.boot.starter.security) } diff --git a/infrastructure/gateway/src/main/kotlin/at/mocode/infrastructure/gateway/GatewayApplication.kt b/infrastructure/gateway/src/main/kotlin/at/mocode/infrastructure/gateway/GatewayApplication.kt index f2aeacb2..466ae5b9 100644 --- a/infrastructure/gateway/src/main/kotlin/at/mocode/infrastructure/gateway/GatewayApplication.kt +++ b/infrastructure/gateway/src/main/kotlin/at/mocode/infrastructure/gateway/GatewayApplication.kt @@ -2,13 +2,10 @@ package at.mocode.infrastructure.gateway import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.boot.runApplication -import org.springframework.cloud.client.discovery.EnableDiscoveryClient @SpringBootApplication -@EnableDiscoveryClient class GatewayApplication fun main(args: Array) { - runApplication(*args) } diff --git a/infrastructure/gateway/src/main/resources/application.yml b/infrastructure/gateway/src/main/resources/application.yml index c74fa9f4..0e3fd8e9 100644 --- a/infrastructure/gateway/src/main/resources/application.yml +++ b/infrastructure/gateway/src/main/resources/application.yml @@ -8,6 +8,20 @@ spring: name: api-gateway cloud: gateway: + # HTTP Client-Timeouts für stabile Upstream-Verbindungen + httpclient: + connect-timeout: 5000 # in Millisekunden + response-timeout: 30s + # Globales CORS-Setup (kann pro Umgebung überschrieben werden) + globalcors: + corsConfigurations: + '[/**]': + allowedOrigins: "*" + allowedMethods: "*" + allowedHeaders: "*" + # Antwort-Header bereinigen (verhindert doppelte CORS-Header) + default-filters: + - DedupeResponseHeader=Access-Control-Allow-Credentials Access-Control-Allow-Origin # Aktiviert die automatische Routen-Erstellung basierend auf Consul discovery: locator: diff --git a/infrastructure/gateway/src/test/kotlin/at/mocode/infrastructure/gateway/GatewayApplicationTests.kt b/infrastructure/gateway/src/test/kotlin/at/mocode/infrastructure/gateway/GatewayApplicationTests.kt new file mode 100644 index 00000000..b9d2b79d --- /dev/null +++ b/infrastructure/gateway/src/test/kotlin/at/mocode/infrastructure/gateway/GatewayApplicationTests.kt @@ -0,0 +1,88 @@ +package at.mocode.infrastructure.gateway + +import org.junit.jupiter.api.Test +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.context.annotation.Import +import org.springframework.test.web.reactive.server.WebTestClient +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.RestController +import org.springframework.cloud.gateway.route.RouteLocator +import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder +import java.time.Duration +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Assertions.assertNotNull +import org.springframework.boot.test.context.TestConfiguration + +@SpringBootTest( + classes = [GatewayApplication::class], + webEnvironment = WebEnvironment.RANDOM_PORT, + properties = [ + // Use a random port and disable discovery/consul for the test + "server.port=0", + "spring.cloud.discovery.enabled=false", + "spring.cloud.consul.enabled=false", + "spring.cloud.consul.config.enabled=false", + "spring.cloud.consul.discovery.register=false", + // Disable security autoconfiguration for tests + "spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration,org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration,org.springframework.boot.actuate.autoconfigure.security.reactive.ReactiveManagementWebSecurityAutoConfiguration", + // Force a reactive web application so that Spring Cloud Gateway auto-config activates + "spring.main.web-application-type=reactive", + // Gateway discovery locator off; we use explicit test routes + "spring.cloud.gateway.discovery.locator.enabled=false" + ] +) +@AutoConfigureWebTestClient +@Import(GatewayApplicationTests.TestRoutes::class, GatewayApplicationTests.InternalHelloController::class, GatewayApplicationTests.TestSecurityConfig::class) +class GatewayApplicationTests { + + @Autowired + lateinit var client: WebTestClient + + @Autowired + lateinit var routeLocator: RouteLocator + + @Test + fun contextLoads() { + // If the application context fails to load, this test will fail. + } + + @Test + fun forwardRouteShouldReturnResponseFromInternalController() { + client.get() + .uri("/hello") + .exchange() + .expectStatus().isOk + .expectBody(String::class.java) + .isEqualTo("OK") + } + + @RestController + class InternalHelloController { + @GetMapping("/internal/hello") + fun hello(): String = "OK" + } + + @Configuration + class TestRoutes { + @Bean + fun routeLocator(builder: RouteLocatorBuilder): RouteLocator = builder.routes() + .route("test-forward") { r -> r.path("/hello").uri("forward:/internal/hello") } + .build() + } + + @TestConfiguration + class TestSecurityConfig { + @Bean + fun springSecurityFilterChain(): org.springframework.security.web.server.SecurityWebFilterChain = + org.springframework.security.config.web.server.ServerHttpSecurity + .http() + .csrf { it.disable() } + .authorizeExchange { exchanges -> exchanges.anyExchange().permitAll() } + .build() + } +} diff --git a/infrastructure/messaging/messaging-client/src/main/kotlin/at/mocode/infrastructure/messaging/client/ReactiveKafkaConfig.kt b/infrastructure/messaging/messaging-client/src/main/kotlin/at/mocode/infrastructure/messaging/client/ReactiveKafkaConfig.kt index e8cb8b1c..d0612c14 100644 --- a/infrastructure/messaging/messaging-client/src/main/kotlin/at/mocode/infrastructure/messaging/client/ReactiveKafkaConfig.kt +++ b/infrastructure/messaging/messaging-client/src/main/kotlin/at/mocode/infrastructure/messaging/client/ReactiveKafkaConfig.kt @@ -1,18 +1,22 @@ package at.mocode.infrastructure.messaging.client -import org.springframework.context.annotation.Bean -import org.springframework.context.annotation.Configuration -import org.springframework.kafka.core.ProducerFactory +import org.springframework.kafka.core.DefaultKafkaProducerFactory import org.springframework.kafka.core.reactive.ReactiveKafkaProducerTemplate import reactor.kafka.sender.SenderOptions -@Configuration +/** + * Reactive Kafka configuration utilities for creating a ReactiveKafkaProducerTemplate. + */ class ReactiveKafkaConfig { - @Bean - fun reactiveKafkaProducerTemplate(producerFactory: ProducerFactory): ReactiveKafkaProducerTemplate { - // Nutzt die ProducerFactory aus dem messaging-config-Modul - val senderOptions = SenderOptions.create(producerFactory.configurationProperties) + /** + * Create a ReactiveKafkaProducerTemplate using the configuration from the given ProducerFactory. + */ + fun reactiveKafkaProducerTemplate( + producerFactory: DefaultKafkaProducerFactory + ): ReactiveKafkaProducerTemplate { + val props: Map = producerFactory.configurationProperties + val senderOptions: SenderOptions = SenderOptions.create(props) return ReactiveKafkaProducerTemplate(senderOptions) } } diff --git a/infrastructure/messaging/messaging-client/src/test/kotlin/at/mocode/infrastructure/messaging/client/KafkaIntegrationTest.kt b/infrastructure/messaging/messaging-client/src/test/kotlin/at/mocode/infrastructure/messaging/client/KafkaIntegrationTest.kt index 285f175e..eeb2d68c 100644 --- a/infrastructure/messaging/messaging-client/src/test/kotlin/at/mocode/infrastructure/messaging/client/KafkaIntegrationTest.kt +++ b/infrastructure/messaging/messaging-client/src/test/kotlin/at/mocode/infrastructure/messaging/client/KafkaIntegrationTest.kt @@ -36,7 +36,7 @@ class KafkaIntegrationTest { val kafkaConfig = KafkaConfig().apply { bootstrapServers = kafkaContainer.bootstrapServers } - producerFactory = kafkaConfig.producerFactory() as DefaultKafkaProducerFactory + producerFactory = kafkaConfig.producerFactory() val reactiveKafkaConfig = ReactiveKafkaConfig() val reactiveTemplate = reactiveKafkaConfig.reactiveKafkaProducerTemplate(producerFactory) @@ -60,9 +60,18 @@ class KafkaIntegrationTest { ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG to StringDeserializer::class.java, ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG to JsonDeserializer::class.java, ConsumerConfig.AUTO_OFFSET_RESET_CONFIG to "earliest", - JsonDeserializer.TRUSTED_PACKAGES to "*" + JsonDeserializer.TRUSTED_PACKAGES to "*", + JsonDeserializer.USE_TYPE_INFO_HEADERS to false, + JsonDeserializer.VALUE_DEFAULT_TYPE to TestEvent::class.java.name ) - val receiverOptions = ReceiverOptions.create(consumerProps).subscription(listOf(testTopic)) + + val jsonValueDeserializer = JsonDeserializer(TestEvent::class.java).apply { + addTrustedPackages("*") + } + val receiverOptions = ReceiverOptions.create(consumerProps) + .withKeyDeserializer(StringDeserializer()) + .withValueDeserializer(jsonValueDeserializer) + .subscription(listOf(testTopic)) // Der Mono, der das nächste empfangene Ereignis darstellt val receivedEvent = KafkaReceiver.create(receiverOptions) diff --git a/infrastructure/messaging/messaging-config/src/main/kotlin/at/mocode/infrastructure/messaging/config/KafkaConfig.kt b/infrastructure/messaging/messaging-config/src/main/kotlin/at/mocode/infrastructure/messaging/config/KafkaConfig.kt index 22730efa..62da016e 100644 --- a/infrastructure/messaging/messaging-config/src/main/kotlin/at/mocode/infrastructure/messaging/config/KafkaConfig.kt +++ b/infrastructure/messaging/messaging-config/src/main/kotlin/at/mocode/infrastructure/messaging/config/KafkaConfig.kt @@ -1,57 +1,38 @@ package at.mocode.infrastructure.messaging.config -import org.apache.kafka.clients.consumer.ConsumerConfig import org.apache.kafka.clients.producer.ProducerConfig -import org.apache.kafka.common.serialization.StringDeserializer import org.apache.kafka.common.serialization.StringSerializer -import org.springframework.beans.factory.annotation.Value -import org.springframework.context.annotation.Bean -import org.springframework.context.annotation.Configuration import org.springframework.kafka.core.DefaultKafkaProducerFactory -import org.springframework.kafka.core.KafkaTemplate import org.springframework.kafka.core.ProducerFactory -import org.springframework.kafka.support.serializer.JsonDeserializer import org.springframework.kafka.support.serializer.JsonSerializer -@Configuration +/** + * Central Kafka producer configuration used across modules. + * + * This class can be instantiated programmatically (as done in tests) or + * registered as a Spring @Configuration with @Bean methods in an application context. + */ class KafkaConfig { - // KORREKTUR: Von lateinit zu einer public var mit Standardwert, um Tests zu ermöglichen - @Value($$"${spring.kafka.bootstrap-servers:localhost:9092}") + /** + * Comma-separated list of host:port pairs used for establishing the initial connection to the Kafka cluster. + */ var bootstrapServers: String = "localhost:9092" - @Value("\${spring.kafka.consumer.group-id:meldestelle-group}") - private lateinit var consumerGroupId: String + /** + * Common producer properties with sensible defaults (String keys, JSON values). + */ + fun producerConfigs(): Map = mapOf( + ProducerConfig.BOOTSTRAP_SERVERS_CONFIG to bootstrapServers, + ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG to StringSerializer::class.java, + ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG to JsonSerializer::class.java, + // Avoid adding type info headers; keeps payloads simple and interoperable. + JsonSerializer.ADD_TYPE_INFO_HEADERS to false + ) - @Bean - fun producerFactory(): ProducerFactory { - val configProps = mapOf( - ProducerConfig.BOOTSTRAP_SERVERS_CONFIG to bootstrapServers, - ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG to StringSerializer::class.java, - ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG to JsonSerializer::class.java, - ProducerConfig.ACKS_CONFIG to "all", - ProducerConfig.RETRIES_CONFIG to 3, - ProducerConfig.ENABLE_IDEMPOTENCE_CONFIG to true, - ProducerConfig.MAX_IN_FLIGHT_REQUESTS_PER_CONNECTION to 1 - ) - return DefaultKafkaProducerFactory(configProps) - } - - @Bean - fun kafkaTemplate(producerFactory: ProducerFactory): KafkaTemplate { - return KafkaTemplate(producerFactory) - } - - // NEU: Stellt eine zentrale Map mit den Basis-Konfigurationen für alle Consumer bereit. - @Bean - fun kafkaConsumerConfiguration(): Map { - return mapOf( - ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG to bootstrapServers, - ConsumerConfig.GROUP_ID_CONFIG to consumerGroupId, - ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG to StringDeserializer::class.java, - ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG to JsonDeserializer::class.java, - ConsumerConfig.AUTO_OFFSET_RESET_CONFIG to "earliest", // Beginne davon am Anfang, wenn kein Offset existiert - JsonDeserializer.TRUSTED_PACKAGES to "*" // Erlaube Deserialisierung aller unserer Klassen - ) - } + /** + * Strongly typed producer factory to avoid unchecked casts in consumers/tests. + */ + fun producerFactory(): DefaultKafkaProducerFactory = + DefaultKafkaProducerFactory(producerConfigs()) } diff --git a/infrastructure/monitoring/monitoring-client/build.gradle.kts b/infrastructure/monitoring/monitoring-client/build.gradle.kts index 2364d4d2..bd362f64 100644 --- a/infrastructure/monitoring/monitoring-client/build.gradle.kts +++ b/infrastructure/monitoring/monitoring-client/build.gradle.kts @@ -3,14 +3,9 @@ plugins { alias(libs.plugins.kotlin.jvm) alias(libs.plugins.kotlin.spring) - alias(libs.plugins.spring.boot) alias(libs.plugins.spring.dependencyManagement) } -// Deaktiviert die Erstellung eines ausführbaren Jars für dieses Bibliotheks-Modul. -tasks.getByName("bootJar") { - enabled = false -} dependencies { diff --git a/infrastructure/monitoring/monitoring-client/src/main/kotlin/at/mocode/infrastructure/monitoring/client/MonitoringClientAutoConfiguration.kt b/infrastructure/monitoring/monitoring-client/src/main/kotlin/at/mocode/infrastructure/monitoring/client/MonitoringClientAutoConfiguration.kt new file mode 100644 index 00000000..c0936115 --- /dev/null +++ b/infrastructure/monitoring/monitoring-client/src/main/kotlin/at/mocode/infrastructure/monitoring/client/MonitoringClientAutoConfiguration.kt @@ -0,0 +1,19 @@ +package at.mocode.infrastructure.monitoring.client + +import org.springframework.boot.autoconfigure.AutoConfiguration +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass +import org.springframework.context.annotation.PropertySource + +/** + * AutoConfiguration für das Monitoring-Client-Modul. + * + * Lädt konservative Default-Properties mit niedriger Priorität, die in jeder Anwendung + * leicht per application.properties/-yaml überschrieben werden können. + */ +@AutoConfiguration +@ConditionalOnClass(name = [ + "org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration", + "io.micrometer.core.instrument.MeterRegistry" +]) +@PropertySource("classpath:monitoring-defaults.properties") +class MonitoringClientAutoConfiguration diff --git a/infrastructure/monitoring/monitoring-client/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/infrastructure/monitoring/monitoring-client/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 00000000..fd942502 --- /dev/null +++ b/infrastructure/monitoring/monitoring-client/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +at.mocode.infrastructure.monitoring.client.MonitoringClientAutoConfiguration diff --git a/infrastructure/monitoring/monitoring-client/src/main/resources/monitoring-defaults.properties b/infrastructure/monitoring/monitoring-client/src/main/resources/monitoring-defaults.properties new file mode 100644 index 00000000..338f5f7e --- /dev/null +++ b/infrastructure/monitoring/monitoring-client/src/main/resources/monitoring-defaults.properties @@ -0,0 +1,27 @@ +# =================================================================== +# MELDENSTELLE - MONITORING CLIENT DEFAULTS (via AutoConfiguration) +# Diese Konfigurationen werden automatisch von jedem Service übernommen, +# der das monitoring-client-Modul einbindet. Sie können in der Anwendung +# jederzeit überschrieben werden. +# =================================================================== + +# --- Spring Boot Actuator --- +# Stellt die /actuator Endpunkte bereit (health, info, prometheus) +management.endpoints.web.exposure.include=health,info,prometheus + +# --- Micrometer Tracing --- +# Aktiviert das Tracing +management.tracing.enabled=true +# Definiert, dass Traces immer gesammelt werden sollen (1.0 = 100%) +management.tracing.sampling.probability=1.0 + +# --- Micrometer Observation (für Metriken UND Tracing) --- +# Aktiviert die "Beobachtung" von HTTP Server Requests. +# Dies erzeugt automatisch Metriken (Timer) UND Traces für eingehende Anfragen. +management.observations.http.server.requests.enabled=true + +# Fügt Anwendungs-Informationen zu den Metriken hinzu +management.info.env.enabled=true + +# Definiert den Standard-Endpunkt, an den die Traces gesendet werden. +management.zipkin.tracing.endpoint=http://zipkin:9411/api/v2/spans diff --git a/infrastructure/monitoring/monitoring-client/src/test/kotlin/at/mocode/infrastructure/monitoring/client/MonitoringClientAutoConfigurationTest.kt b/infrastructure/monitoring/monitoring-client/src/test/kotlin/at/mocode/infrastructure/monitoring/client/MonitoringClientAutoConfigurationTest.kt new file mode 100644 index 00000000..c63ed087 --- /dev/null +++ b/infrastructure/monitoring/monitoring-client/src/test/kotlin/at/mocode/infrastructure/monitoring/client/MonitoringClientAutoConfigurationTest.kt @@ -0,0 +1,25 @@ +package at.mocode.infrastructure.monitoring.client + +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test +import org.springframework.boot.autoconfigure.AutoConfigurations +import org.springframework.boot.test.context.runner.ApplicationContextRunner + +class MonitoringClientAutoConfigurationTest { + + private val contextRunner = ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(MonitoringClientAutoConfiguration::class.java)) + + @Test + fun `should load monitoring properties correctly into the environment`() { + // Arrange + val expectedPropertyValue = "true" + val propertyKey = "management.observations.http.server.requests.enabled" + + // Act & Assert + contextRunner.run { context -> + val actualPropertyValue = context.environment.getProperty(propertyKey) + assertThat(actualPropertyValue).isEqualTo(expectedPropertyValue) + } + } +} diff --git a/infrastructure/monitoring/monitoring-client/src/test/kotlin/at/mocode/infrastructure/monitoring/client/MonitoringClientTestApplication.kt b/infrastructure/monitoring/monitoring-client/src/test/kotlin/at/mocode/infrastructure/monitoring/client/MonitoringClientTestApplication.kt new file mode 100644 index 00000000..f35de440 --- /dev/null +++ b/infrastructure/monitoring/monitoring-client/src/test/kotlin/at/mocode/infrastructure/monitoring/client/MonitoringClientTestApplication.kt @@ -0,0 +1,7 @@ +package at.mocode.infrastructure.monitoring.client + +import org.springframework.boot.autoconfigure.SpringBootApplication + +// Minimaler Test-Application-Context für Library-Tests. +@SpringBootApplication +class MonitoringClientTestApplication diff --git a/infrastructure/monitoring/monitoring-server/src/main/kotlin/at/mocode/infrastructure/monitoring/MonitoringServerApplication.kt b/infrastructure/monitoring/monitoring-server/src/main/kotlin/at/mocode/infrastructure/monitoring/MonitoringServerApplication.kt index 1aee8401..5072273c 100644 --- a/infrastructure/monitoring/monitoring-server/src/main/kotlin/at/mocode/infrastructure/monitoring/MonitoringServerApplication.kt +++ b/infrastructure/monitoring/monitoring-server/src/main/kotlin/at/mocode/infrastructure/monitoring/MonitoringServerApplication.kt @@ -2,7 +2,9 @@ package at.mocode.infrastructure.monitoring import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.boot.runApplication +import zipkin2.server.internal.EnableZipkinServer +@EnableZipkinServer @SpringBootApplication class MonitoringServerApplication diff --git a/infrastructure/monitoring/monitoring-server/src/test/kotlin/at/mocode/infrastructure/monitoring/MonitoringServerApplicationTest.kt b/infrastructure/monitoring/monitoring-server/src/test/kotlin/at/mocode/infrastructure/monitoring/MonitoringServerApplicationTest.kt new file mode 100644 index 00000000..37b5926a --- /dev/null +++ b/infrastructure/monitoring/monitoring-server/src/test/kotlin/at/mocode/infrastructure/monitoring/MonitoringServerApplicationTest.kt @@ -0,0 +1,24 @@ +package at.mocode.infrastructure.monitoring + +import org.junit.jupiter.api.Test +import org.springframework.boot.test.context.SpringBootTest + +// Startet den ApplicationContext mit Webserver auf zufälligem Port und sicherer Testkonfiguration. +@SpringBootTest( + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, + properties = [ + "server.port=0", + "management.server.port=0", + "zipkin.storage.type=mem", + "zipkin.self-tracing.enabled=false", + "management.tracing.enabled=false", + "management.zipkin.tracing.endpoint=" + ] +) +class MonitoringServerApplicationTest { + + @Test + fun `context loads successfully`() { + // Test ist bestanden, wenn der Kontext ohne Exception startet. + } +}