(vision) SCS/DDD
This commit is contained in:
@@ -0,0 +1,250 @@
|
||||
package at.mocode
|
||||
|
||||
import at.mocode.utils.ApiResponse
|
||||
import at.mocode.utils.ErrorResponse
|
||||
import kotlin.test.*
|
||||
|
||||
/**
|
||||
* Comprehensive test suite for ApiResponse utility classes and data structures.
|
||||
*
|
||||
* This test class verifies:
|
||||
* - ApiResponse data class structure and behavior
|
||||
* - ErrorResponse data class structure and behavior
|
||||
* - Data class serialization compatibility
|
||||
* - Proper field assignments and null handling
|
||||
*/
|
||||
class ApiResponseTest {
|
||||
|
||||
@Test
|
||||
fun testApiResponseDataClassSuccess() {
|
||||
val response = ApiResponse(
|
||||
success = true,
|
||||
data = mapOf("id" to "1", "name" to "Test"),
|
||||
message = "Operation successful"
|
||||
)
|
||||
|
||||
assertTrue(response.success)
|
||||
assertNotNull(response.data)
|
||||
assertEquals("Operation successful", response.message)
|
||||
assertNull(response.error)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testApiResponseDataClassError() {
|
||||
val response = ApiResponse<Nothing>(
|
||||
success = false,
|
||||
error = "VALIDATION_ERROR",
|
||||
message = "Invalid input"
|
||||
)
|
||||
|
||||
assertFalse(response.success)
|
||||
assertNull(response.data)
|
||||
assertEquals("VALIDATION_ERROR", response.error)
|
||||
assertEquals("Invalid input", response.message)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testApiResponseDataClassMinimal() {
|
||||
val response = ApiResponse<String>(success = true)
|
||||
|
||||
assertTrue(response.success)
|
||||
assertNull(response.data)
|
||||
assertNull(response.error)
|
||||
assertNull(response.message)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testApiResponseDataClassWithNullData() {
|
||||
val response = ApiResponse<String>(
|
||||
success = true,
|
||||
data = null,
|
||||
message = "No data available"
|
||||
)
|
||||
|
||||
assertTrue(response.success)
|
||||
assertNull(response.data)
|
||||
assertNull(response.error)
|
||||
assertEquals("No data available", response.message)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testApiResponseDataClassWithAllFields() {
|
||||
val response = ApiResponse(
|
||||
success = false,
|
||||
data = "some data",
|
||||
error = "ERROR_CODE",
|
||||
message = "Error message"
|
||||
)
|
||||
|
||||
assertFalse(response.success)
|
||||
assertEquals("some data", response.data)
|
||||
assertEquals("ERROR_CODE", response.error)
|
||||
assertEquals("Error message", response.message)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testErrorResponseDataClass() {
|
||||
val errorResponse = ErrorResponse(
|
||||
code = "NOT_FOUND",
|
||||
message = "Resource not found",
|
||||
details = "The requested item does not exist"
|
||||
)
|
||||
|
||||
assertEquals("NOT_FOUND", errorResponse.code)
|
||||
assertEquals("Resource not found", errorResponse.message)
|
||||
assertEquals("The requested item does not exist", errorResponse.details)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testErrorResponseDataClassWithoutDetails() {
|
||||
val errorResponse = ErrorResponse(
|
||||
code = "VALIDATION_ERROR",
|
||||
message = "Invalid input"
|
||||
)
|
||||
|
||||
assertEquals("VALIDATION_ERROR", errorResponse.code)
|
||||
assertEquals("Invalid input", errorResponse.message)
|
||||
assertNull(errorResponse.details)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testErrorResponseDataClassWithEmptyDetails() {
|
||||
val errorResponse = ErrorResponse(
|
||||
code = "INTERNAL_ERROR",
|
||||
message = "Server error",
|
||||
details = ""
|
||||
)
|
||||
|
||||
assertEquals("INTERNAL_ERROR", errorResponse.code)
|
||||
assertEquals("Server error", errorResponse.message)
|
||||
assertEquals("", errorResponse.details)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testApiResponseEquality() {
|
||||
val response1 = ApiResponse(
|
||||
success = true,
|
||||
data = "test",
|
||||
message = "success"
|
||||
)
|
||||
|
||||
val response2 = ApiResponse(
|
||||
success = true,
|
||||
data = "test",
|
||||
message = "success"
|
||||
)
|
||||
|
||||
assertEquals(response1, response2)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testApiResponseInequality() {
|
||||
val response1 = ApiResponse(
|
||||
success = true,
|
||||
data = "test1",
|
||||
message = "success"
|
||||
)
|
||||
|
||||
val response2 = ApiResponse(
|
||||
success = true,
|
||||
data = "test2",
|
||||
message = "success"
|
||||
)
|
||||
|
||||
assertNotEquals(response1, response2)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testErrorResponseEquality() {
|
||||
val error1 = ErrorResponse(
|
||||
code = "ERROR",
|
||||
message = "Test error",
|
||||
details = "Details"
|
||||
)
|
||||
|
||||
val error2 = ErrorResponse(
|
||||
code = "ERROR",
|
||||
message = "Test error",
|
||||
details = "Details"
|
||||
)
|
||||
|
||||
assertEquals(error1, error2)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testErrorResponseInequality() {
|
||||
val error1 = ErrorResponse(
|
||||
code = "ERROR1",
|
||||
message = "Test error",
|
||||
details = "Details"
|
||||
)
|
||||
|
||||
val error2 = ErrorResponse(
|
||||
code = "ERROR2",
|
||||
message = "Test error",
|
||||
details = "Details"
|
||||
)
|
||||
|
||||
assertNotEquals(error1, error2)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testApiResponseToString() {
|
||||
val response = ApiResponse(
|
||||
success = true,
|
||||
data = "test",
|
||||
message = "success"
|
||||
)
|
||||
|
||||
val toString = response.toString()
|
||||
assertTrue(toString.contains("success=true"))
|
||||
assertTrue(toString.contains("data=test"))
|
||||
assertTrue(toString.contains("message=success"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testErrorResponseToString() {
|
||||
val error = ErrorResponse(
|
||||
code = "ERROR",
|
||||
message = "Test error",
|
||||
details = "Details"
|
||||
)
|
||||
|
||||
val toString = error.toString()
|
||||
assertTrue(toString.contains("code=ERROR"))
|
||||
assertTrue(toString.contains("message=Test error"))
|
||||
assertTrue(toString.contains("details=Details"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testApiResponseCopy() {
|
||||
val original = ApiResponse(
|
||||
success = true,
|
||||
data = "original",
|
||||
message = "original message"
|
||||
)
|
||||
|
||||
val copied = original.copy(data = "modified")
|
||||
|
||||
assertTrue(copied.success)
|
||||
assertEquals("modified", copied.data)
|
||||
assertEquals("original message", copied.message)
|
||||
assertNull(copied.error)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testErrorResponseCopy() {
|
||||
val original = ErrorResponse(
|
||||
code = "ERROR",
|
||||
message = "Original message",
|
||||
details = "Original details"
|
||||
)
|
||||
|
||||
val copied = original.copy(message = "Modified message")
|
||||
|
||||
assertEquals("ERROR", copied.code)
|
||||
assertEquals("Modified message", copied.message)
|
||||
assertEquals("Original details", copied.details)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,304 @@
|
||||
package at.mocode
|
||||
|
||||
import at.mocode.model.Artikel
|
||||
import at.mocode.repositories.ArtikelRepository
|
||||
import at.mocode.services.ArtikelService
|
||||
import com.benasher44.uuid.uuid4
|
||||
import com.ionspin.kotlin.bignum.decimal.BigDecimal
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.datetime.Clock
|
||||
import kotlin.test.*
|
||||
|
||||
class ArtikelServiceTest {
|
||||
|
||||
private lateinit var mockRepository: MockArtikelRepository
|
||||
private lateinit var artikelService: ArtikelService
|
||||
|
||||
@BeforeTest
|
||||
fun setup() {
|
||||
mockRepository = MockArtikelRepository()
|
||||
artikelService = ArtikelService(mockRepository)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testGetAllArtikel() = runBlocking {
|
||||
// Given
|
||||
val artikel1 = createTestArtikel("Test Artikel 1")
|
||||
val artikel2 = createTestArtikel("Test Artikel 2")
|
||||
mockRepository.articles = mutableListOf(artikel1, artikel2)
|
||||
|
||||
// When
|
||||
val result = artikelService.getAllArtikel()
|
||||
|
||||
// Then
|
||||
assertEquals(2, result.size)
|
||||
assertTrue(result.contains(artikel1))
|
||||
assertTrue(result.contains(artikel2))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testGetArtikelById() = runBlocking {
|
||||
// Given
|
||||
val artikel = createTestArtikel("Test Artikel")
|
||||
mockRepository.articles = mutableListOf(artikel)
|
||||
|
||||
// When
|
||||
val result = artikelService.getArtikelById(artikel.id)
|
||||
|
||||
// Then
|
||||
assertNotNull(result)
|
||||
assertEquals(artikel.id, result.id)
|
||||
assertEquals(artikel.bezeichnung, result.bezeichnung)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testGetArtikelByIdNotFound() = runBlocking {
|
||||
// Given
|
||||
val nonExistentId = uuid4()
|
||||
|
||||
// When
|
||||
val result = artikelService.getArtikelById(nonExistentId)
|
||||
|
||||
// Then
|
||||
assertNull(result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testGetArtikelByVerbandsabgabe() = runBlocking {
|
||||
// Given
|
||||
val verbandsArtikel = createTestArtikel("Verbands Artikel", istVerbandsabgabe = true)
|
||||
val normalArtikel = createTestArtikel("Normal Artikel", istVerbandsabgabe = false)
|
||||
mockRepository.articles = mutableListOf(verbandsArtikel, normalArtikel)
|
||||
|
||||
// When
|
||||
val verbandsResult = artikelService.getArtikelByVerbandsabgabe(true)
|
||||
val normalResult = artikelService.getArtikelByVerbandsabgabe(false)
|
||||
|
||||
// Then
|
||||
assertEquals(1, verbandsResult.size)
|
||||
assertEquals(verbandsArtikel.id, verbandsResult[0].id)
|
||||
|
||||
assertEquals(1, normalResult.size)
|
||||
assertEquals(normalArtikel.id, normalResult[0].id)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testGetVerbandsabgabeArtikel() = runBlocking {
|
||||
// Given
|
||||
val verbandsArtikel = createTestArtikel("Verbands Artikel", istVerbandsabgabe = true)
|
||||
val normalArtikel = createTestArtikel("Normal Artikel", istVerbandsabgabe = false)
|
||||
mockRepository.articles = mutableListOf(verbandsArtikel, normalArtikel)
|
||||
|
||||
// When
|
||||
val result = artikelService.getVerbandsabgabeArtikel()
|
||||
|
||||
// Then
|
||||
assertEquals(1, result.size)
|
||||
assertEquals(verbandsArtikel.id, result[0].id)
|
||||
assertTrue(result[0].istVerbandsabgabe)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testGetNonVerbandsabgabeArtikel() = runBlocking {
|
||||
// Given
|
||||
val verbandsArtikel = createTestArtikel("Verbands Artikel", istVerbandsabgabe = true)
|
||||
val normalArtikel = createTestArtikel("Normal Artikel", istVerbandsabgabe = false)
|
||||
mockRepository.articles = mutableListOf(verbandsArtikel, normalArtikel)
|
||||
|
||||
// When
|
||||
val result = artikelService.getNonVerbandsabgabeArtikel()
|
||||
|
||||
// Then
|
||||
assertEquals(1, result.size)
|
||||
assertEquals(normalArtikel.id, result[0].id)
|
||||
assertFalse(result[0].istVerbandsabgabe)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSearchArtikel() = runBlocking {
|
||||
// Given
|
||||
val artikel1 = createTestArtikel("Test Artikel")
|
||||
val artikel2 = createTestArtikel("Another Item")
|
||||
mockRepository.articles = mutableListOf(artikel1, artikel2)
|
||||
|
||||
// When
|
||||
val result = artikelService.searchArtikel("Test")
|
||||
|
||||
// Then
|
||||
assertEquals(1, result.size)
|
||||
assertEquals(artikel1.id, result[0].id)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSearchArtikelBlankQuery() {
|
||||
// When & Then
|
||||
assertFailsWith<IllegalArgumentException> {
|
||||
runBlocking { artikelService.searchArtikel("") }
|
||||
}
|
||||
|
||||
assertFailsWith<IllegalArgumentException> {
|
||||
runBlocking { artikelService.searchArtikel(" ") }
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCreateArtikel() = runBlocking {
|
||||
// Given
|
||||
val artikel = createTestArtikel("New Artikel")
|
||||
|
||||
// When
|
||||
val result = artikelService.createArtikel(artikel)
|
||||
|
||||
// Then
|
||||
assertEquals(artikel.bezeichnung, result.bezeichnung)
|
||||
assertEquals(artikel.preis, result.preis)
|
||||
assertEquals(artikel.einheit, result.einheit)
|
||||
assertTrue(mockRepository.articles.contains(result))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCreateArtikelValidation() {
|
||||
// Test blank bezeichnung
|
||||
assertFailsWith<IllegalArgumentException> {
|
||||
runBlocking { artikelService.createArtikel(createTestArtikel("")) }
|
||||
}
|
||||
|
||||
// Test long bezeichnung
|
||||
assertFailsWith<IllegalArgumentException> {
|
||||
runBlocking { artikelService.createArtikel(createTestArtikel("a".repeat(256))) }
|
||||
}
|
||||
|
||||
// Test negative price
|
||||
assertFailsWith<IllegalArgumentException> {
|
||||
runBlocking { artikelService.createArtikel(createTestArtikel("Test", preis = BigDecimal.fromInt(-1))) }
|
||||
}
|
||||
|
||||
// Test blank einheit
|
||||
assertFailsWith<IllegalArgumentException> {
|
||||
runBlocking { artikelService.createArtikel(createTestArtikel("Test", einheit = "")) }
|
||||
}
|
||||
|
||||
// Test long einheit
|
||||
assertFailsWith<IllegalArgumentException> {
|
||||
runBlocking { artikelService.createArtikel(createTestArtikel("Test", einheit = "a".repeat(51))) }
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testUpdateArtikel() = runBlocking {
|
||||
// Given
|
||||
val originalArtikel = createTestArtikel("Original")
|
||||
mockRepository.articles = mutableListOf(originalArtikel)
|
||||
|
||||
val updatedArtikel = originalArtikel.copy(bezeichnung = "Updated")
|
||||
|
||||
// When
|
||||
val result = artikelService.updateArtikel(originalArtikel.id, updatedArtikel)
|
||||
|
||||
// Then
|
||||
assertNotNull(result)
|
||||
assertEquals("Updated", result.bezeichnung)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testUpdateArtikelNotFound() = runBlocking {
|
||||
// Given
|
||||
val nonExistentId = uuid4()
|
||||
val artikel = createTestArtikel("Test")
|
||||
|
||||
// When
|
||||
val result = artikelService.updateArtikel(nonExistentId, artikel)
|
||||
|
||||
// Then
|
||||
assertNull(result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testUpdateArtikelValidation() {
|
||||
// Given
|
||||
val originalArtikel = createTestArtikel("Original")
|
||||
mockRepository.articles = mutableListOf(originalArtikel)
|
||||
|
||||
// Test validation during update
|
||||
assertFailsWith<IllegalArgumentException> {
|
||||
runBlocking { artikelService.updateArtikel(originalArtikel.id, createTestArtikel("")) }
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDeleteArtikel() = runBlocking {
|
||||
// Given
|
||||
val artikel = createTestArtikel("To Delete")
|
||||
mockRepository.articles = mutableListOf(artikel)
|
||||
|
||||
// When
|
||||
val result = artikelService.deleteArtikel(artikel.id)
|
||||
|
||||
// Then
|
||||
assertTrue(result)
|
||||
assertFalse(mockRepository.articles.contains(artikel))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDeleteArtikelNotFound() = runBlocking {
|
||||
// Given
|
||||
val nonExistentId = uuid4()
|
||||
|
||||
// When
|
||||
val result = artikelService.deleteArtikel(nonExistentId)
|
||||
|
||||
// Then
|
||||
assertFalse(result)
|
||||
}
|
||||
|
||||
// Helper function to create test articles
|
||||
private fun createTestArtikel(
|
||||
bezeichnung: String,
|
||||
preis: BigDecimal = BigDecimal.fromInt(100),
|
||||
einheit: String = "Stück",
|
||||
istVerbandsabgabe: Boolean = false
|
||||
): Artikel {
|
||||
return Artikel(
|
||||
id = uuid4(),
|
||||
bezeichnung = bezeichnung,
|
||||
preis = preis,
|
||||
einheit = einheit,
|
||||
istVerbandsabgabe = istVerbandsabgabe,
|
||||
createdAt = Clock.System.now(),
|
||||
updatedAt = Clock.System.now()
|
||||
)
|
||||
}
|
||||
|
||||
// Mock repository implementation for testing
|
||||
private class MockArtikelRepository : ArtikelRepository {
|
||||
var articles = mutableListOf<Artikel>()
|
||||
|
||||
override suspend fun findAll(): List<Artikel> = articles
|
||||
|
||||
override suspend fun findById(id: com.benasher44.uuid.Uuid): Artikel? =
|
||||
articles.find { it.id == id }
|
||||
|
||||
override suspend fun findByVerbandsabgabe(istVerbandsabgabe: Boolean): List<Artikel> =
|
||||
articles.filter { it.istVerbandsabgabe == istVerbandsabgabe }
|
||||
|
||||
override suspend fun search(query: String): List<Artikel> =
|
||||
articles.filter { it.bezeichnung.contains(query, ignoreCase = true) }
|
||||
|
||||
override suspend fun create(artikel: Artikel): Artikel {
|
||||
articles.add(artikel)
|
||||
return artikel
|
||||
}
|
||||
|
||||
override suspend fun update(id: com.benasher44.uuid.Uuid, artikel: Artikel): Artikel? {
|
||||
val index = articles.indexOfFirst { it.id == id }
|
||||
return if (index >= 0) {
|
||||
articles[index] = artikel.copy(id = id)
|
||||
articles[index]
|
||||
} else null
|
||||
}
|
||||
|
||||
override suspend fun delete(id: com.benasher44.uuid.Uuid): Boolean {
|
||||
return articles.removeIf { it.id == id }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,253 @@
|
||||
# Comprehensive Testing Implementation Summary
|
||||
|
||||
## Overview
|
||||
This document summarizes the comprehensive testing implementation for the Meldestelle project, covering testing strategies, patterns, and coverage achieved.
|
||||
|
||||
## Testing Framework and Patterns
|
||||
|
||||
### Framework Used
|
||||
- **Kotlin Test**: Primary testing framework using `kotlin.test.*`
|
||||
- **Ktor Testing**: For HTTP endpoint testing using `testApplication`
|
||||
- **Coroutines Testing**: Using `runBlocking` for suspend function testing
|
||||
- **Mock Objects**: Custom mock implementations for repository testing
|
||||
|
||||
### Testing Patterns Established
|
||||
|
||||
#### 1. Utility Testing Pattern (RouteUtilsTest.kt)
|
||||
```kotlin
|
||||
@Test
|
||||
fun testFunctionName() = testApplication {
|
||||
application {
|
||||
install(ContentNegotiation) { json() }
|
||||
routing {
|
||||
get("/test") {
|
||||
// Test implementation
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
client.get("/test").apply {
|
||||
assertEquals(HttpStatusCode.OK, status)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 2. Data Class Testing Pattern (ApiResponseTest.kt)
|
||||
```kotlin
|
||||
@Test
|
||||
fun testDataClassBehavior() {
|
||||
val instance = DataClass(param1 = "value1", param2 = "value2")
|
||||
|
||||
assertEquals("value1", instance.param1)
|
||||
assertEquals("value2", instance.param2)
|
||||
// Test equality, copy, toString, etc.
|
||||
}
|
||||
```
|
||||
|
||||
#### 3. Service Testing Pattern (TurnierServiceTest.kt)
|
||||
```kotlin
|
||||
class ServiceTest {
|
||||
private lateinit var mockRepository: MockRepository
|
||||
private lateinit var service: Service
|
||||
|
||||
@BeforeTest
|
||||
fun setup() {
|
||||
mockRepository = MockRepository()
|
||||
service = Service(mockRepository)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testServiceMethod() = runBlocking {
|
||||
// Given
|
||||
val testData = createTestData()
|
||||
mockRepository.data.add(testData)
|
||||
|
||||
// When
|
||||
val result = service.method()
|
||||
|
||||
// Then
|
||||
assertNotNull(result)
|
||||
assertEquals(expected, result)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Implemented Test Suites
|
||||
|
||||
### 1. RouteUtilsTest.kt ✅ (21/21 tests passing)
|
||||
**Coverage**: Comprehensive testing of route utility functions
|
||||
- Parameter extraction (UUID, String, Int, Query)
|
||||
- Validation and error handling
|
||||
- Safe execution patterns
|
||||
- Generic handler functions
|
||||
- HTTP status code verification
|
||||
|
||||
**Key Tests**:
|
||||
- `testGetUuidParameterValid/Invalid/Missing`
|
||||
- `testSafeExecuteSuccess/IllegalArgumentException/GenericException`
|
||||
- `testHandleFindById/ByStringParam/ByUuidParamList`
|
||||
|
||||
### 2. ApiResponseTest.kt ✅ (16/16 tests passing)
|
||||
**Coverage**: Complete data class testing for API response structures
|
||||
- ApiResponse data class behavior
|
||||
- ErrorResponse data class behavior
|
||||
- Serialization compatibility
|
||||
- Equality and copy operations
|
||||
- Edge cases and null handling
|
||||
|
||||
**Key Tests**:
|
||||
- `testApiResponseDataClassSuccess/Error/Minimal`
|
||||
- `testErrorResponseDataClass/WithoutDetails`
|
||||
- `testApiResponseEquality/Inequality/Copy`
|
||||
|
||||
### 3. TurnierServiceTest.kt ⚠️ (12/20 tests passing)
|
||||
**Coverage**: Business logic testing for tournament service
|
||||
- CRUD operations testing
|
||||
- Business validation rules
|
||||
- Search functionality
|
||||
- Duplicate checking logic
|
||||
- Error handling scenarios
|
||||
|
||||
**Issues**: Database connection required for transaction-based operations
|
||||
**Passing Tests**: Read operations, validation logic
|
||||
**Failing Tests**: Create/Update operations (require database setup)
|
||||
|
||||
## Testing Infrastructure Components
|
||||
|
||||
### Mock Repository Pattern
|
||||
```kotlin
|
||||
class MockRepository : Repository {
|
||||
val data = mutableListOf<Entity>()
|
||||
|
||||
override suspend fun findAll(): List<Entity> = data.toList()
|
||||
override suspend fun findById(id: Uuid): Entity? = data.find { it.id == id }
|
||||
override suspend fun create(entity: Entity): Entity {
|
||||
data.add(entity)
|
||||
return entity
|
||||
}
|
||||
// ... other CRUD operations
|
||||
}
|
||||
```
|
||||
|
||||
### Test Data Creation Utilities
|
||||
```kotlin
|
||||
private fun createTestEntity(
|
||||
param1: String,
|
||||
param2: String,
|
||||
param3: Uuid = uuid4()
|
||||
): Entity {
|
||||
return Entity(
|
||||
id = uuid4(),
|
||||
param1 = param1,
|
||||
param2 = param2,
|
||||
param3 = param3,
|
||||
// ... other required parameters
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## Components Identified for Additional Testing
|
||||
|
||||
### High Priority
|
||||
1. **TransactionManager** - Database transaction handling
|
||||
2. **Database Plugin** - Database connection and configuration
|
||||
3. **Additional Services**:
|
||||
- BewerbService
|
||||
- DomLizenzService
|
||||
- PersonService
|
||||
- VeranstaltungService
|
||||
|
||||
### Medium Priority
|
||||
4. **Repository Layer**:
|
||||
- BaseRepository
|
||||
- PostgresTurnierRepository
|
||||
- PostgresArtikelRepository
|
||||
- Other PostgreSQL repositories
|
||||
|
||||
5. **Route Handlers**:
|
||||
- TurnierRoutes
|
||||
- BewerbRoutes
|
||||
- PersonRoutes
|
||||
- Other route handlers
|
||||
|
||||
### Lower Priority
|
||||
6. **Configuration Classes**:
|
||||
- AppConfig
|
||||
- ServiceConfiguration
|
||||
7. **Validation Components**
|
||||
8. **Serialization Components**
|
||||
|
||||
## Testing Best Practices Established
|
||||
|
||||
### 1. Test Structure
|
||||
- **Given-When-Then** pattern for clarity
|
||||
- Descriptive test names indicating scenario
|
||||
- Proper setup and teardown
|
||||
|
||||
### 2. Error Testing
|
||||
- Comprehensive validation testing
|
||||
- Edge case coverage
|
||||
- Exception handling verification
|
||||
|
||||
### 3. Mock Usage
|
||||
- Isolated unit testing
|
||||
- Predictable test data
|
||||
- No external dependencies
|
||||
|
||||
### 4. HTTP Testing
|
||||
- Status code verification
|
||||
- Response content validation
|
||||
- Request/response cycle testing
|
||||
|
||||
## Recommendations for Complete Implementation
|
||||
|
||||
### 1. Database Testing Setup
|
||||
```kotlin
|
||||
@BeforeTest
|
||||
fun setupDatabase() {
|
||||
Database.connect("jdbc:h2:mem:test", driver = "org.h2.Driver")
|
||||
transaction {
|
||||
SchemaUtils.create(Tables.all)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Integration Testing
|
||||
- End-to-end API testing
|
||||
- Database integration testing
|
||||
- Multi-service interaction testing
|
||||
|
||||
### 3. Performance Testing
|
||||
- Load testing for critical endpoints
|
||||
- Database query performance
|
||||
- Memory usage validation
|
||||
|
||||
### 4. Test Configuration
|
||||
- Separate test configurations
|
||||
- Test-specific database settings
|
||||
- Environment-specific test suites
|
||||
|
||||
## Metrics and Coverage
|
||||
|
||||
### Current Status
|
||||
- **Utility Classes**: 100% coverage (RouteUtils, ApiResponse)
|
||||
- **Service Layer**: Partial coverage (TurnierService pattern established)
|
||||
- **Repository Layer**: Mock pattern established
|
||||
- **Route Layer**: Pattern established, needs implementation
|
||||
|
||||
### Test Quality Metrics
|
||||
- **RouteUtilsTest**: 21 comprehensive tests covering all utility functions
|
||||
- **ApiResponseTest**: 16 tests covering all data class behaviors
|
||||
- **TurnierServiceTest**: 20 tests covering business logic (12 passing, 8 require DB)
|
||||
|
||||
## Conclusion
|
||||
|
||||
The comprehensive testing foundation has been successfully established with:
|
||||
|
||||
1. ✅ **Complete utility testing** - RouteUtils and ApiResponse fully tested
|
||||
2. ✅ **Testing patterns defined** - Reusable patterns for all component types
|
||||
3. ✅ **Mock infrastructure** - Repository mocking pattern established
|
||||
4. ⚠️ **Service testing framework** - Pattern established, needs database setup
|
||||
5. 📋 **Clear roadmap** - Identified components and priorities for additional testing
|
||||
|
||||
The testing infrastructure provides a solid foundation for expanding test coverage across the entire application, ensuring reliability, maintainability, and confidence in the codebase.
|
||||
@@ -0,0 +1,402 @@
|
||||
package at.mocode
|
||||
|
||||
import at.mocode.events.*
|
||||
import at.mocode.events.handlers.*
|
||||
import at.mocode.model.Turnier
|
||||
import at.mocode.repositories.TurnierRepository
|
||||
import at.mocode.services.TurnierService
|
||||
import at.mocode.utils.StructuredLogger
|
||||
import at.mocode.utils.structuredLogger
|
||||
import com.benasher44.uuid.uuid4
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.datetime.LocalDate
|
||||
import kotlinx.datetime.LocalDateTime
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertNotNull
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
/**
|
||||
* Mock repository for testing
|
||||
*/
|
||||
class MockTurnierRepository : TurnierRepository {
|
||||
private val tournaments = mutableMapOf<com.benasher44.uuid.Uuid, Turnier>()
|
||||
|
||||
override suspend fun findAll(): List<Turnier> = tournaments.values.toList()
|
||||
|
||||
override suspend fun findById(id: com.benasher44.uuid.Uuid): Turnier? = tournaments[id]
|
||||
|
||||
override suspend fun findByVeranstaltungId(veranstaltungId: com.benasher44.uuid.Uuid): List<Turnier> {
|
||||
return tournaments.values.filter { it.veranstaltungId == veranstaltungId }
|
||||
}
|
||||
|
||||
override suspend fun findByOepsTurnierNr(oepsTurnierNr: String): Turnier? {
|
||||
return tournaments.values.find { it.oepsTurnierNr == oepsTurnierNr }
|
||||
}
|
||||
|
||||
override suspend fun search(query: String): List<Turnier> {
|
||||
return tournaments.values.filter {
|
||||
it.titel.contains(query, ignoreCase = true) ||
|
||||
it.oepsTurnierNr.contains(query, ignoreCase = true)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun create(turnier: Turnier): Turnier {
|
||||
tournaments[turnier.id] = turnier
|
||||
return turnier
|
||||
}
|
||||
|
||||
override suspend fun update(id: com.benasher44.uuid.Uuid, turnier: Turnier): Turnier? {
|
||||
return if (tournaments.containsKey(id)) {
|
||||
val updated = turnier.copy(id = id)
|
||||
tournaments[id] = updated
|
||||
updated
|
||||
} else null
|
||||
}
|
||||
|
||||
override suspend fun delete(id: com.benasher44.uuid.Uuid): Boolean {
|
||||
return tournaments.remove(id) != null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test class for Event-Driven Architecture implementation
|
||||
*/
|
||||
class EventDrivenArchitectureTest {
|
||||
|
||||
private val log = structuredLogger()
|
||||
private lateinit var eventPublisher: EventPublisher
|
||||
private lateinit var turnierService: TurnierService
|
||||
private lateinit var mockRepository: MockTurnierRepository
|
||||
private val capturedEvents = mutableListOf<DomainEvent>()
|
||||
|
||||
@BeforeEach
|
||||
fun setup() {
|
||||
// Clear any existing state
|
||||
eventPublisher = EventPublisher.getInstance()
|
||||
eventPublisher.clearEvents()
|
||||
eventPublisher.clearHandlers()
|
||||
capturedEvents.clear()
|
||||
|
||||
// Setup mock repository and service
|
||||
mockRepository = MockTurnierRepository()
|
||||
turnierService = TurnierService(mockRepository, eventPublisher)
|
||||
|
||||
// Register event handlers
|
||||
registerTestEventHandlers()
|
||||
}
|
||||
|
||||
private fun createTestTurnier(
|
||||
oepsTurnierNr: String,
|
||||
titel: String,
|
||||
untertitel: String? = null,
|
||||
veranstaltungId: com.benasher44.uuid.Uuid = uuid4(),
|
||||
datumVon: LocalDate = LocalDate(2024, 6, 1),
|
||||
datumBis: LocalDate = LocalDate(2024, 6, 3)
|
||||
): Turnier {
|
||||
return Turnier(
|
||||
veranstaltungId = veranstaltungId,
|
||||
oepsTurnierNr = oepsTurnierNr,
|
||||
titel = titel,
|
||||
untertitel = untertitel,
|
||||
datumVon = datumVon,
|
||||
datumBis = datumBis,
|
||||
nennungsschluss = null,
|
||||
nennungsHinweis = null,
|
||||
eigenesNennsystemUrl = null,
|
||||
nenngeld = null,
|
||||
startgeldStandard = null,
|
||||
turnierleiterId = null,
|
||||
turnierbeauftragterId = null,
|
||||
tierarztInfos = null,
|
||||
hufschmiedInfo = null,
|
||||
meldestelleVerantwortlicherId = null,
|
||||
meldestelleTelefon = null,
|
||||
meldestelleOeffnungszeiten = null,
|
||||
ergebnislistenUrl = null
|
||||
)
|
||||
}
|
||||
|
||||
private fun registerTestEventHandlers() {
|
||||
// Register a test handler that captures all events
|
||||
val testHandler = object : EventHandler<DomainEvent> {
|
||||
override suspend fun handle(event: DomainEvent) {
|
||||
capturedEvents.add(event)
|
||||
log.debug("Test handler captured event", mapOf(
|
||||
"event_type" to event.eventType,
|
||||
"aggregate_id" to event.aggregateId.toString(),
|
||||
"test_context" to "event_capture",
|
||||
"handler_type" to "test_handler"
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
eventPublisher.registerHandler("TurnierCreated", testHandler)
|
||||
eventPublisher.registerHandler("TurnierUpdated", testHandler)
|
||||
eventPublisher.registerHandler("TurnierDeleted", testHandler)
|
||||
|
||||
// Register the actual handlers
|
||||
val auditHandler = TurnierAuditHandler()
|
||||
val notificationHandler = TurnierNotificationHandler()
|
||||
val analyticsHandler = TurnierAnalyticsHandler()
|
||||
val cacheHandler = TurnierCacheHandler()
|
||||
|
||||
eventPublisher.registerHandler("TurnierCreated", auditHandler)
|
||||
eventPublisher.registerHandler("TurnierUpdated", auditHandler)
|
||||
eventPublisher.registerHandler("TurnierDeleted", auditHandler)
|
||||
|
||||
eventPublisher.registerHandler("TurnierCreated", notificationHandler)
|
||||
eventPublisher.registerHandler("TurnierCreated", analyticsHandler)
|
||||
eventPublisher.registerHandler("TurnierCreated", cacheHandler)
|
||||
eventPublisher.registerHandler("TurnierUpdated", cacheHandler)
|
||||
eventPublisher.registerHandler("TurnierDeleted", cacheHandler)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test tournament creation publishes TurnierCreatedEvent`() = runBlocking {
|
||||
log.info("Starting test", mapOf(
|
||||
"test_name" to "tournament_creation",
|
||||
"test_phase" to "start",
|
||||
"test_type" to "event_driven_architecture"
|
||||
))
|
||||
|
||||
// Given
|
||||
val veranstaltungId = uuid4()
|
||||
val turnier = createTestTurnier(
|
||||
oepsTurnierNr = "TEST001",
|
||||
titel = "Test Tournament",
|
||||
untertitel = "Test Subtitle",
|
||||
veranstaltungId = veranstaltungId,
|
||||
datumVon = LocalDate(2024, 6, 1),
|
||||
datumBis = LocalDate(2024, 6, 3)
|
||||
)
|
||||
|
||||
// When
|
||||
val createdTurnier = turnierService.createTurnier(turnier)
|
||||
|
||||
// Give some time for async event processing
|
||||
delay(100)
|
||||
|
||||
// Then
|
||||
assertNotNull(createdTurnier)
|
||||
assertEquals("Test Tournament", createdTurnier.titel)
|
||||
|
||||
// Verify event was published
|
||||
val events = eventPublisher.getAllEvents()
|
||||
assertTrue(events.isNotEmpty(), "Events should be published")
|
||||
|
||||
val createdEvent = events.find { it.eventType == "TurnierCreated" }
|
||||
assertNotNull(createdEvent, "TurnierCreatedEvent should be published")
|
||||
assertTrue(createdEvent is TurnierCreatedEvent)
|
||||
|
||||
val typedEvent = createdEvent as TurnierCreatedEvent
|
||||
assertEquals(createdTurnier.id, typedEvent.aggregateId)
|
||||
assertEquals("Test Tournament", typedEvent.turnier.titel)
|
||||
|
||||
// Verify test handler captured the event
|
||||
assertTrue(capturedEvents.any { it.eventType == "TurnierCreated" })
|
||||
|
||||
log.info("Test completed", mapOf(
|
||||
"test_name" to "tournament_creation",
|
||||
"test_phase" to "complete",
|
||||
"test_result" to "success",
|
||||
"test_type" to "event_driven_architecture"
|
||||
))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test tournament update publishes TurnierUpdatedEvent`() = runBlocking {
|
||||
log.info("Starting test", mapOf(
|
||||
"test_name" to "tournament_update",
|
||||
"test_phase" to "start",
|
||||
"test_type" to "event_driven_architecture"
|
||||
))
|
||||
|
||||
// Given - create a tournament first
|
||||
val veranstaltungId = uuid4()
|
||||
val originalTurnier = createTestTurnier(
|
||||
oepsTurnierNr = "TEST002",
|
||||
titel = "Original Title",
|
||||
untertitel = "Original Subtitle",
|
||||
veranstaltungId = veranstaltungId,
|
||||
datumVon = LocalDate(2024, 7, 1),
|
||||
datumBis = LocalDate(2024, 7, 3)
|
||||
)
|
||||
|
||||
val createdTurnier = turnierService.createTurnier(originalTurnier)
|
||||
capturedEvents.clear() // Clear creation events
|
||||
|
||||
// When - update the tournament
|
||||
val updatedTurnier = createdTurnier.copy(titel = "Updated Title")
|
||||
val result = turnierService.updateTurnier(createdTurnier.id, updatedTurnier)
|
||||
|
||||
// Give some time for async event processing
|
||||
delay(100)
|
||||
|
||||
// Then
|
||||
assertNotNull(result)
|
||||
assertEquals("Updated Title", result.titel)
|
||||
|
||||
// Verify update event was published
|
||||
val updateEvents = eventPublisher.getEventsByType("TurnierUpdated")
|
||||
assertTrue(updateEvents.isNotEmpty(), "TurnierUpdatedEvent should be published")
|
||||
|
||||
val updateEvent = updateEvents.first() as TurnierUpdatedEvent
|
||||
assertEquals(createdTurnier.id, updateEvent.aggregateId)
|
||||
assertEquals("Original Title", updateEvent.previousTurnier.titel)
|
||||
assertEquals("Updated Title", updateEvent.updatedTurnier.titel)
|
||||
|
||||
// Verify test handler captured the event
|
||||
assertTrue(capturedEvents.any { it.eventType == "TurnierUpdated" })
|
||||
|
||||
log.info("Test completed", mapOf(
|
||||
"test_name" to "tournament_update",
|
||||
"test_phase" to "complete",
|
||||
"test_result" to "success",
|
||||
"test_type" to "event_driven_architecture"
|
||||
))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test tournament deletion publishes TurnierDeletedEvent`() = runBlocking {
|
||||
log.info("Starting test", mapOf(
|
||||
"test_name" to "tournament_deletion",
|
||||
"test_phase" to "start",
|
||||
"test_type" to "event_driven_architecture"
|
||||
))
|
||||
|
||||
// Given - create a tournament first
|
||||
val veranstaltungId = uuid4()
|
||||
val turnier = createTestTurnier(
|
||||
oepsTurnierNr = "TEST003",
|
||||
titel = "Tournament to Delete",
|
||||
untertitel = "Will be deleted",
|
||||
veranstaltungId = veranstaltungId,
|
||||
datumVon = LocalDate(2024, 8, 1),
|
||||
datumBis = LocalDate(2024, 8, 3)
|
||||
)
|
||||
|
||||
val createdTurnier = turnierService.createTurnier(turnier)
|
||||
capturedEvents.clear() // Clear creation events
|
||||
|
||||
// When - delete the tournament
|
||||
val deleted = turnierService.deleteTurnier(createdTurnier.id)
|
||||
|
||||
// Give some time for async event processing
|
||||
delay(100)
|
||||
|
||||
// Then
|
||||
assertTrue(deleted, "Tournament should be deleted")
|
||||
|
||||
// Verify delete event was published
|
||||
val deleteEvents = eventPublisher.getEventsByType("TurnierDeleted")
|
||||
assertTrue(deleteEvents.isNotEmpty(), "TurnierDeletedEvent should be published")
|
||||
|
||||
val deleteEvent = deleteEvents.first() as TurnierDeletedEvent
|
||||
assertEquals(createdTurnier.id, deleteEvent.aggregateId)
|
||||
assertEquals("Tournament to Delete", deleteEvent.deletedTurnier.titel)
|
||||
|
||||
// Verify test handler captured the event
|
||||
assertTrue(capturedEvents.any { it.eventType == "TurnierDeleted" })
|
||||
|
||||
log.info("Test completed", mapOf(
|
||||
"test_name" to "tournament_deletion",
|
||||
"test_phase" to "complete",
|
||||
"test_result" to "success",
|
||||
"test_type" to "event_driven_architecture"
|
||||
))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test event store functionality`() = runBlocking {
|
||||
log.info("Starting test", mapOf(
|
||||
"test_name" to "event_store",
|
||||
"test_phase" to "start",
|
||||
"test_type" to "event_driven_architecture"
|
||||
))
|
||||
|
||||
// Given
|
||||
val veranstaltungId = uuid4()
|
||||
val turnier = createTestTurnier(
|
||||
oepsTurnierNr = "TEST004",
|
||||
titel = "Event Store Test",
|
||||
untertitel = "Testing event store",
|
||||
veranstaltungId = veranstaltungId,
|
||||
datumVon = LocalDate(2024, 9, 1),
|
||||
datumBis = LocalDate(2024, 9, 3)
|
||||
)
|
||||
|
||||
// When - perform multiple operations
|
||||
val createdTurnier = turnierService.createTurnier(turnier)
|
||||
val updatedTurnier = turnierService.updateTurnier(createdTurnier.id, createdTurnier.copy(titel = "Updated Title"))
|
||||
turnierService.deleteTurnier(createdTurnier.id)
|
||||
|
||||
// Give some time for async event processing
|
||||
delay(100)
|
||||
|
||||
// Then - verify event store contains all events
|
||||
val allEvents = eventPublisher.getAllEvents()
|
||||
assertTrue(allEvents.size >= 3, "Should have at least 3 events (create, update, delete)")
|
||||
|
||||
val aggregateEvents = eventPublisher.getEventsByAggregateId(createdTurnier.id)
|
||||
assertEquals(3, aggregateEvents.size, "Should have exactly 3 events for this aggregate")
|
||||
|
||||
val eventTypes = aggregateEvents.map { it.eventType }.toSet()
|
||||
assertTrue(eventTypes.contains("TurnierCreated"))
|
||||
assertTrue(eventTypes.contains("TurnierUpdated"))
|
||||
assertTrue(eventTypes.contains("TurnierDeleted"))
|
||||
|
||||
log.info("Test completed", mapOf(
|
||||
"test_name" to "event_store",
|
||||
"test_phase" to "complete",
|
||||
"test_result" to "success",
|
||||
"test_type" to "event_driven_architecture"
|
||||
))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test multiple event handlers process same event`() = runBlocking {
|
||||
log.info("Starting test", mapOf(
|
||||
"test_name" to "multiple_handlers",
|
||||
"test_phase" to "start",
|
||||
"test_type" to "event_driven_architecture"
|
||||
))
|
||||
|
||||
// Given
|
||||
val veranstaltungId = uuid4()
|
||||
val turnier = createTestTurnier(
|
||||
oepsTurnierNr = "TEST005",
|
||||
titel = "Multi Handler Test",
|
||||
untertitel = "Testing multiple handlers",
|
||||
veranstaltungId = veranstaltungId,
|
||||
datumVon = LocalDate(2024, 10, 1),
|
||||
datumBis = LocalDate(2024, 10, 3)
|
||||
)
|
||||
|
||||
// When
|
||||
turnierService.createTurnier(turnier)
|
||||
|
||||
// Give some time for async event processing
|
||||
delay(200)
|
||||
|
||||
// Then - verify that multiple handlers processed the same event
|
||||
// This is verified by the fact that we registered multiple handlers
|
||||
// (audit, notification, analytics, cache) for the same event type
|
||||
// and they should all process the event without interfering with each other
|
||||
|
||||
val createdEvents = eventPublisher.getEventsByType("TurnierCreated")
|
||||
assertTrue(createdEvents.isNotEmpty(), "TurnierCreatedEvent should be published")
|
||||
|
||||
// The test handler should have captured the event
|
||||
assertTrue(capturedEvents.any { it.eventType == "TurnierCreated" })
|
||||
|
||||
log.info("Test completed", mapOf(
|
||||
"test_name" to "multiple_handlers",
|
||||
"test_phase" to "complete",
|
||||
"test_result" to "success",
|
||||
"test_type" to "event_driven_architecture"
|
||||
))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,347 @@
|
||||
package at.mocode
|
||||
|
||||
import at.mocode.model.Artikel
|
||||
import at.mocode.model.Platz
|
||||
import at.mocode.enums.PlatzTypE
|
||||
import com.benasher44.uuid.uuid4
|
||||
import com.ionspin.kotlin.bignum.decimal.BigDecimal
|
||||
import io.ktor.client.request.*
|
||||
import io.ktor.client.statement.*
|
||||
import io.ktor.http.*
|
||||
import io.ktor.server.testing.*
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.datetime.Clock
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlin.test.*
|
||||
|
||||
/**
|
||||
* Integration tests that verify the complete application stack:
|
||||
* Routes -> Services -> Repositories -> Database
|
||||
*
|
||||
* These tests ensure that all layers work together correctly
|
||||
* and provide end-to-end functionality verification.
|
||||
*/
|
||||
class IntegrationTest {
|
||||
|
||||
companion object {
|
||||
init {
|
||||
// Set test environment property for database configuration
|
||||
System.setProperty("isTestEnvironment", "true")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testApplicationStartupAndBasicEndpoints() = testApplication {
|
||||
application {
|
||||
module()
|
||||
}
|
||||
|
||||
// Test health endpoint
|
||||
client.get("/health").apply {
|
||||
assertEquals(HttpStatusCode.OK, status)
|
||||
assertEquals("OK", bodyAsText())
|
||||
}
|
||||
|
||||
// Test API info endpoint
|
||||
client.get("/api").apply {
|
||||
assertEquals(HttpStatusCode.OK, status)
|
||||
val responseText = bodyAsText()
|
||||
assertTrue(responseText.contains("Meldestelle API Server"))
|
||||
assertTrue(responseText.contains("v1.0.0"))
|
||||
}
|
||||
|
||||
// Test root endpoint serves HTML
|
||||
client.get("/").apply {
|
||||
assertEquals(HttpStatusCode.OK, status)
|
||||
val responseText = bodyAsText()
|
||||
assertTrue(responseText.contains("<!DOCTYPE html>"))
|
||||
assertTrue(responseText.contains("Meldestelle"))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSwaggerDocumentationEndpoints() = testApplication {
|
||||
application {
|
||||
module()
|
||||
}
|
||||
|
||||
// Test Swagger UI endpoint
|
||||
client.get("/swagger").apply {
|
||||
assertEquals(HttpStatusCode.OK, status)
|
||||
assertTrue(bodyAsText().contains("swagger", ignoreCase = true))
|
||||
}
|
||||
|
||||
// Test OpenAPI endpoint
|
||||
client.get("/openapi").apply {
|
||||
assertEquals(HttpStatusCode.OK, status)
|
||||
val content = bodyAsText()
|
||||
assertTrue(content.isNotEmpty())
|
||||
assertTrue(content.contains("openapi") || content.contains("swagger"))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testArtikelEndpointsIntegration() = testApplication {
|
||||
application {
|
||||
module()
|
||||
}
|
||||
|
||||
// Test GET /api/artikel endpoint
|
||||
client.get("/api/artikel").apply {
|
||||
assertEquals(HttpStatusCode.OK, status)
|
||||
// Should return valid JSON response
|
||||
val responseText = bodyAsText()
|
||||
assertTrue(responseText.isNotEmpty())
|
||||
assertTrue(responseText.contains("{") || responseText.startsWith("["))
|
||||
}
|
||||
|
||||
// Test GET /api/artikel/verbandsabgabe/true endpoint
|
||||
client.get("/api/artikel/verbandsabgabe/true").apply {
|
||||
assertEquals(HttpStatusCode.OK, status)
|
||||
val responseText = bodyAsText()
|
||||
assertTrue(responseText.isNotEmpty())
|
||||
assertTrue(responseText.contains("{") || responseText.startsWith("["))
|
||||
}
|
||||
|
||||
// Test GET /api/artikel/verbandsabgabe/false endpoint
|
||||
client.get("/api/artikel/verbandsabgabe/false").apply {
|
||||
assertEquals(HttpStatusCode.OK, status)
|
||||
val responseText = bodyAsText()
|
||||
assertTrue(responseText.isNotEmpty())
|
||||
assertTrue(responseText.contains("{") || responseText.startsWith("["))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testPlatzEndpointsIntegration() = testApplication {
|
||||
application {
|
||||
module()
|
||||
}
|
||||
|
||||
// Test GET /api/plaetze endpoint
|
||||
client.get("/api/plaetze").apply {
|
||||
assertEquals(HttpStatusCode.OK, status)
|
||||
val responseText = bodyAsText()
|
||||
assertTrue(responseText.isNotEmpty())
|
||||
assertTrue(responseText.contains("{") || responseText.startsWith("["))
|
||||
}
|
||||
|
||||
// Test GET /api/plaetze/typ/{typ} endpoint
|
||||
client.get("/api/plaetze/typ/AUSTRAGUNG").apply {
|
||||
assertEquals(HttpStatusCode.OK, status)
|
||||
val responseText = bodyAsText()
|
||||
assertTrue(responseText.isNotEmpty())
|
||||
assertTrue(responseText.contains("{") || responseText.startsWith("["))
|
||||
}
|
||||
|
||||
// Test invalid typ parameter
|
||||
client.get("/api/plaetze/typ/INVALID_TYPE").apply {
|
||||
assertEquals(HttpStatusCode.BadRequest, status)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testErrorHandling() = testApplication {
|
||||
application {
|
||||
module()
|
||||
}
|
||||
|
||||
// Test 404 for non-existent endpoint
|
||||
client.get("/api/nonexistent").apply {
|
||||
assertEquals(HttpStatusCode.NotFound, status)
|
||||
}
|
||||
|
||||
// Test 404 for non-existent artikel
|
||||
val nonExistentId = uuid4()
|
||||
client.get("/api/artikel/$nonExistentId").apply {
|
||||
assertEquals(HttpStatusCode.NotFound, status)
|
||||
}
|
||||
|
||||
// Test 404 for non-existent platz
|
||||
client.get("/api/plaetze/$nonExistentId").apply {
|
||||
assertEquals(HttpStatusCode.NotFound, status)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSearchEndpoints() = testApplication {
|
||||
application {
|
||||
module()
|
||||
}
|
||||
|
||||
// Test artikel search with valid query
|
||||
client.get("/api/artikel/search?q=test").apply {
|
||||
assertEquals(HttpStatusCode.OK, status)
|
||||
val responseText = bodyAsText()
|
||||
assertTrue(responseText.isNotEmpty())
|
||||
assertTrue(responseText.contains("{") || responseText.startsWith("["))
|
||||
}
|
||||
|
||||
// Test artikel search with empty query (should return 400)
|
||||
client.get("/api/artikel/search?q=").apply {
|
||||
assertEquals(HttpStatusCode.BadRequest, status)
|
||||
}
|
||||
|
||||
// Test plaetze search with valid query
|
||||
client.get("/api/plaetze/search?q=test").apply {
|
||||
assertEquals(HttpStatusCode.OK, status)
|
||||
val responseText = bodyAsText()
|
||||
assertTrue(responseText.isNotEmpty())
|
||||
assertTrue(responseText.contains("{") || responseText.startsWith("["))
|
||||
}
|
||||
|
||||
// Test plaetze search with empty query (should return 400)
|
||||
client.get("/api/plaetze/search?q=").apply {
|
||||
assertEquals(HttpStatusCode.BadRequest, status)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCorsHeaders() = testApplication {
|
||||
application {
|
||||
module()
|
||||
}
|
||||
|
||||
// Test CORS headers are present
|
||||
client.get("/api/artikel") {
|
||||
header(HttpHeaders.Origin, "http://localhost:3000")
|
||||
}.apply {
|
||||
assertEquals(HttpStatusCode.OK, status)
|
||||
assertNotNull(headers[HttpHeaders.AccessControlAllowOrigin])
|
||||
}
|
||||
|
||||
// Test OPTIONS request for CORS preflight
|
||||
client.options("/api/artikel") {
|
||||
header(HttpHeaders.Origin, "http://localhost:3000")
|
||||
header(HttpHeaders.AccessControlRequestMethod, "GET")
|
||||
}.apply {
|
||||
// Should handle OPTIONS request properly
|
||||
assertTrue(status.isSuccess() || status == HttpStatusCode.NotFound)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testContentNegotiation() = testApplication {
|
||||
application {
|
||||
module()
|
||||
}
|
||||
|
||||
// Test JSON content type
|
||||
client.get("/api/artikel") {
|
||||
header(HttpHeaders.Accept, "application/json")
|
||||
}.apply {
|
||||
assertEquals(HttpStatusCode.OK, status)
|
||||
assertEquals(ContentType.Application.Json.withCharset(Charsets.UTF_8), contentType())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testVersioningIntegration() = testApplication {
|
||||
application {
|
||||
module()
|
||||
}
|
||||
|
||||
// Test version validation endpoint (if it exists)
|
||||
client.get("/api/version").apply {
|
||||
// This endpoint might not exist, so we just check it doesn't crash
|
||||
assertTrue(status == HttpStatusCode.OK || status == HttpStatusCode.NotFound)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDatabaseConnectionAndBasicOperations() = testApplication {
|
||||
application {
|
||||
module()
|
||||
}
|
||||
|
||||
// This test verifies that the database connection works
|
||||
// by testing endpoints that require database access
|
||||
|
||||
// Test that we can retrieve data (even if empty)
|
||||
client.get("/api/artikel").apply {
|
||||
assertEquals(HttpStatusCode.OK, status)
|
||||
// Should return valid JSON response
|
||||
val responseText = bodyAsText()
|
||||
assertTrue(responseText.isNotEmpty())
|
||||
assertTrue(responseText.contains("{") || responseText.startsWith("["))
|
||||
}
|
||||
|
||||
client.get("/api/plaetze").apply {
|
||||
assertEquals(HttpStatusCode.OK, status)
|
||||
// Should return valid JSON response
|
||||
val responseText = bodyAsText()
|
||||
assertTrue(responseText.isNotEmpty())
|
||||
assertTrue(responseText.contains("{") || responseText.startsWith("["))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testServiceLayerIntegration() = testApplication {
|
||||
application {
|
||||
module()
|
||||
}
|
||||
|
||||
// Test that service layer validation works through the API
|
||||
|
||||
// Test artikel search with blank query (should trigger service validation)
|
||||
client.get("/api/artikel/search?q= ").apply {
|
||||
assertEquals(HttpStatusCode.BadRequest, status)
|
||||
}
|
||||
|
||||
// Test plaetze search with blank query (should trigger service validation)
|
||||
client.get("/api/plaetze/search?q= ").apply {
|
||||
assertEquals(HttpStatusCode.BadRequest, status)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCompleteApplicationFlow() = testApplication {
|
||||
application {
|
||||
module()
|
||||
}
|
||||
|
||||
println("[DEBUG_LOG] Testing complete application flow...")
|
||||
|
||||
// 1. Test application startup
|
||||
client.get("/health").apply {
|
||||
assertEquals(HttpStatusCode.OK, status)
|
||||
println("[DEBUG_LOG] ✓ Application health check passed")
|
||||
}
|
||||
|
||||
// 2. Test API documentation
|
||||
client.get("/swagger").apply {
|
||||
assertEquals(HttpStatusCode.OK, status)
|
||||
println("[DEBUG_LOG] ✓ Swagger documentation accessible")
|
||||
}
|
||||
|
||||
// 3. Test data retrieval endpoints
|
||||
client.get("/api/artikel").apply {
|
||||
assertEquals(HttpStatusCode.OK, status)
|
||||
println("[DEBUG_LOG] ✓ Artikel endpoint accessible")
|
||||
}
|
||||
|
||||
client.get("/api/plaetze").apply {
|
||||
assertEquals(HttpStatusCode.OK, status)
|
||||
println("[DEBUG_LOG] ✓ Plaetze endpoint accessible")
|
||||
}
|
||||
|
||||
// 4. Test search functionality
|
||||
client.get("/api/artikel/search?q=test").apply {
|
||||
assertEquals(HttpStatusCode.OK, status)
|
||||
println("[DEBUG_LOG] ✓ Artikel search functionality working")
|
||||
}
|
||||
|
||||
client.get("/api/plaetze/search?q=test").apply {
|
||||
assertEquals(HttpStatusCode.OK, status)
|
||||
println("[DEBUG_LOG] ✓ Plaetze search functionality working")
|
||||
}
|
||||
|
||||
// 5. Test error handling
|
||||
client.get("/api/nonexistent").apply {
|
||||
assertEquals(HttpStatusCode.NotFound, status)
|
||||
println("[DEBUG_LOG] ✓ 404 error handling working")
|
||||
}
|
||||
|
||||
println("[DEBUG_LOG] ✅ Complete application flow test passed!")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,325 @@
|
||||
package at.mocode
|
||||
|
||||
import at.mocode.model.Platz
|
||||
import at.mocode.repositories.PlatzRepository
|
||||
import at.mocode.services.PlatzService
|
||||
import at.mocode.enums.PlatzTypE
|
||||
import com.benasher44.uuid.uuid4
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlin.test.*
|
||||
|
||||
class PlatzServiceTest {
|
||||
|
||||
private lateinit var mockRepository: MockPlatzRepository
|
||||
private lateinit var platzService: PlatzService
|
||||
|
||||
@BeforeTest
|
||||
fun setup() {
|
||||
mockRepository = MockPlatzRepository()
|
||||
platzService = PlatzService(mockRepository)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testGetAllPlaetze() = runBlocking {
|
||||
// Given
|
||||
val platz1 = createTestPlatz("Platz 1")
|
||||
val platz2 = createTestPlatz("Platz 2")
|
||||
mockRepository.plaetze = mutableListOf(platz1, platz2)
|
||||
|
||||
// When
|
||||
val result = platzService.getAllPlaetze()
|
||||
|
||||
// Then
|
||||
assertEquals(2, result.size)
|
||||
assertTrue(result.contains(platz1))
|
||||
assertTrue(result.contains(platz2))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testGetPlatzById() = runBlocking {
|
||||
// Given
|
||||
val platz = createTestPlatz("Test Platz")
|
||||
mockRepository.plaetze = mutableListOf(platz)
|
||||
|
||||
// When
|
||||
val result = platzService.getPlatzById(platz.id)
|
||||
|
||||
// Then
|
||||
assertNotNull(result)
|
||||
assertEquals(platz.id, result.id)
|
||||
assertEquals(platz.name, result.name)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testGetPlatzByIdNotFound() = runBlocking {
|
||||
// Given
|
||||
val nonExistentId = uuid4()
|
||||
|
||||
// When
|
||||
val result = platzService.getPlatzById(nonExistentId)
|
||||
|
||||
// Then
|
||||
assertNull(result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testGetPlaetzeByTurnierId() = runBlocking {
|
||||
// Given
|
||||
val turnierId1 = uuid4()
|
||||
val turnierId2 = uuid4()
|
||||
val platz1 = createTestPlatz("Platz 1", turnierId = turnierId1)
|
||||
val platz2 = createTestPlatz("Platz 2", turnierId = turnierId1)
|
||||
val platz3 = createTestPlatz("Platz 3", turnierId = turnierId2)
|
||||
mockRepository.plaetze = mutableListOf(platz1, platz2, platz3)
|
||||
|
||||
// When
|
||||
val result = platzService.getPlaetzeByTurnierId(turnierId1)
|
||||
|
||||
// Then
|
||||
assertEquals(2, result.size)
|
||||
assertTrue(result.all { it.turnierId == turnierId1 })
|
||||
assertTrue(result.contains(platz1))
|
||||
assertTrue(result.contains(platz2))
|
||||
assertFalse(result.contains(platz3))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testGetPlaetzeByTyp() = runBlocking {
|
||||
// Given
|
||||
val platz1 = createTestPlatz("Austragung Platz", typ = PlatzTypE.AUSTRAGUNG)
|
||||
val platz2 = createTestPlatz("Vorbereitung Platz", typ = PlatzTypE.VORBEREITUNG)
|
||||
val platz3 = createTestPlatz("Another Austragung", typ = PlatzTypE.AUSTRAGUNG)
|
||||
mockRepository.plaetze = mutableListOf(platz1, platz2, platz3)
|
||||
|
||||
// When
|
||||
val austragungResult = platzService.getPlaetzeByTyp(PlatzTypE.AUSTRAGUNG)
|
||||
val vorbereitungResult = platzService.getPlaetzeByTyp(PlatzTypE.VORBEREITUNG)
|
||||
|
||||
// Then
|
||||
assertEquals(2, austragungResult.size)
|
||||
assertTrue(austragungResult.all { it.typ == PlatzTypE.AUSTRAGUNG })
|
||||
|
||||
assertEquals(1, vorbereitungResult.size)
|
||||
assertEquals(platz2.id, vorbereitungResult[0].id)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSearchPlaetze() = runBlocking {
|
||||
// Given
|
||||
val platz1 = createTestPlatz("Hauptplatz")
|
||||
val platz2 = createTestPlatz("Nebenplatz")
|
||||
val platz3 = createTestPlatz("Trainingsplatz")
|
||||
mockRepository.plaetze = mutableListOf(platz1, platz2, platz3)
|
||||
|
||||
// When
|
||||
val result = platzService.searchPlaetze("Haupt")
|
||||
|
||||
// Then
|
||||
assertEquals(1, result.size)
|
||||
assertEquals(platz1.id, result[0].id)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSearchPlaetzeBlankQuery() {
|
||||
// When & Then
|
||||
assertFailsWith<IllegalArgumentException> {
|
||||
runBlocking { platzService.searchPlaetze("") }
|
||||
}
|
||||
|
||||
assertFailsWith<IllegalArgumentException> {
|
||||
runBlocking { platzService.searchPlaetze(" ") }
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCreatePlatz() = runBlocking {
|
||||
// Given
|
||||
val platz = createTestPlatz("New Platz")
|
||||
|
||||
// When
|
||||
val result = platzService.createPlatz(platz)
|
||||
|
||||
// Then
|
||||
assertEquals(platz.name, result.name)
|
||||
assertEquals(platz.turnierId, result.turnierId)
|
||||
assertEquals(platz.typ, result.typ)
|
||||
assertTrue(mockRepository.plaetze.contains(result))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCreatePlatzValidation() {
|
||||
// Test blank name
|
||||
assertFailsWith<IllegalArgumentException> {
|
||||
runBlocking { platzService.createPlatz(createTestPlatz("")) }
|
||||
}
|
||||
|
||||
// Test long name
|
||||
assertFailsWith<IllegalArgumentException> {
|
||||
runBlocking { platzService.createPlatz(createTestPlatz("a".repeat(101))) }
|
||||
}
|
||||
|
||||
// Test long dimension
|
||||
assertFailsWith<IllegalArgumentException> {
|
||||
runBlocking {
|
||||
platzService.createPlatz(createTestPlatz("Test", dimension = "a".repeat(51)))
|
||||
}
|
||||
}
|
||||
|
||||
// Test long boden
|
||||
assertFailsWith<IllegalArgumentException> {
|
||||
runBlocking {
|
||||
platzService.createPlatz(createTestPlatz("Test", boden = "a".repeat(101)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testUpdatePlatz() = runBlocking {
|
||||
// Given
|
||||
val originalPlatz = createTestPlatz("Original")
|
||||
mockRepository.plaetze = mutableListOf(originalPlatz)
|
||||
|
||||
val updatedPlatz = originalPlatz.copy(name = "Updated")
|
||||
|
||||
// When
|
||||
val result = platzService.updatePlatz(originalPlatz.id, updatedPlatz)
|
||||
|
||||
// Then
|
||||
assertNotNull(result)
|
||||
assertEquals("Updated", result.name)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testUpdatePlatzNotFound() = runBlocking {
|
||||
// Given
|
||||
val nonExistentId = uuid4()
|
||||
val platz = createTestPlatz("Test")
|
||||
|
||||
// When
|
||||
val result = platzService.updatePlatz(nonExistentId, platz)
|
||||
|
||||
// Then
|
||||
assertNull(result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testUpdatePlatzValidation() {
|
||||
// Given
|
||||
val originalPlatz = createTestPlatz("Original")
|
||||
mockRepository.plaetze = mutableListOf(originalPlatz)
|
||||
|
||||
// Test validation during update
|
||||
assertFailsWith<IllegalArgumentException> {
|
||||
runBlocking { platzService.updatePlatz(originalPlatz.id, createTestPlatz("")) }
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDeletePlatz() = runBlocking {
|
||||
// Given
|
||||
val platz = createTestPlatz("To Delete")
|
||||
mockRepository.plaetze = mutableListOf(platz)
|
||||
|
||||
// When
|
||||
val result = platzService.deletePlatz(platz.id)
|
||||
|
||||
// Then
|
||||
assertTrue(result)
|
||||
assertFalse(mockRepository.plaetze.contains(platz))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDeletePlatzNotFound() = runBlocking {
|
||||
// Given
|
||||
val nonExistentId = uuid4()
|
||||
|
||||
// When
|
||||
val result = platzService.deletePlatz(nonExistentId)
|
||||
|
||||
// Then
|
||||
assertFalse(result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testValidationWithOptionalFields() = runBlocking {
|
||||
// Test valid platz with all optional fields
|
||||
val platzWithOptionals = createTestPlatz(
|
||||
name = "Test Platz",
|
||||
dimension = "20x40m",
|
||||
boden = "Sand"
|
||||
)
|
||||
|
||||
// Should not throw exception
|
||||
val result = platzService.createPlatz(platzWithOptionals)
|
||||
assertEquals("Test Platz", result.name)
|
||||
assertEquals("20x40m", result.dimension)
|
||||
assertEquals("Sand", result.boden)
|
||||
|
||||
// Test valid platz without optional fields
|
||||
val platzWithoutOptionals = createTestPlatz(
|
||||
name = "Simple Platz",
|
||||
dimension = null,
|
||||
boden = null
|
||||
)
|
||||
|
||||
// Should not throw exception
|
||||
val result2 = platzService.createPlatz(platzWithoutOptionals)
|
||||
assertEquals("Simple Platz", result2.name)
|
||||
assertNull(result2.dimension)
|
||||
assertNull(result2.boden)
|
||||
}
|
||||
|
||||
// Helper function to create test places
|
||||
private fun createTestPlatz(
|
||||
name: String,
|
||||
turnierId: com.benasher44.uuid.Uuid = uuid4(),
|
||||
dimension: String? = "20x40m",
|
||||
boden: String? = "Sand",
|
||||
typ: PlatzTypE = PlatzTypE.AUSTRAGUNG
|
||||
): Platz {
|
||||
return Platz(
|
||||
id = uuid4(),
|
||||
turnierId = turnierId,
|
||||
name = name,
|
||||
dimension = dimension,
|
||||
boden = boden,
|
||||
typ = typ
|
||||
)
|
||||
}
|
||||
|
||||
// Mock repository implementation for testing
|
||||
private class MockPlatzRepository : PlatzRepository {
|
||||
var plaetze = mutableListOf<Platz>()
|
||||
|
||||
override suspend fun findAll(): List<Platz> = plaetze
|
||||
|
||||
override suspend fun findById(id: com.benasher44.uuid.Uuid): Platz? =
|
||||
plaetze.find { it.id == id }
|
||||
|
||||
override suspend fun findByTurnierId(turnierId: com.benasher44.uuid.Uuid): List<Platz> =
|
||||
plaetze.filter { it.turnierId == turnierId }
|
||||
|
||||
override suspend fun findByTyp(typ: PlatzTypE): List<Platz> =
|
||||
plaetze.filter { it.typ == typ }
|
||||
|
||||
override suspend fun search(query: String): List<Platz> =
|
||||
plaetze.filter { it.name.contains(query, ignoreCase = true) }
|
||||
|
||||
override suspend fun create(platz: Platz): Platz {
|
||||
plaetze.add(platz)
|
||||
return platz
|
||||
}
|
||||
|
||||
override suspend fun update(id: com.benasher44.uuid.Uuid, platz: Platz): Platz? {
|
||||
val index = plaetze.indexOfFirst { it.id == id }
|
||||
return if (index >= 0) {
|
||||
plaetze[index] = platz.copy(id = id)
|
||||
plaetze[index]
|
||||
} else null
|
||||
}
|
||||
|
||||
override suspend fun delete(id: com.benasher44.uuid.Uuid): Boolean {
|
||||
return plaetze.removeIf { it.id == id }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,555 @@
|
||||
package at.mocode
|
||||
|
||||
import at.mocode.utils.*
|
||||
import com.benasher44.uuid.uuid4
|
||||
import io.ktor.client.request.*
|
||||
import io.ktor.client.statement.*
|
||||
import io.ktor.http.*
|
||||
import io.ktor.serialization.kotlinx.json.*
|
||||
import io.ktor.server.application.*
|
||||
import io.ktor.server.plugins.contentnegotiation.*
|
||||
import io.ktor.server.response.*
|
||||
import io.ktor.server.testing.*
|
||||
import io.ktor.server.routing.*
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlin.test.*
|
||||
|
||||
/**
|
||||
* Comprehensive test suite for RouteUtils utility functions.
|
||||
*
|
||||
* This test class verifies:
|
||||
* - UUID parameter extraction and validation
|
||||
* - String parameter extraction and validation
|
||||
* - Integer parameter extraction and validation
|
||||
* - Query parameter extraction and validation
|
||||
* - Safe execution with error handling
|
||||
* - Response utility functions
|
||||
* - Generic handler functions
|
||||
* - Proper HTTP status codes and error messages
|
||||
*/
|
||||
class RouteUtilsTest {
|
||||
|
||||
@Test
|
||||
fun testGetUuidParameterValid() = testApplication {
|
||||
application {
|
||||
install(ContentNegotiation) {
|
||||
json(Json {
|
||||
prettyPrint = true
|
||||
isLenient = true
|
||||
ignoreUnknownKeys = true
|
||||
})
|
||||
}
|
||||
routing {
|
||||
get("/test/{id}") {
|
||||
val uuid = call.getUuidParameter("id")
|
||||
if (uuid != null) {
|
||||
call.respond(HttpStatusCode.OK, mapOf("uuid" to uuid.toString()))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val testUuid = uuid4()
|
||||
client.get("/test/$testUuid").apply {
|
||||
assertEquals(HttpStatusCode.OK, status)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testGetUuidParameterInvalid() = testApplication {
|
||||
application {
|
||||
install(ContentNegotiation) {
|
||||
json(Json {
|
||||
prettyPrint = true
|
||||
isLenient = true
|
||||
ignoreUnknownKeys = true
|
||||
})
|
||||
}
|
||||
routing {
|
||||
get("/test/{id}") {
|
||||
val uuid = call.getUuidParameter("id")
|
||||
if (uuid != null) {
|
||||
call.respond(HttpStatusCode.OK, mapOf("uuid" to uuid.toString()))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
client.get("/test/invalid-uuid").apply {
|
||||
assertEquals(HttpStatusCode.BadRequest, status)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testGetUuidParameterMissing() = testApplication {
|
||||
application {
|
||||
install(ContentNegotiation) {
|
||||
json(Json {
|
||||
prettyPrint = true
|
||||
isLenient = true
|
||||
ignoreUnknownKeys = true
|
||||
})
|
||||
}
|
||||
routing {
|
||||
get("/test") {
|
||||
val uuid = call.getUuidParameter("id")
|
||||
if (uuid != null) {
|
||||
call.respond(HttpStatusCode.OK, mapOf("uuid" to uuid.toString()))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
client.get("/test").apply {
|
||||
assertEquals(HttpStatusCode.BadRequest, status)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testGetStringParameterValid() = testApplication {
|
||||
application {
|
||||
install(ContentNegotiation) {
|
||||
json(Json {
|
||||
prettyPrint = true
|
||||
isLenient = true
|
||||
ignoreUnknownKeys = true
|
||||
})
|
||||
}
|
||||
routing {
|
||||
get("/test/{name}") {
|
||||
val name = call.getStringParameter("name")
|
||||
if (name != null) {
|
||||
call.respond(HttpStatusCode.OK, mapOf("name" to name))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
client.get("/test/testname").apply {
|
||||
assertEquals(HttpStatusCode.OK, status)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testGetStringParameterMissing() = testApplication {
|
||||
application {
|
||||
install(ContentNegotiation) {
|
||||
json(Json {
|
||||
prettyPrint = true
|
||||
isLenient = true
|
||||
ignoreUnknownKeys = true
|
||||
})
|
||||
}
|
||||
routing {
|
||||
get("/test") {
|
||||
val name = call.getStringParameter("name")
|
||||
if (name != null) {
|
||||
call.respond(HttpStatusCode.OK, mapOf("name" to name))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
client.get("/test").apply {
|
||||
assertEquals(HttpStatusCode.BadRequest, status)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testGetIntParameterValid() = testApplication {
|
||||
application {
|
||||
install(ContentNegotiation) {
|
||||
json(Json {
|
||||
prettyPrint = true
|
||||
isLenient = true
|
||||
ignoreUnknownKeys = true
|
||||
})
|
||||
}
|
||||
routing {
|
||||
get("/test/{count}") {
|
||||
val count = call.getIntParameter("count")
|
||||
if (count != null) {
|
||||
call.respond(HttpStatusCode.OK, mapOf("count" to count))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
client.get("/test/42").apply {
|
||||
assertEquals(HttpStatusCode.OK, status)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testGetIntParameterInvalid() = testApplication {
|
||||
application {
|
||||
install(ContentNegotiation) {
|
||||
json(Json {
|
||||
prettyPrint = true
|
||||
isLenient = true
|
||||
ignoreUnknownKeys = true
|
||||
})
|
||||
}
|
||||
routing {
|
||||
get("/test/{count}") {
|
||||
val count = call.getIntParameter("count")
|
||||
if (count != null) {
|
||||
call.respond(HttpStatusCode.OK, mapOf("count" to count))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
client.get("/test/not-a-number").apply {
|
||||
assertEquals(HttpStatusCode.BadRequest, status)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testGetIntParameterMissing() = testApplication {
|
||||
application {
|
||||
install(ContentNegotiation) {
|
||||
json(Json {
|
||||
prettyPrint = true
|
||||
isLenient = true
|
||||
ignoreUnknownKeys = true
|
||||
})
|
||||
}
|
||||
routing {
|
||||
get("/test") {
|
||||
val count = call.getIntParameter("count")
|
||||
if (count != null) {
|
||||
call.respond(HttpStatusCode.OK, mapOf("count" to count))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
client.get("/test").apply {
|
||||
assertEquals(HttpStatusCode.BadRequest, status)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testGetQueryParameterValid() = testApplication {
|
||||
application {
|
||||
install(ContentNegotiation) {
|
||||
json(Json {
|
||||
prettyPrint = true
|
||||
isLenient = true
|
||||
ignoreUnknownKeys = true
|
||||
})
|
||||
}
|
||||
routing {
|
||||
get("/test") {
|
||||
val query = call.getQueryParameter("q")
|
||||
if (query != null) {
|
||||
call.respond(HttpStatusCode.OK, mapOf("query" to query))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
client.get("/test?q=searchterm").apply {
|
||||
assertEquals(HttpStatusCode.OK, status)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testGetQueryParameterMissing() = testApplication {
|
||||
application {
|
||||
install(ContentNegotiation) {
|
||||
json(Json {
|
||||
prettyPrint = true
|
||||
isLenient = true
|
||||
ignoreUnknownKeys = true
|
||||
})
|
||||
}
|
||||
routing {
|
||||
get("/test") {
|
||||
val query = call.getQueryParameter("q")
|
||||
if (query != null) {
|
||||
call.respond(HttpStatusCode.OK, mapOf("query" to query))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
client.get("/test").apply {
|
||||
assertEquals(HttpStatusCode.BadRequest, status)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSafeExecuteSuccess() = testApplication {
|
||||
application {
|
||||
install(ContentNegotiation) {
|
||||
json(Json {
|
||||
prettyPrint = true
|
||||
isLenient = true
|
||||
ignoreUnknownKeys = true
|
||||
})
|
||||
}
|
||||
routing {
|
||||
get("/test") {
|
||||
call.safeExecute {
|
||||
call.respond(HttpStatusCode.OK, "Success")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
client.get("/test").apply {
|
||||
assertEquals(HttpStatusCode.OK, status)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSafeExecuteIllegalArgumentException() = testApplication {
|
||||
application {
|
||||
install(ContentNegotiation) {
|
||||
json(Json {
|
||||
prettyPrint = true
|
||||
isLenient = true
|
||||
ignoreUnknownKeys = true
|
||||
})
|
||||
}
|
||||
routing {
|
||||
get("/test") {
|
||||
call.safeExecute {
|
||||
throw IllegalArgumentException("Invalid argument")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
client.get("/test").apply {
|
||||
assertEquals(HttpStatusCode.BadRequest, status)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSafeExecuteGenericException() = testApplication {
|
||||
application {
|
||||
install(ContentNegotiation) {
|
||||
json(Json {
|
||||
prettyPrint = true
|
||||
isLenient = true
|
||||
ignoreUnknownKeys = true
|
||||
})
|
||||
}
|
||||
routing {
|
||||
get("/test") {
|
||||
call.safeExecute {
|
||||
throw RuntimeException("Something went wrong")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
client.get("/test").apply {
|
||||
assertEquals(HttpStatusCode.InternalServerError, status)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testRespondWithEntityOrNotFoundWithEntity() = testApplication {
|
||||
application {
|
||||
install(ContentNegotiation) {
|
||||
json(Json {
|
||||
prettyPrint = true
|
||||
isLenient = true
|
||||
ignoreUnknownKeys = true
|
||||
})
|
||||
}
|
||||
routing {
|
||||
get("/test") {
|
||||
val entity = mapOf("id" to "1", "name" to "Test")
|
||||
call.respondWithEntityOrNotFound(entity)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
client.get("/test").apply {
|
||||
assertEquals(HttpStatusCode.OK, status)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testRespondWithEntityOrNotFoundWithNull() = testApplication {
|
||||
application {
|
||||
install(ContentNegotiation) {
|
||||
json(Json {
|
||||
prettyPrint = true
|
||||
isLenient = true
|
||||
ignoreUnknownKeys = true
|
||||
})
|
||||
}
|
||||
routing {
|
||||
get("/test") {
|
||||
val entity: Map<String, Any>? = null
|
||||
call.respondWithEntityOrNotFound(entity)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
client.get("/test").apply {
|
||||
assertEquals(HttpStatusCode.NotFound, status)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testRespondWithList() = testApplication {
|
||||
application {
|
||||
install(ContentNegotiation) {
|
||||
json(Json {
|
||||
prettyPrint = true
|
||||
isLenient = true
|
||||
ignoreUnknownKeys = true
|
||||
})
|
||||
}
|
||||
routing {
|
||||
get("/test") {
|
||||
val entities = listOf(
|
||||
mapOf("id" to "1", "name" to "Test1"),
|
||||
mapOf("id" to "2", "name" to "Test2")
|
||||
)
|
||||
call.respondWithList(entities)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
client.get("/test").apply {
|
||||
assertEquals(HttpStatusCode.OK, status)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testHandleFindById() = testApplication {
|
||||
application {
|
||||
install(ContentNegotiation) {
|
||||
json(Json {
|
||||
prettyPrint = true
|
||||
isLenient = true
|
||||
ignoreUnknownKeys = true
|
||||
})
|
||||
}
|
||||
routing {
|
||||
get("/test/{id}") {
|
||||
call.handleFindById<Map<String, Any>> { id ->
|
||||
mapOf("id" to id.toString(), "name" to "Test Entity")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val testUuid = uuid4()
|
||||
client.get("/test/$testUuid").apply {
|
||||
assertEquals(HttpStatusCode.OK, status)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testHandleFindByIdNotFound() = testApplication {
|
||||
application {
|
||||
install(ContentNegotiation) {
|
||||
json(Json {
|
||||
prettyPrint = true
|
||||
isLenient = true
|
||||
ignoreUnknownKeys = true
|
||||
})
|
||||
}
|
||||
routing {
|
||||
get("/test/{id}") {
|
||||
call.handleFindById<Map<String, Any>> { _ ->
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val testUuid = uuid4()
|
||||
client.get("/test/$testUuid").apply {
|
||||
assertEquals(HttpStatusCode.NotFound, status)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testHandleFindByStringParam() = testApplication {
|
||||
application {
|
||||
install(ContentNegotiation) {
|
||||
json(Json {
|
||||
prettyPrint = true
|
||||
isLenient = true
|
||||
ignoreUnknownKeys = true
|
||||
})
|
||||
}
|
||||
routing {
|
||||
get("/test/{name}") {
|
||||
call.handleFindByStringParam<Map<String, String>>("name") { name ->
|
||||
mapOf("name" to name, "found" to "true")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
client.get("/test/testname").apply {
|
||||
assertEquals(HttpStatusCode.OK, status)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testHandleFindByUuidParamList() = testApplication {
|
||||
application {
|
||||
install(ContentNegotiation) {
|
||||
json(Json {
|
||||
prettyPrint = true
|
||||
isLenient = true
|
||||
ignoreUnknownKeys = true
|
||||
})
|
||||
}
|
||||
routing {
|
||||
get("/test/{id}/items") {
|
||||
call.handleFindByUuidParamList<Map<String, String>>("id") { id ->
|
||||
listOf(
|
||||
mapOf("id" to "1", "parentId" to id.toString()),
|
||||
mapOf("id" to "2", "parentId" to id.toString())
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val testUuid = uuid4()
|
||||
client.get("/test/$testUuid/items").apply {
|
||||
assertEquals(HttpStatusCode.OK, status)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testHandleFindByStringParamList() = testApplication {
|
||||
application {
|
||||
install(ContentNegotiation) {
|
||||
json(Json {
|
||||
prettyPrint = true
|
||||
isLenient = true
|
||||
ignoreUnknownKeys = true
|
||||
})
|
||||
}
|
||||
routing {
|
||||
get("/test/{category}/items") {
|
||||
call.handleFindByStringParamList<Map<String, String>>("category") { category ->
|
||||
listOf(
|
||||
mapOf("id" to "1", "category" to category),
|
||||
mapOf("id" to "2", "category" to category)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
client.get("/test/electronics/items").apply {
|
||||
assertEquals(HttpStatusCode.OK, status)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,425 @@
|
||||
package at.mocode
|
||||
|
||||
import at.mocode.model.Turnier
|
||||
import at.mocode.repositories.TurnierRepository
|
||||
import at.mocode.services.TurnierService
|
||||
import com.benasher44.uuid.uuid4
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.datetime.LocalDate
|
||||
import kotlin.test.*
|
||||
|
||||
/**
|
||||
* Comprehensive test suite for TurnierService business logic.
|
||||
*
|
||||
* This test class verifies:
|
||||
* - CRUD operations (create, read, update, delete)
|
||||
* - Search functionality
|
||||
* - Business validation rules
|
||||
* - Error handling and edge cases
|
||||
* - Transaction management behavior
|
||||
* - Duplicate checking logic
|
||||
*/
|
||||
class TurnierServiceTest {
|
||||
|
||||
private lateinit var mockRepository: MockTurnierRepository
|
||||
private lateinit var turnierService: TurnierService
|
||||
|
||||
@BeforeTest
|
||||
fun setup() {
|
||||
mockRepository = MockTurnierRepository()
|
||||
turnierService = TurnierService(mockRepository)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testGetAllTurniere() = runBlocking {
|
||||
// Given
|
||||
val turnier1 = createTestTurnier("Tournament 1", "T001")
|
||||
val turnier2 = createTestTurnier("Tournament 2", "T002")
|
||||
mockRepository.turniere.addAll(listOf(turnier1, turnier2))
|
||||
|
||||
// When
|
||||
val result = turnierService.getAllTurniere()
|
||||
|
||||
// Then
|
||||
assertEquals(2, result.size)
|
||||
assertTrue(result.contains(turnier1))
|
||||
assertTrue(result.contains(turnier2))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testGetTurnierById() = runBlocking {
|
||||
// Given
|
||||
val turnier = createTestTurnier("Test Tournament", "T001")
|
||||
mockRepository.turniere.add(turnier)
|
||||
|
||||
// When
|
||||
val result = turnierService.getTurnierById(turnier.id)
|
||||
|
||||
// Then
|
||||
assertNotNull(result)
|
||||
assertEquals(turnier.id, result.id)
|
||||
assertEquals("Test Tournament", result.titel)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testGetTurnierByIdNotFound() = runBlocking {
|
||||
// Given
|
||||
val nonExistentId = uuid4()
|
||||
|
||||
// When
|
||||
val result = turnierService.getTurnierById(nonExistentId)
|
||||
|
||||
// Then
|
||||
assertNull(result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testGetTurniereByVeranstaltungId() = runBlocking {
|
||||
// Given
|
||||
val veranstaltungId = uuid4()
|
||||
val turnier1 = createTestTurnier("Tournament 1", "T001", veranstaltungId)
|
||||
val turnier2 = createTestTurnier("Tournament 2", "T002", veranstaltungId)
|
||||
val turnier3 = createTestTurnier("Tournament 3", "T003", uuid4()) // Different event
|
||||
mockRepository.turniere.addAll(listOf(turnier1, turnier2, turnier3))
|
||||
|
||||
// When
|
||||
val result = turnierService.getTurniereByVeranstaltungId(veranstaltungId)
|
||||
|
||||
// Then
|
||||
assertEquals(2, result.size)
|
||||
assertTrue(result.all { it.veranstaltungId == veranstaltungId })
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testGetTurnierByOepsTurnierNr() = runBlocking {
|
||||
// Given
|
||||
val turnier = createTestTurnier("Test Tournament", "T001")
|
||||
mockRepository.turniere.add(turnier)
|
||||
|
||||
// When
|
||||
val result = turnierService.getTurnierByOepsTurnierNr("T001")
|
||||
|
||||
// Then
|
||||
assertNotNull(result)
|
||||
assertEquals("T001", result.oepsTurnierNr)
|
||||
assertEquals("Test Tournament", result.titel)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testGetTurnierByOepsTurnierNrNotFound() = runBlocking {
|
||||
// When
|
||||
val result = turnierService.getTurnierByOepsTurnierNr("NONEXISTENT")
|
||||
|
||||
// Then
|
||||
assertNull(result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testGetTurnierByOepsTurnierNrBlank() {
|
||||
runBlocking {
|
||||
// When & Then
|
||||
assertFailsWith<IllegalArgumentException> {
|
||||
turnierService.getTurnierByOepsTurnierNr("")
|
||||
}
|
||||
|
||||
assertFailsWith<IllegalArgumentException> {
|
||||
turnierService.getTurnierByOepsTurnierNr(" ")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSearchTurniere() = runBlocking {
|
||||
// Given
|
||||
val turnier1 = createTestTurnier("Spring Tournament", "T001")
|
||||
val turnier2 = createTestTurnier("Summer Championship", "T002")
|
||||
val turnier3 = createTestTurnier("Winter Cup", "T003")
|
||||
mockRepository.turniere.addAll(listOf(turnier1, turnier2, turnier3))
|
||||
|
||||
// When
|
||||
val result = turnierService.searchTurniere("Tournament")
|
||||
|
||||
// Then
|
||||
assertEquals(1, result.size)
|
||||
assertEquals("Spring Tournament", result[0].titel)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSearchTurniereBlankQuery() {
|
||||
runBlocking {
|
||||
// When & Then
|
||||
assertFailsWith<IllegalArgumentException> {
|
||||
turnierService.searchTurniere("")
|
||||
}
|
||||
|
||||
assertFailsWith<IllegalArgumentException> {
|
||||
turnierService.searchTurniere(" ")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCreateTurnier() = runBlocking {
|
||||
// Given
|
||||
val turnier = createTestTurnier("New Tournament", "T001")
|
||||
|
||||
// When
|
||||
val result = turnierService.createTurnier(turnier)
|
||||
|
||||
// Then
|
||||
assertNotNull(result)
|
||||
assertEquals("New Tournament", result.titel)
|
||||
assertEquals("T001", result.oepsTurnierNr)
|
||||
assertTrue(mockRepository.turniere.contains(result))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCreateTurnierValidation() {
|
||||
runBlocking {
|
||||
// Test blank title
|
||||
assertFailsWith<IllegalArgumentException> {
|
||||
turnierService.createTurnier(createTestTurnier("", "T001"))
|
||||
}
|
||||
|
||||
// Test title too long
|
||||
val longTitle = "a".repeat(256)
|
||||
assertFailsWith<IllegalArgumentException> {
|
||||
turnierService.createTurnier(createTestTurnier(longTitle, "T001"))
|
||||
}
|
||||
|
||||
// Test invalid date range
|
||||
assertFailsWith<IllegalArgumentException> {
|
||||
turnierService.createTurnier(
|
||||
createTestTurnier(
|
||||
"Test Tournament",
|
||||
"T001",
|
||||
datumVon = LocalDate(2024, 12, 31),
|
||||
datumBis = LocalDate(2024, 1, 1)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
// Test blank OEPS number
|
||||
assertFailsWith<IllegalArgumentException> {
|
||||
turnierService.createTurnier(createTestTurnier("Test Tournament", ""))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCreateTurnierDuplicateOepsNr() {
|
||||
runBlocking {
|
||||
// Given
|
||||
val existingTurnier = createTestTurnier("Existing Tournament", "T001")
|
||||
mockRepository.turniere.add(existingTurnier)
|
||||
|
||||
// When & Then
|
||||
assertFailsWith<IllegalArgumentException> {
|
||||
turnierService.createTurnier(createTestTurnier("New Tournament", "T001"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testUpdateTurnier() = runBlocking {
|
||||
// Given
|
||||
val originalTurnier = createTestTurnier("Original Tournament", "T001")
|
||||
mockRepository.turniere.add(originalTurnier)
|
||||
val updatedTurnier = originalTurnier.copy(titel = "Updated Tournament")
|
||||
|
||||
// When
|
||||
val result = turnierService.updateTurnier(originalTurnier.id, updatedTurnier)
|
||||
|
||||
// Then
|
||||
assertNotNull(result)
|
||||
assertEquals("Updated Tournament", result.titel)
|
||||
assertEquals("T001", result.oepsTurnierNr)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testUpdateTurnierNotFound() = runBlocking {
|
||||
// Given
|
||||
val nonExistentId = uuid4()
|
||||
val turnier = createTestTurnier("Test Tournament", "T001")
|
||||
|
||||
// When
|
||||
val result = turnierService.updateTurnier(nonExistentId, turnier)
|
||||
|
||||
// Then
|
||||
assertNull(result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testUpdateTurnierValidation() {
|
||||
runBlocking {
|
||||
// Given
|
||||
val originalTurnier = createTestTurnier("Original Tournament", "T001")
|
||||
mockRepository.turniere.add(originalTurnier)
|
||||
|
||||
// Test blank title
|
||||
assertFailsWith<IllegalArgumentException> {
|
||||
turnierService.updateTurnier(originalTurnier.id, originalTurnier.copy(titel = ""))
|
||||
}
|
||||
|
||||
// Test title too long
|
||||
val longTitle = "a".repeat(256)
|
||||
assertFailsWith<IllegalArgumentException> {
|
||||
turnierService.updateTurnier(originalTurnier.id, originalTurnier.copy(titel = longTitle))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testUpdateTurnierDuplicateOepsNr() {
|
||||
runBlocking {
|
||||
// Given
|
||||
val turnier1 = createTestTurnier("Tournament 1", "T001")
|
||||
val turnier2 = createTestTurnier("Tournament 2", "T002")
|
||||
mockRepository.turniere.addAll(listOf(turnier1, turnier2))
|
||||
|
||||
// When & Then - Try to update turnier2 with turnier1's OEPS number
|
||||
assertFailsWith<IllegalArgumentException> {
|
||||
turnierService.updateTurnier(turnier2.id, turnier2.copy(oepsTurnierNr = "T001"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testUpdateTurnierSameOepsNr() = runBlocking {
|
||||
// Given - Should allow updating with the same OEPS number
|
||||
val turnier = createTestTurnier("Tournament", "T001")
|
||||
mockRepository.turniere.add(turnier)
|
||||
|
||||
// When
|
||||
val result = turnierService.updateTurnier(turnier.id, turnier.copy(titel = "Updated Tournament"))
|
||||
|
||||
// Then
|
||||
assertNotNull(result)
|
||||
assertEquals("Updated Tournament", result.titel)
|
||||
assertEquals("T001", result.oepsTurnierNr)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDeleteTurnier() = runBlocking {
|
||||
// Given
|
||||
val turnier = createTestTurnier("Test Tournament", "T001")
|
||||
mockRepository.turniere.add(turnier)
|
||||
|
||||
// When
|
||||
val result = turnierService.deleteTurnier(turnier.id)
|
||||
|
||||
// Then
|
||||
assertTrue(result)
|
||||
assertFalse(mockRepository.turniere.contains(turnier))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDeleteTurnierNotFound() = runBlocking {
|
||||
// Given
|
||||
val nonExistentId = uuid4()
|
||||
|
||||
// When
|
||||
val result = turnierService.deleteTurnier(nonExistentId)
|
||||
|
||||
// Then
|
||||
assertFalse(result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testGetTurniereForEvent() = runBlocking {
|
||||
// Given
|
||||
val veranstaltungId = uuid4()
|
||||
val turnier1 = createTestTurnier("Tournament 1", "T001", veranstaltungId)
|
||||
val turnier2 = createTestTurnier("Tournament 2", "T002", veranstaltungId)
|
||||
mockRepository.turniere.addAll(listOf(turnier1, turnier2))
|
||||
|
||||
// When
|
||||
val result = turnierService.getTurniereForEvent(veranstaltungId)
|
||||
|
||||
// Then
|
||||
assertEquals(2, result.size)
|
||||
assertTrue(result.all { it.veranstaltungId == veranstaltungId })
|
||||
}
|
||||
|
||||
private fun createTestTurnier(
|
||||
titel: String,
|
||||
oepsTurnierNr: String,
|
||||
veranstaltungId: com.benasher44.uuid.Uuid = uuid4(),
|
||||
datumVon: LocalDate = LocalDate(2024, 6, 1),
|
||||
datumBis: LocalDate = LocalDate(2024, 6, 3)
|
||||
): Turnier {
|
||||
return Turnier(
|
||||
id = uuid4(),
|
||||
veranstaltungId = veranstaltungId,
|
||||
oepsTurnierNr = oepsTurnierNr,
|
||||
titel = titel,
|
||||
untertitel = null,
|
||||
datumVon = datumVon,
|
||||
datumBis = datumBis,
|
||||
nennungsschluss = null,
|
||||
nennungsArt = emptyList(),
|
||||
nennungsHinweis = null,
|
||||
eigenesNennsystemUrl = null,
|
||||
nenngeld = null,
|
||||
startgeldStandard = null,
|
||||
austragungsplaetze = emptyList(),
|
||||
vorbereitungsplaetze = emptyList(),
|
||||
turnierleiterId = null,
|
||||
turnierbeauftragterId = null,
|
||||
richterIds = emptyList(),
|
||||
parcoursbauerIds = emptyList(),
|
||||
parcoursAssistentIds = emptyList(),
|
||||
tierarztInfos = null,
|
||||
hufschmiedInfo = null,
|
||||
meldestelleVerantwortlicherId = null,
|
||||
meldestelleTelefon = null,
|
||||
meldestelleOeffnungszeiten = null,
|
||||
ergebnislistenUrl = null,
|
||||
verfuegbareArtikel = emptyList(),
|
||||
meisterschaftRefs = emptyList(),
|
||||
createdAt = kotlinx.datetime.Clock.System.now(),
|
||||
updatedAt = kotlinx.datetime.Clock.System.now()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Mock implementation of TurnierRepository for testing
|
||||
*/
|
||||
class MockTurnierRepository : TurnierRepository {
|
||||
val turniere = mutableListOf<Turnier>()
|
||||
|
||||
override suspend fun findAll(): List<Turnier> = turniere.toList()
|
||||
|
||||
override suspend fun findById(id: com.benasher44.uuid.Uuid): Turnier? =
|
||||
turniere.find { it.id == id }
|
||||
|
||||
override suspend fun findByVeranstaltungId(veranstaltungId: com.benasher44.uuid.Uuid): List<Turnier> =
|
||||
turniere.filter { it.veranstaltungId == veranstaltungId }
|
||||
|
||||
override suspend fun findByOepsTurnierNr(oepsTurnierNr: String): Turnier? =
|
||||
turniere.find { it.oepsTurnierNr == oepsTurnierNr }
|
||||
|
||||
override suspend fun search(query: String): List<Turnier> =
|
||||
turniere.filter { it.titel.contains(query, ignoreCase = true) }
|
||||
|
||||
override suspend fun create(turnier: Turnier): Turnier {
|
||||
turniere.add(turnier)
|
||||
return turnier
|
||||
}
|
||||
|
||||
override suspend fun update(id: com.benasher44.uuid.Uuid, turnier: Turnier): Turnier? {
|
||||
val index = turniere.indexOfFirst { it.id == id }
|
||||
return if (index >= 0) {
|
||||
val updated = turnier.copy(id = id)
|
||||
turniere[index] = updated
|
||||
updated
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun delete(id: com.benasher44.uuid.Uuid): Boolean {
|
||||
return turniere.removeIf { it.id == id }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,10 +17,15 @@ class VersioningTest {
|
||||
|
||||
@Test
|
||||
fun testVersionManagerValidation() {
|
||||
// Test valid version
|
||||
val validResult = VersionManager.validateClientVersion("1.0")
|
||||
// Test current version (1.1)
|
||||
val validResult = VersionManager.validateClientVersion("1.1")
|
||||
assertIs<VersionValidationResult.Valid>(validResult)
|
||||
assertEquals("1.0", validResult.version)
|
||||
assertEquals("1.1", validResult.version)
|
||||
|
||||
// Test the deprecated version (1.0)
|
||||
val deprecatedResult = VersionManager.validateClientVersion("1.0")
|
||||
assertIs<VersionValidationResult.DeprecatedVersion>(deprecatedResult)
|
||||
assertEquals("1.0", deprecatedResult.version)
|
||||
|
||||
// Test unsupported version
|
||||
val unsupportedResult = VersionManager.validateClientVersion("2.0")
|
||||
@@ -35,7 +40,8 @@ class VersioningTest {
|
||||
@Test
|
||||
fun testVersionManagerInfo() {
|
||||
val versionInfo = VersionManager.getVersionInfo()
|
||||
assertEquals("1.0", versionInfo.apiVersion)
|
||||
assertEquals("1.1", versionInfo.apiVersion)
|
||||
assertTrue(versionInfo.supportedVersions.contains("1.1"))
|
||||
assertTrue(versionInfo.supportedVersions.contains("1.0"))
|
||||
assertEquals("1.0", versionInfo.minimumClientVersion)
|
||||
}
|
||||
@@ -110,7 +116,9 @@ class VersioningTest {
|
||||
@Test
|
||||
fun testVersionSupport() {
|
||||
assertTrue(VersionManager.isVersionSupported("1.0"))
|
||||
assertTrue(VersionManager.isVersionSupported("1.1"))
|
||||
assertTrue(!VersionManager.isVersionSupported("2.0"))
|
||||
assertTrue(!VersionManager.isVersionDeprecated("1.0"))
|
||||
assertTrue(VersionManager.isVersionDeprecated("1.0"))
|
||||
assertTrue(!VersionManager.isVersionDeprecated("1.1"))
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user