fixing web-app

This commit is contained in:
stefan
2025-09-24 14:21:57 +02:00
parent cd2b0796a6
commit 1c4184809a
156 changed files with 440 additions and 1708 deletions
@@ -0,0 +1,56 @@
plugins {
// kotlin("jvm")
// kotlin("plugin.spring")
// id("org.springframework.boot")
alias(libs.plugins.kotlin.jvm)
alias(libs.plugins.kotlin.spring)
// KORREKTUR: Dieses Plugin ist entscheidend. Es schaltet den `springBoot`-Block
// und alle Spring-Boot-spezifischen Gradle-Tasks frei.
alias(libs.plugins.spring.boot)
// Dependency Management für konsistente Spring-Versionen
alias(libs.plugins.spring.dependencyManagement)
}
springBoot {
mainClass.set("at.mocode.members.service.MembersServiceApplicationKt")
}
dependencies {
implementation(projects.platform.platformDependencies)
implementation(projects.core.coreDomain)
implementation(projects.core.coreUtils)
implementation(projects.members.membersDomain)
implementation(projects.members.membersApplication)
implementation(projects.members.membersInfrastructure)
implementation(projects.members.membersApi)
implementation(projects.infrastructure.auth.authClient)
implementation(projects.infrastructure.cache.redisCache)
implementation(projects.infrastructure.messaging.messagingClient)
implementation(projects.infrastructure.monitoring.monitoringClient)
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.springframework.boot:spring-boot-starter-validation")
implementation("org.springframework.boot:spring-boot-starter-actuator")
implementation("org.springdoc:springdoc-openapi-starter-webmvc-ui")
// Database dependencies
implementation("org.jetbrains.exposed:exposed-core")
implementation("org.jetbrains.exposed:exposed-dao")
implementation("org.jetbrains.exposed:exposed-jdbc")
implementation("org.jetbrains.exposed:exposed-kotlin-datetime")
implementation("com.zaxxer:HikariCP")
runtimeOnly("org.postgresql:postgresql")
testRuntimeOnly("com.h2database:h2")
testImplementation(projects.platform.platformTesting)
testImplementation(libs.logback.classic) // SLF4J provider for tests
}
tasks.test {
useJUnitPlatform()
}
@@ -0,0 +1,21 @@
package at.mocode.members.service
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
import org.springframework.context.annotation.ComponentScan
/**
* Main application class for the Members Service.
*
* This service provides APIs for managing members and their data.
*/
@SpringBootApplication
@ComponentScan(basePackages = ["at.mocode.members"])
class MembersServiceApplication
/**
* Main entry point for the Members Service application.
*/
fun main(args: Array<String>) {
runApplication<MembersServiceApplication>(*args)
}
@@ -0,0 +1,104 @@
package at.mocode.members.service.config
import at.mocode.core.utils.database.DatabaseConfig
import at.mocode.core.utils.database.DatabaseFactory
import at.mocode.members.infrastructure.persistence.MemberTable
import org.springframework.context.annotation.Configuration
import org.springframework.context.annotation.Profile
import jakarta.annotation.PostConstruct
import jakarta.annotation.PreDestroy
import org.slf4j.LoggerFactory
import org.jetbrains.exposed.sql.SchemaUtils
import org.jetbrains.exposed.sql.transactions.transaction
/**
* Database configuration for the Members Service.
*
* This configuration ensures that Database.connect() is called properly
* before any Exposed operations are performed.
*/
@Configuration
@Profile("!test")
class MembersDatabaseConfiguration {
private val log = LoggerFactory.getLogger(MembersDatabaseConfiguration::class.java)
@PostConstruct
fun initializeDatabase() {
log.info("Initializing database schema for Members Service...")
try {
// Database connection is already initialized by the gateway
// Only initialize the schema for this service
transaction {
SchemaUtils.createMissingTablesAndColumns(MemberTable)
log.info("Members database schema initialized successfully")
}
} catch (e: Exception) {
log.error("Failed to initialize database schema", e)
throw e
}
}
@PreDestroy
fun closeDatabase() {
log.info("Closing database connection for Members Service...")
try {
DatabaseFactory.close()
log.info("Database connection closed successfully")
} catch (e: Exception) {
log.error("Error closing database connection", e)
}
}
}
/**
* Test-specific database configuration.
*/
@Configuration
@Profile("test")
class MembersTestDatabaseConfiguration {
private val log = LoggerFactory.getLogger(MembersTestDatabaseConfiguration::class.java)
@PostConstruct
fun initializeTestDatabase() {
log.info("Initializing test database connection for Members Service...")
try {
// Use H2 in-memory database for tests
val testConfig = DatabaseConfig(
jdbcUrl = "jdbc:h2:mem:members_test;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE",
username = "sa",
password = "",
driverClassName = "org.h2.Driver",
maxPoolSize = 5,
minPoolSize = 1,
autoMigrate = true
)
DatabaseFactory.init(testConfig)
log.info("Test database connection initialized successfully")
// Initialize database schema for tests
transaction {
SchemaUtils.createMissingTablesAndColumns(MemberTable)
log.info("Test members database schema initialized successfully")
}
} catch (e: Exception) {
log.error("Failed to initialize test database connection", e)
throw e
}
}
@PreDestroy
fun closeTestDatabase() {
log.info("Closing test database connection for Members Service...")
try {
DatabaseFactory.close()
log.info("Test database connection closed successfully")
} catch (e: Exception) {
log.error("Error closing test database connection", e)
}
}
}
@@ -0,0 +1,245 @@
package at.mocode.members.service.integration
import at.mocode.infrastructure.messaging.client.EventPublisher
import at.mocode.members.api.rest.MemberController
import at.mocode.members.domain.model.Member
import at.mocode.members.domain.repository.MemberRepository
import io.mockk.mockk
import kotlinx.coroutines.runBlocking
import kotlinx.datetime.LocalDate
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.test.context.ActiveProfiles
import org.springframework.test.context.ContextConfiguration
import org.springframework.test.context.TestPropertySource
import kotlin.test.assertEquals
import kotlin.test.assertNotNull
import kotlin.test.assertTrue
/**
* Integration tests for the Members Service.
*
* These tests verify the complete functionality including
* - REST API endpoints
* - Database operations
* - Event publishing
*/
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ActiveProfiles("test")
@TestPropertySource(properties = [
"spring.datasource.url=jdbc:h2:mem:testdb",
"spring.kafka.bootstrap-servers=localhost:9092"
])
@ContextConfiguration(classes = [MemberServiceIntegrationTest.TestConfig::class])
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class MemberServiceIntegrationTest {
@Autowired
@Qualifier("memberRepositoryImpl")
private lateinit var memberRepository: MemberRepository
@Configuration
class TestConfig {
@Bean
fun eventPublisher(): EventPublisher = mockk(relaxed = true)
}
@BeforeEach
fun setUp() = runBlocking {
// Clean up database before each test
// Note: In a real implementation, you might want to use @Transactional or @DirtiesContext
println("[DEBUG_LOG] Setting up test - cleaning database")
}
@Test
fun `should create member successfully`() = runBlocking {
println("[DEBUG_LOG] Testing member creation")
// Given
val createRequest = MemberController.CreateMemberRequest(
firstName = "John",
lastName = "Doe",
email = "john.doe@example.com",
phone = "+43123456789",
dateOfBirth = LocalDate(1990, 1, 15),
membershipNumber = "M001",
membershipStartDate = LocalDate(2024, 1, 1),
membershipEndDate = null,
isActive = true,
address = "123 Test Street, Vienna",
emergencyContact = "Jane Doe: +43987654321"
)
// When
val member = Member(
firstName = createRequest.firstName,
lastName = createRequest.lastName,
email = createRequest.email,
phone = createRequest.phone,
dateOfBirth = createRequest.dateOfBirth,
membershipNumber = createRequest.membershipNumber,
membershipStartDate = createRequest.membershipStartDate,
membershipEndDate = createRequest.membershipEndDate,
isActive = createRequest.isActive,
address = createRequest.address,
emergencyContact = createRequest.emergencyContact
)
val savedMember = memberRepository.save(member)
// Then
assertNotNull(savedMember)
assertEquals(createRequest.firstName, savedMember.firstName)
assertEquals(createRequest.lastName, savedMember.lastName)
assertEquals(createRequest.email, savedMember.email)
assertEquals(createRequest.membershipNumber, savedMember.membershipNumber)
assertTrue(savedMember.isActive)
println("[DEBUG_LOG] Member created successfully with ID: ${savedMember.memberId}")
}
@Test
fun `should find member by membership number`() = runBlocking {
println("[DEBUG_LOG] Testing find member by membership number")
// Given
val member = Member(
firstName = "Jane",
lastName = "Smith",
email = "jane.smith@example.com",
membershipNumber = "M002",
membershipStartDate = LocalDate(2024, 1, 1),
isActive = true
)
memberRepository.save(member)
// When
val foundMember = memberRepository.findByMembershipNumber("M002")
// Then
assertNotNull(foundMember)
assertEquals("Jane", foundMember.firstName)
assertEquals("Smith", foundMember.lastName)
assertEquals("M002", foundMember.membershipNumber)
println("[DEBUG_LOG] Member found by membership number: ${foundMember.memberId}")
}
@Test
fun `should find member by email`() = runBlocking {
println("[DEBUG_LOG] Testing find member by email")
// Given
val member = Member(
firstName = "Bob",
lastName = "Johnson",
email = "bob.johnson@example.com",
membershipNumber = "M003",
membershipStartDate = LocalDate(2024, 1, 1),
isActive = true
)
memberRepository.save(member)
// When
val foundMember = memberRepository.findByEmail("bob.johnson@example.com")
// Then
assertNotNull(foundMember)
assertEquals("Bob", foundMember.firstName)
assertEquals("Johnson", foundMember.lastName)
assertEquals("bob.johnson@example.com", foundMember.email)
println("[DEBUG_LOG] Member found by email: ${foundMember.memberId}")
}
@Test
fun `should count active members`() = runBlocking {
println("[DEBUG_LOG] Testing count active members")
// Given
val activeMember = Member(
firstName = "Active",
lastName = "Member",
email = "active@example.com",
membershipNumber = "M004",
membershipStartDate = LocalDate(2024, 1, 1),
isActive = true
)
val inactiveMember = Member(
firstName = "Inactive",
lastName = "Member",
email = "inactive@example.com",
membershipNumber = "M005",
membershipStartDate = LocalDate(2024, 1, 1),
isActive = false
)
memberRepository.save(activeMember)
memberRepository.save(inactiveMember)
// When
val activeCount = memberRepository.countActive()
val totalCount = memberRepository.countAll()
// Then
assertTrue(activeCount >= 1, "Should have at least 1 active member")
assertTrue(totalCount >= 2, "Should have at least 2 total members")
println("[DEBUG_LOG] Active members: $activeCount, Total members: $totalCount")
}
@Test
fun `should validate duplicate membership number`() = runBlocking {
println("[DEBUG_LOG] Testing duplicate membership number validation")
// Given
val member1 = Member(
firstName = "First",
lastName = "Member",
email = "first@example.com",
membershipNumber = "M006",
membershipStartDate = LocalDate(2024, 1, 1),
isActive = true
)
memberRepository.save(member1)
// When
val exists = memberRepository.existsByMembershipNumber("M006")
// Then
assertTrue(exists, "Should detect existing membership number")
println("[DEBUG_LOG] Duplicate membership number validation passed")
}
@Test
fun `should validate duplicate email`() = runBlocking {
println("[DEBUG_LOG] Testing duplicate email validation")
// Given
val member = Member(
firstName = "Email",
lastName = "Test",
email = "email.test@example.com",
membershipNumber = "M007",
membershipStartDate = LocalDate(2024, 1, 1),
isActive = true
)
memberRepository.save(member)
// When
val exists = memberRepository.existsByEmail("email.test@example.com")
// Then
assertTrue(exists, "Should detect existing email")
println("[DEBUG_LOG] Duplicate email validation passed")
}
}
@@ -0,0 +1,10 @@
<configuration>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="CONSOLE" />
</root>
</configuration>