chore(ci): Align GH Workflows with Docker SSoT, new paths; minimal SSoT guard; staticAnalysis (#23)
* chore(MP-21): snapshot pre-refactor state (Epic 1)
* chore(MP-22): scaffold new repo structure, relocate Docker Compose, move frontend/backend modules, update Makefile; add docs mapping and env template
* MP-22 Epic 2: Erfolgreich umgesetzt und verifiziert
* MP-23 Epic 3: Gradle/Build Governance zentralisieren
* MP-23 Epic 3: Gradle/Build Governance zentralisieren
* chore(devops)!: Docker-SSoT (.env) konsolidiert, Compose-Mounts ergänzt, Makefile entfernt
- ENV Single Source of Truth
- docker/.env.example neu (inkl. REDIS_PASSWORD, Ports, Build-Overrides)
- config/.env(.example) als DEPRECATED markiert (Verweis auf docker/.env[.example])
- Docker Compose vereinheitlicht (docker/docker-compose.yaml)
- Postgres: zentralen postgresql.conf mounten (../config/postgres/postgresql.conf)
und Start mit -c config_file=/etc/postgresql/postgresql.conf
- Redis: zentralen redis.conf mounten (../config/redis/redis.conf)
und Start via "redis-server … ${REDIS_PASSWORD:+--requirepass $REDIS_PASSWORD}"
- Web-Nginx: ../config/nginx/nginx.prod.conf → /etc/nginx/nginx.conf (ro)
- Monitoring: Prometheus/Grafana nutzen ../config/monitoring/* als SSoT
- Frontend/DI/Network (MP-23 Grundlage)
- :frontend:core:network Modul mit Koin `apiClient` (Ktor + JSON/Retry/Timeout/Logging)
- Plattform-Basis-URL-Auflösung (JVM: ENV API_BASE_URL; JS: globalThis.API_BASE_URL / Same-Origin)
- Web index.html setzt API_BASE_URL (Query `?apiBaseUrl=…` > Same-Origin > Fallback)
- Build/Gradle & Module-Refs
- settings.gradle.kts: neue Frontend-/Backend-Pfade bereits inkludiert
- Features/Shell: Abhängigkeiten auf :frontend:shared / :frontend:core:* angepasst
- Ping-API-Refs auf :backend:services:ping:ping-api vereinheitlicht
- Dockerfiles angepasst
- backend/infrastructure/gateway/Dockerfile → Tasks/Pfade auf :backend:gateway
- backend/services/ping/Dockerfile → Tasks/Pfade auf :backend:services:ping:ping-service
- Static Analysis / Guards
- config/detekt/detekt.yml hinzugefügt
- Leichter Arch-Guard (Frontend) gegen manuelle Authorization-Header vorbereitet
- Doku
- docs/ARCHITECTURE.md (Struktur, Mapping, Next Steps) ergänzt
- docs/adr/README.md angelegt
BREAKING CHANGES:
- Makefile komplett entfernt (bitte direkt `docker compose` verwenden)
- ENV-Quelle ist jetzt docker/.env (statt config/.env oder Root)
- Compose-Datei unter docker/docker-compose.yaml (nicht mehr compose.yaml im Repo-Root)
Verifikation (lokal):
- ENV anlegen: `cp docker/.env.example docker/.env` (Werte anpassen)
- Compose prüfen: `docker compose --env-file docker/.env -f docker/docker-compose.yaml config`
- Infrastruktur: `docker compose --env-file docker/.env -f docker/docker-compose.yaml -p meldestelle up -d postgres redis keycloak web-app`
- Services bauen: `docker compose --env-file docker/.env -f docker/docker-compose.yaml -p meldestelle build api-gateway ping-service --no-cache --progress=plain`
Refs: MP-22 (Epic 2), MP-23 (Epic 3)
* chore(devops)!: Docker-SSoT (.env) konsolidiert, Compose-Mounts ergänzt, Makefile entfernt
- ENV Single Source of Truth
- docker/.env.example neu (inkl. REDIS_PASSWORD, Ports, Build-Overrides)
- config/.env(.example) als DEPRECATED markiert (Verweis auf docker/.env[.example])
- Docker Compose vereinheitlicht (docker/docker-compose.yaml)
- Postgres: zentralen postgresql.conf mounten (../config/postgres/postgresql.conf)
und Start mit -c config_file=/etc/postgresql/postgresql.conf
- Redis: zentralen redis.conf mounten (../config/redis/redis.conf)
und Start via "redis-server … ${REDIS_PASSWORD:+--requirepass $REDIS_PASSWORD}"
- Web-Nginx: ../config/nginx/nginx.prod.conf → /etc/nginx/nginx.conf (ro)
- Monitoring: Prometheus/Grafana nutzen ../config/monitoring/* als SSoT
- Frontend/DI/Network (MP-23 Grundlage)
- :frontend:core:network Modul mit Koin `apiClient` (Ktor + JSON/Retry/Timeout/Logging)
- Plattform-Basis-URL-Auflösung (JVM: ENV API_BASE_URL; JS: globalThis.API_BASE_URL / Same-Origin)
- Web index.html setzt API_BASE_URL (Query `?apiBaseUrl=…` > Same-Origin > Fallback)
- Build/Gradle & Module-Refs
- settings.gradle.kts: neue Frontend-/Backend-Pfade bereits inkludiert
- Features/Shell: Abhängigkeiten auf :frontend:shared / :frontend:core:* angepasst
- Ping-API-Refs auf :backend:services:ping:ping-api vereinheitlicht
- Dockerfiles angepasst
- backend/infrastructure/gateway/Dockerfile → Tasks/Pfade auf :backend:gateway
- backend/services/ping/Dockerfile → Tasks/Pfade auf :backend:services:ping:ping-service
- Static Analysis / Guards
- config/detekt/detekt.yml hinzugefügt
- Leichter Arch-Guard (Frontend) gegen manuelle Authorization-Header vorbereitet
- Doku
- docs/ARCHITECTURE.md (Struktur, Mapping, Next Steps) ergänzt
- docs/adr/README.md angelegt
BREAKING CHANGES:
- Makefile komplett entfernt (bitte direkt `docker compose` verwenden)
- ENV-Quelle ist jetzt docker/.env (statt config/.env oder Root)
- Compose-Datei unter docker/docker-compose.yaml (nicht mehr compose.yaml im Repo-Root)
Verifikation (lokal):
- ENV anlegen: `cp docker/.env.example docker/.env` (Werte anpassen)
- Compose prüfen: `docker compose --env-file docker/.env -f docker/docker-compose.yaml config`
- Infrastruktur: `docker compose --env-file docker/.env -f docker/docker-compose.yaml -p meldestelle up -d postgres redis keycloak web-app`
- Services bauen: `docker compose --env-file docker/.env -f docker/docker-compose.yaml -p meldestelle build api-gateway ping-service --no-cache --progress=plain`
Refs: MP-22 (Epic 2), MP-23 (Epic 3)
* chore(devops)!: Docker-SSoT (.env) konsolidiert, Compose-Mounts ergänzt, Makefile entfernt
- ENV Single Source of Truth
- docker/.env.example neu (inkl. REDIS_PASSWORD, Ports, Build-Overrides)
- config/.env(.example) als DEPRECATED markiert (Verweis auf docker/.env[.example])
- Docker Compose vereinheitlicht (docker/docker-compose.yaml)
- Postgres: zentralen postgresql.conf mounten (../config/postgres/postgresql.conf)
und Start mit -c config_file=/etc/postgresql/postgresql.conf
- Redis: zentralen redis.conf mounten (../config/redis/redis.conf)
und Start via "redis-server … ${REDIS_PASSWORD:+--requirepass $REDIS_PASSWORD}"
- Web-Nginx: ../config/nginx/nginx.prod.conf → /etc/nginx/nginx.conf (ro)
- Monitoring: Prometheus/Grafana nutzen ../config/monitoring/* als SSoT
- Frontend/DI/Network (MP-23 Grundlage)
- :frontend:core:network Modul mit Koin `apiClient` (Ktor + JSON/Retry/Timeout/Logging)
- Plattform-Basis-URL-Auflösung (JVM: ENV API_BASE_URL; JS: globalThis.API_BASE_URL / Same-Origin)
- Web index.html setzt API_BASE_URL (Query `?apiBaseUrl=…` > Same-Origin > Fallback)
- Build/Gradle & Module-Refs
- settings.gradle.kts: neue Frontend-/Backend-Pfade bereits inkludiert
- Features/Shell: Abhängigkeiten auf :frontend:shared / :frontend:core:* angepasst
- Ping-API-Refs auf :backend:services:ping:ping-api vereinheitlicht
- Dockerfiles angepasst
- backend/infrastructure/gateway/Dockerfile → Tasks/Pfade auf :backend:gateway
- backend/services/ping/Dockerfile → Tasks/Pfade auf :backend:services:ping:ping-service
- Static Analysis / Guards
- config/detekt/detekt.yml hinzugefügt
- Leichter Arch-Guard (Frontend) gegen manuelle Authorization-Header vorbereitet
- Doku
- docs/ARCHITECTURE.md (Struktur, Mapping, Next Steps) ergänzt
- docs/adr/README.md angelegt
BREAKING CHANGES:
- Makefile komplett entfernt (bitte direkt `docker compose` verwenden)
- ENV-Quelle ist jetzt docker/.env (statt config/.env oder Root)
- Compose-Datei unter docker/docker-compose.yaml (nicht mehr compose.yaml im Repo-Root)
Verifikation (lokal):
- ENV anlegen: `cp docker/.env.example docker/.env` (Werte anpassen)
- Compose prüfen: `docker compose --env-file docker/.env -f docker/docker-compose.yaml config`
- Infrastruktur: `docker compose --env-file docker/.env -f docker/docker-compose.yaml -p meldestelle up -d postgres redis keycloak web-app`
- Services bauen: `docker compose --env-file docker/.env -f docker/docker-compose.yaml -p meldestelle build api-gateway ping-service --no-cache --progress=plain`
Refs: MP-22 (Epic 2), MP-23 (Epic 3)
* chore(ci): Workflows an Docker-SSoT & neue Struktur angepasst, minimaler SSoT-Guard
- ssot-guard.yml: Option B (minimal) → `docker compose -f docker/docker-compose.yaml config` als Lint
- integration-tests.yml: `./gradlew staticAnalysis` vor Integrationstests
- docs-kdoc-sync.yml: Dokka-Task Fallback (dokkaGfmAll || dokkaGfm), YouTrack-Sync nur wenn Script vorhanden
- deploy-proxmox.yml: Compose-Pfade auf docker/docker-compose.yaml + `--env-file docker/.env`; Build/Test Schritte vereinheitlicht
- ci-main.yml: SSoT-Skripte per `if: hashFiles(...)` guarded, Compose-Lint Fallback; OpenAPI‑Pfad → backend/gateway; ADR‑Pfade → docs/adr/**; `staticAnalysis` in Build integriert
- youtrack-sync.yml: unverändert (funktional)
Refs: MP-22, MP-23
* chore(ci): Workflows an Docker-SSoT & neue Struktur angepasst, minimaler SSoT-Guard
- ssot-guard.yml: Option B (minimal) → `docker compose -f docker/docker-compose.yaml config` als Lint
- integration-tests.yml: `./gradlew staticAnalysis` vor Integrationstests
- docs-kdoc-sync.yml: Dokka-Task Fallback (dokkaGfmAll || dokkaGfm), YouTrack-Sync nur wenn Script vorhanden
- deploy-proxmox.yml: Compose-Pfade auf docker/docker-compose.yaml + `--env-file docker/.env`; Build/Test Schritte vereinheitlicht
- ci-main.yml: SSoT-Skripte per `if: hashFiles(...)` guarded, Compose-Lint Fallback; OpenAPI‑Pfad → backend/gateway; ADR‑Pfade → docs/adr/**; `staticAnalysis` in Build integriert
- youtrack-sync.yml: unverändert (funktional)
Refs: MP-22, MP-23
* fix(ci): create .env from example before validating compose config
* fix(ci): update ssot-guard filename (.yaml) and sync workflow state
* fixing
* fix(webpack): correct sql.js fallback configuration for webpack 5
This commit is contained in:
+136
@@ -0,0 +1,136 @@
|
||||
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.kafka.core.DefaultKafkaProducerFactory
|
||||
import org.springframework.kafka.support.serializer.JsonDeserializer
|
||||
import org.springframework.kafka.support.serializer.JsonSerializer
|
||||
|
||||
/**
|
||||
* Zentrale Kafka-Konfiguration mit optimierten Einstellungen für Performance und Zuverlässigkeit.
|
||||
*
|
||||
* Diese Klasse kann programmatisch instanziiert werden (z. B. in Tests) oder
|
||||
* als Spring-@Configuration mit @Bean-Methoden in einem Application Context registriert werden.
|
||||
*
|
||||
* Erweitert um Konfigurationsvalidierung und zusätzliche Optimierungen.
|
||||
*/
|
||||
class KafkaConfig {
|
||||
|
||||
/**
|
||||
* Kommagetrennte Liste von host:port-Paaren für die initiale Verbindung zum Kafka-Cluster.
|
||||
*/
|
||||
var bootstrapServers: String = "localhost:9092"
|
||||
set(value) {
|
||||
require(value.isNotBlank()) { "Bootstrap servers cannot be blank" }
|
||||
// Support both simple format (host:port) and protocol-prefixed format (PLAINTEXT://host:port)
|
||||
val isValidFormat = value.matches(Regex("^[a-zA-Z0-9._-]+:[0-9]+(,[a-zA-Z0-9._-]+:[0-9]+)*$")) ||
|
||||
value.matches(Regex("^[A-Z]+://[a-zA-Z0-9._-]+:[0-9]+(,[A-Z]+://[a-zA-Z0-9._-]+:[0-9]+)*$"))
|
||||
require(isValidFormat) {
|
||||
"Bootstrap servers must be in format 'host:port' or 'PROTOCOL://host:port'"
|
||||
}
|
||||
field = value
|
||||
}
|
||||
|
||||
/**
|
||||
* Standard-Präfix für Consumer-Group-IDs.
|
||||
*/
|
||||
var defaultGroupIdPrefix: String = "messaging-client"
|
||||
set(value) {
|
||||
require(value.isNotBlank()) { "Default group ID prefix cannot be blank" }
|
||||
require(value.matches(Regex("^[a-zA-Z0-9._-]+$"))) {
|
||||
"Group ID prefix must contain only alphanumeric characters, dots, underscores, and hyphens"
|
||||
}
|
||||
field = value
|
||||
}
|
||||
|
||||
/**
|
||||
* Comma-separated list of trusted packages for JSON deserialization security.
|
||||
* Default restricts to application packages only.
|
||||
*/
|
||||
var trustedPackages: String = "at.mocode.*"
|
||||
set(value) {
|
||||
require(value.isNotBlank()) { "Trusted packages cannot be blank" }
|
||||
field = value
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable additional security features for production environments.
|
||||
*/
|
||||
var enableSecurityFeatures: Boolean = true
|
||||
|
||||
/**
|
||||
* Connection pool size for better resource management.
|
||||
*/
|
||||
var connectionPoolSize: Int = 10
|
||||
set(value) {
|
||||
require(value > 0) { "Connection pool size must be positive" }
|
||||
field = value
|
||||
}
|
||||
|
||||
/**
|
||||
* Optimized producer properties with performance tuning and reliability settings.
|
||||
*/
|
||||
fun producerConfigs(): Map<String, Any> = 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,
|
||||
|
||||
// Performance optimizations
|
||||
ProducerConfig.BATCH_SIZE_CONFIG to 32768, // 32KB batch size for better throughput
|
||||
ProducerConfig.LINGER_MS_CONFIG to 5, // Wait up to 5ms to batch messages
|
||||
ProducerConfig.COMPRESSION_TYPE_CONFIG to "snappy", // Fast compression
|
||||
ProducerConfig.BUFFER_MEMORY_CONFIG to 67108864, // 64MB buffer memory
|
||||
|
||||
// Reliability settings
|
||||
ProducerConfig.ACKS_CONFIG to "all", // Wait for all replicas
|
||||
ProducerConfig.RETRIES_CONFIG to 3, // Retry failed sends
|
||||
ProducerConfig.RETRY_BACKOFF_MS_CONFIG to 1000, // 1 second retry backoff
|
||||
ProducerConfig.DELIVERY_TIMEOUT_MS_CONFIG to 30000, // 30 second delivery timeout
|
||||
ProducerConfig.REQUEST_TIMEOUT_MS_CONFIG to 10000, // 10 second request timeout
|
||||
|
||||
// Idempotence for exactly-once semantics
|
||||
ProducerConfig.ENABLE_IDEMPOTENCE_CONFIG to true,
|
||||
ProducerConfig.MAX_IN_FLIGHT_REQUESTS_PER_CONNECTION to 5
|
||||
)
|
||||
|
||||
/**
|
||||
* Optimized consumer properties with performance tuning and reliability settings.
|
||||
*/
|
||||
fun consumerConfigs(groupId: String? = null): Map<String, Any> = mapOf(
|
||||
ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG to bootstrapServers,
|
||||
ConsumerConfig.GROUP_ID_CONFIG to (groupId ?: "${defaultGroupIdPrefix}-${System.currentTimeMillis()}"),
|
||||
ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG to StringDeserializer::class.java,
|
||||
ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG to JsonDeserializer::class.java,
|
||||
|
||||
// JSON deserialization security
|
||||
JsonDeserializer.TRUSTED_PACKAGES to trustedPackages,
|
||||
JsonDeserializer.USE_TYPE_INFO_HEADERS to false,
|
||||
|
||||
// Performance optimizations
|
||||
ConsumerConfig.FETCH_MIN_BYTES_CONFIG to 1024, // 1KB minimum fetch size
|
||||
ConsumerConfig.FETCH_MAX_WAIT_MS_CONFIG to 500, // Max 500ms wait for fetch
|
||||
ConsumerConfig.MAX_PARTITION_FETCH_BYTES_CONFIG to 1048576, // 1MB max partition fetch
|
||||
ConsumerConfig.MAX_POLL_RECORDS_CONFIG to 500, // Process up to 500 records per poll
|
||||
|
||||
// Reliability settings
|
||||
ConsumerConfig.AUTO_OFFSET_RESET_CONFIG to "earliest",
|
||||
ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG to false, // Manual commit for better control
|
||||
ConsumerConfig.SESSION_TIMEOUT_MS_CONFIG to 30000, // 30 second session timeout
|
||||
ConsumerConfig.HEARTBEAT_INTERVAL_MS_CONFIG to 3000, // 3 second heartbeat
|
||||
|
||||
// Connection settings
|
||||
ConsumerConfig.CONNECTIONS_MAX_IDLE_MS_CONFIG to 540000, // 9 minutes idle timeout
|
||||
ConsumerConfig.RECONNECT_BACKOFF_MS_CONFIG to 50,
|
||||
ConsumerConfig.RECONNECT_BACKOFF_MAX_MS_CONFIG to 1000
|
||||
)
|
||||
|
||||
/**
|
||||
* Strongly typed producer factory to avoid unchecked casts in consumers/tests.
|
||||
*/
|
||||
fun producerFactory(): DefaultKafkaProducerFactory<String, Any> =
|
||||
DefaultKafkaProducerFactory(producerConfigs())
|
||||
}
|
||||
+147
@@ -0,0 +1,147 @@
|
||||
package at.mocode.infrastructure.messaging.config
|
||||
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import org.junit.jupiter.api.assertDoesNotThrow
|
||||
import org.junit.jupiter.api.assertThrows
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class KafkaConfigTest {
|
||||
|
||||
@Test
|
||||
fun `should validate bootstrap servers format`() {
|
||||
val config = KafkaConfig()
|
||||
|
||||
// Valid formats
|
||||
assertDoesNotThrow { config.bootstrapServers = "localhost:9092" }
|
||||
assertDoesNotThrow { config.bootstrapServers = "PLAINTEXT://localhost:9092" }
|
||||
assertDoesNotThrow { config.bootstrapServers = "host1:9092,host2:9092" }
|
||||
assertDoesNotThrow { config.bootstrapServers = "PLAINTEXT://host1:9092,PLAINTEXT://host2:9092" }
|
||||
assertDoesNotThrow { config.bootstrapServers = "kafka.example.com:9092" }
|
||||
assertDoesNotThrow { config.bootstrapServers = "kafka-cluster-01.internal:9092" }
|
||||
|
||||
// Invalid formats
|
||||
assertThrows<IllegalArgumentException> { config.bootstrapServers = "" }
|
||||
assertThrows<IllegalArgumentException> { config.bootstrapServers = " " }
|
||||
assertThrows<IllegalArgumentException> { config.bootstrapServers = "invalid-format" }
|
||||
assertThrows<IllegalArgumentException> { config.bootstrapServers = "localhost" }
|
||||
assertThrows<IllegalArgumentException> { config.bootstrapServers = ":9092" }
|
||||
assertThrows<IllegalArgumentException> { config.bootstrapServers = "localhost:" }
|
||||
assertThrows<IllegalArgumentException> { config.bootstrapServers = "localhost:abc" }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should validate group ID prefix`() {
|
||||
val config = KafkaConfig()
|
||||
|
||||
// Valid prefixes
|
||||
assertDoesNotThrow { config.defaultGroupIdPrefix = "valid-prefix_123" }
|
||||
assertDoesNotThrow { config.defaultGroupIdPrefix = "messaging-client" }
|
||||
assertDoesNotThrow { config.defaultGroupIdPrefix = "test.group.id" }
|
||||
assertDoesNotThrow { config.defaultGroupIdPrefix = "simple123" }
|
||||
|
||||
// Invalid prefixes
|
||||
assertThrows<IllegalArgumentException> { config.defaultGroupIdPrefix = "" }
|
||||
assertThrows<IllegalArgumentException> { config.defaultGroupIdPrefix = " " }
|
||||
assertThrows<IllegalArgumentException> { config.defaultGroupIdPrefix = "invalid@prefix" }
|
||||
assertThrows<IllegalArgumentException> { config.defaultGroupIdPrefix = "invalid#prefix" }
|
||||
assertThrows<IllegalArgumentException> { config.defaultGroupIdPrefix = "invalid prefix" }
|
||||
assertThrows<IllegalArgumentException> { config.defaultGroupIdPrefix = "invalid/prefix" }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should validate trusted packages`() {
|
||||
val config = KafkaConfig()
|
||||
|
||||
// Valid trusted packages
|
||||
assertDoesNotThrow { config.trustedPackages = "at.mocode.*,com.example.*" }
|
||||
assertDoesNotThrow { config.trustedPackages = "at.mocode.*" }
|
||||
assertDoesNotThrow { config.trustedPackages = "com.example.specific.Package" }
|
||||
assertDoesNotThrow { config.trustedPackages = "java.lang.*,java.util.*" }
|
||||
|
||||
// Invalid trusted packages
|
||||
assertThrows<IllegalArgumentException> { config.trustedPackages = "" }
|
||||
assertThrows<IllegalArgumentException> { config.trustedPackages = " " }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should validate connection pool size`() {
|
||||
val config = KafkaConfig()
|
||||
|
||||
// Valid pool sizes
|
||||
assertDoesNotThrow { config.connectionPoolSize = 1 }
|
||||
assertDoesNotThrow { config.connectionPoolSize = 5 }
|
||||
assertDoesNotThrow { config.connectionPoolSize = 10 }
|
||||
assertDoesNotThrow { config.connectionPoolSize = 100 }
|
||||
|
||||
// Invalid pool sizes
|
||||
assertThrows<IllegalArgumentException> { config.connectionPoolSize = 0 }
|
||||
assertThrows<IllegalArgumentException> { config.connectionPoolSize = -1 }
|
||||
assertThrows<IllegalArgumentException> { config.connectionPoolSize = -10 }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should have default values set correctly`() {
|
||||
val config = KafkaConfig()
|
||||
|
||||
assertThat(config.bootstrapServers).isEqualTo("localhost:9092")
|
||||
assertThat(config.defaultGroupIdPrefix).isEqualTo("messaging-client")
|
||||
assertThat(config.trustedPackages).isEqualTo("at.mocode.*")
|
||||
assertThat(config.enableSecurityFeatures).isEqualTo(true)
|
||||
assertThat(config.connectionPoolSize).isEqualTo(10)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should generate valid producer configs`() {
|
||||
val config = KafkaConfig()
|
||||
val producerConfigs = config.producerConfigs()
|
||||
|
||||
// Verify essential producer configuration
|
||||
assertThat(producerConfigs["bootstrap.servers"]).isEqualTo("localhost:9092")
|
||||
assertThat(producerConfigs["key.serializer"]).isEqualTo(org.apache.kafka.common.serialization.StringSerializer::class.java)
|
||||
assertThat(producerConfigs["value.serializer"]).isEqualTo(org.springframework.kafka.support.serializer.JsonSerializer::class.java)
|
||||
assertThat(producerConfigs["acks"]).isEqualTo("all")
|
||||
assertThat(producerConfigs["enable.idempotence"]).isEqualTo(true)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should generate valid consumer configs with custom group ID`() {
|
||||
val config = KafkaConfig()
|
||||
val customGroupId = "test-group-123"
|
||||
val consumerConfigs = config.consumerConfigs(customGroupId)
|
||||
|
||||
// Verify essential consumer configuration
|
||||
assertThat(consumerConfigs["bootstrap.servers"]).isEqualTo("localhost:9092")
|
||||
assertThat(consumerConfigs["group.id"]).isEqualTo(customGroupId)
|
||||
assertThat(consumerConfigs["key.deserializer"]).isEqualTo(org.apache.kafka.common.serialization.StringDeserializer::class.java)
|
||||
assertThat(consumerConfigs["value.deserializer"]).isEqualTo(org.springframework.kafka.support.serializer.JsonDeserializer::class.java)
|
||||
assertThat(consumerConfigs["spring.json.trusted.packages"]).isEqualTo("at.mocode.*")
|
||||
assertThat(consumerConfigs["auto.offset.reset"]).isEqualTo("earliest")
|
||||
assertThat(consumerConfigs["enable.auto.commit"]).isEqualTo(false)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should generate unique consumer configs when no group ID provided`() {
|
||||
val config = KafkaConfig()
|
||||
val consumerConfigs1 = config.consumerConfigs()
|
||||
val consumerConfigs2 = config.consumerConfigs()
|
||||
|
||||
// Group IDs should be different (timestamp-based)
|
||||
val groupId1 = consumerConfigs1["group.id"].toString()
|
||||
val groupId2 = consumerConfigs2["group.id"].toString()
|
||||
|
||||
assertThat(groupId1).isNotEqualTo(groupId2)
|
||||
assertThat(groupId1).startsWith("messaging-client-")
|
||||
assertThat(groupId2).startsWith("messaging-client-")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should create producer factory with correct configuration`() {
|
||||
val config = KafkaConfig()
|
||||
val producerFactory = config.producerFactory()
|
||||
|
||||
assertDoesNotThrow { producerFactory.createProducer() }
|
||||
assertThat(producerFactory.configurationProperties["bootstrap.servers"]).isEqualTo("localhost:9092")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user