Merge pull request #18

* MP-19 Refactoring: Einführung der "Registry" & "Masterdata" Trennung …

* MP-19 Refactoring: Frontend Tabula Rasa

* MP-19 Refactoring: Frontend Tabula Rasa

* refactoring:

* MP-20 fix(docker/clients): include `:domains` module in web/desktop b…

* MP-20 fix(web-app build): resolve JS compile error and add dev/prod b…

* MP-20 fix(web-app): remove vendor.js reference and harden JS bootstra…

* MP-20 fixing: clients

* MP-20 fixing: clients
This commit is contained in:
StefanMo
2025-11-30 14:13:12 +01:00
committed by GitHub
parent 596a05b69c
commit 9ea2b74a81
254 changed files with 5485 additions and 15971 deletions
@@ -4,7 +4,7 @@ import at.mocode.ping.api.PingApi
import at.mocode.ping.api.PingResponse
import at.mocode.ping.api.EnhancedPingResponse
import at.mocode.ping.api.HealthResponse
import at.mocode.clients.shared.AppConfig
import at.mocode.clients.shared.core.AppConstants
import io.ktor.client.*
import io.ktor.client.call.*
import io.ktor.client.plugins.contentnegotiation.*
@@ -13,30 +13,30 @@ import io.ktor.serialization.kotlinx.json.*
import kotlinx.serialization.json.Json
class PingApiClient(
private val baseUrl: String = AppConfig.GATEWAY_URL
private val baseUrl: String = AppConstants.GATEWAY_URL
) : PingApi {
private val client = HttpClient {
install(ContentNegotiation) {
json(Json {
prettyPrint = true
isLenient = true
ignoreUnknownKeys = true
})
}
private val client = HttpClient {
install(ContentNegotiation) {
json(Json {
prettyPrint = true
isLenient = true
ignoreUnknownKeys = true
})
}
}
override suspend fun simplePing(): PingResponse {
return client.get("$baseUrl/api/ping/simple").body()
}
override suspend fun simplePing(): PingResponse {
return client.get("$baseUrl/api/ping/simple").body()
}
override suspend fun enhancedPing(simulate: Boolean): EnhancedPingResponse {
return client.get("$baseUrl/api/ping/enhanced") {
parameter("simulate", simulate)
}.body()
}
override suspend fun enhancedPing(simulate: Boolean): EnhancedPingResponse {
return client.get("$baseUrl/api/ping/enhanced") {
parameter("simulate", simulate)
}.body()
}
override suspend fun healthCheck(): HealthResponse {
return client.get("$baseUrl/api/ping/health").body()
}
override suspend fun healthCheck(): HealthResponse {
return client.get("$baseUrl/api/ping/health").body()
}
}
@@ -21,288 +21,288 @@ import at.mocode.clients.pingfeature.model.RoleCategory
@Composable
fun PingScreen(viewModel: PingViewModel) {
val uiState = viewModel.uiState
val uiState = viewModel.uiState
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp)
.verticalScroll(rememberScrollState()),
verticalArrangement = Arrangement.spacedBy(16.dp)
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp)
.verticalScroll(rememberScrollState()),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
Text(
text = "Ping Service",
style = MaterialTheme.typography.headlineMedium,
fontWeight = FontWeight.Bold
)
// Action Buttons
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
Text(
text = "Ping Service",
style = MaterialTheme.typography.headlineMedium,
fontWeight = FontWeight.Bold
)
Button(
onClick = { viewModel.performSimplePing() },
enabled = !uiState.isLoading,
modifier = Modifier.weight(1f)
) {
Text("Simple Ping")
}
// Action Buttons
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
Button(
onClick = { viewModel.performSimplePing() },
enabled = !uiState.isLoading,
modifier = Modifier.weight(1f)
) {
Text("Simple Ping")
}
Button(
onClick = { viewModel.performEnhancedPing() },
enabled = !uiState.isLoading,
modifier = Modifier.weight(1f)
) {
Text("Enhanced Ping")
}
Button(
onClick = { viewModel.performEnhancedPing() },
enabled = !uiState.isLoading,
modifier = Modifier.weight(1f)
) {
Text("Enhanced Ping")
}
Button(
onClick = { viewModel.performHealthCheck() },
enabled = !uiState.isLoading,
modifier = Modifier.weight(1f)
) {
Text("Health Check")
}
}
// Loading indicator
if (uiState.isLoading) {
Box(
modifier = Modifier.fillMaxWidth(),
contentAlignment = Alignment.Center
) {
CircularProgressIndicator()
}
}
// Error message
uiState.errorMessage?.let { error ->
Card(
colors = CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.errorContainer
)
) {
Column(
modifier = Modifier.padding(16.dp)
) {
Text(
text = "Error",
style = MaterialTheme.typography.titleMedium,
color = MaterialTheme.colorScheme.onErrorContainer,
fontWeight = FontWeight.Bold
)
Spacer(modifier = Modifier.height(8.dp))
Text(
text = error,
color = MaterialTheme.colorScheme.onErrorContainer
)
Spacer(modifier = Modifier.height(8.dp))
Button(
onClick = { viewModel.clearError() }
) {
Text("Dismiss")
}
}
}
}
// Simple Ping Response
uiState.simplePingResponse?.let { response ->
ResponseCard(
title = "Simple Ping Response",
status = response.status,
timestamp = response.timestamp,
service = response.service
)
}
// Enhanced Ping Response
uiState.enhancedPingResponse?.let { response ->
ResponseCard(
title = "Enhanced Ping Response",
status = response.status,
timestamp = response.timestamp,
service = response.service,
additionalInfo = mapOf(
"Circuit Breaker State" to response.circuitBreakerState,
"Response Time" to "${response.responseTime}ms"
)
)
}
// Health Response
uiState.healthResponse?.let { response ->
ResponseCard(
title = "Health Check Response",
status = response.status,
timestamp = response.timestamp,
service = response.service,
additionalInfo = mapOf(
"Healthy" to response.healthy.toString()
)
)
}
// Neue Reitsport-Authentication-Sektion
Spacer(modifier = Modifier.height(24.dp))
ReitsportTestingSection(
viewModel = viewModel,
uiState = uiState
)
Button(
onClick = { viewModel.performHealthCheck() },
enabled = !uiState.isLoading,
modifier = Modifier.weight(1f)
) {
Text("Health Check")
}
}
// Loading indicator
if (uiState.isLoading) {
Box(
modifier = Modifier.fillMaxWidth(),
contentAlignment = Alignment.Center
) {
CircularProgressIndicator()
}
}
// Error message
uiState.errorMessage?.let { error ->
Card(
colors = CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.errorContainer
)
) {
Column(
modifier = Modifier.padding(16.dp)
) {
Text(
text = "Error",
style = MaterialTheme.typography.titleMedium,
color = MaterialTheme.colorScheme.onErrorContainer,
fontWeight = FontWeight.Bold
)
Spacer(modifier = Modifier.height(8.dp))
Text(
text = error,
color = MaterialTheme.colorScheme.onErrorContainer
)
Spacer(modifier = Modifier.height(8.dp))
Button(
onClick = { viewModel.clearError() }
) {
Text("Dismiss")
}
}
}
}
// Simple Ping Response
uiState.simplePingResponse?.let { response ->
ResponseCard(
title = "Simple Ping Response",
status = response.status,
timestamp = response.timestamp,
service = response.service
)
}
// Enhanced Ping Response
uiState.enhancedPingResponse?.let { response ->
ResponseCard(
title = "Enhanced Ping Response",
status = response.status,
timestamp = response.timestamp,
service = response.service,
additionalInfo = mapOf(
"Circuit Breaker State" to response.circuitBreakerState,
"Response Time" to "${response.responseTime}ms"
)
)
}
// Health Response
uiState.healthResponse?.let { response ->
ResponseCard(
title = "Health Check Response",
status = response.status,
timestamp = response.timestamp,
service = response.service,
additionalInfo = mapOf(
"Healthy" to response.healthy.toString()
)
)
}
// Neue Reitsport-Authentication-Sektion
Spacer(modifier = Modifier.height(24.dp))
ReitsportTestingSection(
viewModel = viewModel,
uiState = uiState
)
}
}
@Composable
private fun ResponseCard(
title: String,
status: String,
timestamp: String,
service: String,
additionalInfo: Map<String, String> = emptyMap()
title: String,
status: String,
timestamp: String,
service: String,
additionalInfo: Map<String, String> = emptyMap()
) {
Card(
modifier = Modifier.fillMaxWidth(),
colors = CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.surfaceVariant
)
Card(
modifier = Modifier.fillMaxWidth(),
colors = CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.surfaceVariant
)
) {
Column(
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
Column(
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
Text(
text = title,
style = MaterialTheme.typography.titleMedium,
fontWeight = FontWeight.Bold
)
Text(
text = title,
style = MaterialTheme.typography.titleMedium,
fontWeight = FontWeight.Bold
)
InfoRow("Status", status)
InfoRow("Timestamp", timestamp)
InfoRow("Service", service)
InfoRow("Status", status)
InfoRow("Timestamp", timestamp)
InfoRow("Service", service)
additionalInfo.forEach { (key, value) ->
InfoRow(key, value)
}
}
additionalInfo.forEach { (key, value) ->
InfoRow(key, value)
}
}
}
}
@Composable
private fun InfoRow(label: String, value: String) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween
) {
Text(
text = "$label:",
fontWeight = FontWeight.Medium
)
Text(text = value)
}
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween
) {
Text(
text = "$label:",
fontWeight = FontWeight.Medium
)
Text(text = value)
}
}
@Composable
private fun ReitsportTestingSection(
viewModel: PingViewModel,
uiState: PingUiState
viewModel: PingViewModel,
uiState: PingUiState
) {
Card(
modifier = Modifier.fillMaxWidth(),
colors = CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.secondaryContainer
)
Card(
modifier = Modifier.fillMaxWidth(),
colors = CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.secondaryContainer
)
) {
Column(
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
Column(
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
// Header
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = "🐎",
style = MaterialTheme.typography.headlineMedium
)
Spacer(modifier = Modifier.width(8.dp))
Text(
text = "Reitsport-Authentication-Testing",
style = MaterialTheme.typography.headlineSmall,
fontWeight = FontWeight.Bold
)
}
// Header
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = "🐎",
style = MaterialTheme.typography.headlineMedium
)
Spacer(modifier = Modifier.width(8.dp))
Text(
text = "Reitsport-Authentication-Testing",
style = MaterialTheme.typography.headlineSmall,
fontWeight = FontWeight.Bold
)
}
Text(
text = "Teste verschiedene Benutzerrollen und ihre Berechtigungen im Meldestelle_Pro System",
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSecondaryContainer.copy(alpha = 0.8f)
)
Text(
text = "Teste verschiedene Benutzerrollen und ihre Berechtigungen im Meldestelle_Pro System",
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSecondaryContainer.copy(alpha = 0.8f)
)
// Rollen-Grid
LazyVerticalGrid(
columns = GridCells.Adaptive(minSize = 120.dp),
verticalArrangement = Arrangement.spacedBy(8.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp),
modifier = Modifier.height(200.dp) // Feste Höhe für 2 Reihen
) {
items(ReitsportRoles.ALL_ROLES) { role ->
RoleTestButton(
role = role,
onClick = { viewModel.testReitsportRole(role) },
isLoading = uiState.isLoading
)
}
}
// Rollen-Grid
LazyVerticalGrid(
columns = GridCells.Adaptive(minSize = 120.dp),
verticalArrangement = Arrangement.spacedBy(8.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp),
modifier = Modifier.height(200.dp) // Feste Höhe für 2 Reihen
) {
items(ReitsportRoles.ALL_ROLES) { role ->
RoleTestButton(
role = role,
onClick = { viewModel.testReitsportRole(role) },
isLoading = uiState.isLoading
)
}
}
}
}
}
@Composable
private fun RoleTestButton(
role: ReitsportRole,
onClick: () -> Unit,
isLoading: Boolean
role: ReitsportRole,
onClick: () -> Unit,
isLoading: Boolean
) {
OutlinedButton(
onClick = onClick,
enabled = !isLoading,
modifier = Modifier
.fillMaxWidth()
.height(80.dp),
colors = ButtonDefaults.outlinedButtonColors(
containerColor = Color.Transparent,
contentColor = when (role.category) {
RoleCategory.SYSTEM -> Color(0xFFFF5722)
RoleCategory.OFFICIAL -> Color(0xFF3F51B5)
RoleCategory.ACTIVE -> Color(0xFF4CAF50)
RoleCategory.PASSIVE -> Color(0xFF9E9E9E)
}
)
OutlinedButton(
onClick = onClick,
enabled = !isLoading,
modifier = Modifier
.fillMaxWidth()
.height(80.dp),
colors = ButtonDefaults.outlinedButtonColors(
containerColor = Color.Transparent,
contentColor = when (role.category) {
RoleCategory.SYSTEM -> Color(0xFFFF5722)
RoleCategory.OFFICIAL -> Color(0xFF3F51B5)
RoleCategory.ACTIVE -> Color(0xFF4CAF50)
RoleCategory.PASSIVE -> Color(0xFF9E9E9E)
}
)
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text(
text = role.icon,
fontSize = 20.sp
)
Text(
text = role.displayName.split(" ").first(), // Erstes Wort nur
fontSize = 10.sp,
fontWeight = FontWeight.Medium,
textAlign = TextAlign.Center,
maxLines = 1
)
Text(
text = "${role.permissions.size} Rechte",
fontSize = 8.sp,
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f),
textAlign = TextAlign.Center
)
}
Text(
text = role.icon,
fontSize = 20.sp
)
Text(
text = role.displayName.split(" ").first(), // Erstes Wort nur
fontSize = 10.sp,
fontWeight = FontWeight.Medium,
textAlign = TextAlign.Center,
maxLines = 1
)
Text(
text = "${role.permissions.size} Rechte",
fontSize = 8.sp,
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f),
textAlign = TextAlign.Center
)
}
}
}
@@ -15,136 +15,136 @@ import at.mocode.ping.api.PingResponse
import kotlinx.coroutines.launch
data class PingUiState(
val isLoading: Boolean = false,
val simplePingResponse: PingResponse? = null,
val enhancedPingResponse: EnhancedPingResponse? = null,
val healthResponse: HealthResponse? = null,
val errorMessage: String? = null
val isLoading: Boolean = false,
val simplePingResponse: PingResponse? = null,
val enhancedPingResponse: EnhancedPingResponse? = null,
val healthResponse: HealthResponse? = null,
val errorMessage: String? = null
)
class PingViewModel(
private val apiClient: PingApi = PingApiClient()
private val apiClient: PingApi = PingApiClient()
) : ViewModel() {
var uiState by mutableStateOf(PingUiState())
private set
var uiState by mutableStateOf(PingUiState())
private set
fun performSimplePing() {
viewModelScope.launch {
uiState = uiState.copy(isLoading = true, errorMessage = null)
try {
val response = apiClient.simplePing()
uiState = uiState.copy(
isLoading = false,
simplePingResponse = response
)
} catch (e: Exception) {
uiState = uiState.copy(
isLoading = false,
errorMessage = "Simple ping failed: ${e.message}"
)
}
}
}
fun performEnhancedPing(simulate: Boolean = false) {
viewModelScope.launch {
uiState = uiState.copy(isLoading = true, errorMessage = null)
try {
val response = apiClient.enhancedPing(simulate)
uiState = uiState.copy(
isLoading = false,
enhancedPingResponse = response
)
} catch (e: Exception) {
uiState = uiState.copy(
isLoading = false,
errorMessage = "Enhanced ping failed: ${e.message}"
)
}
}
}
fun performHealthCheck() {
viewModelScope.launch {
uiState = uiState.copy(isLoading = true, errorMessage = null)
try {
val response = apiClient.healthCheck()
uiState = uiState.copy(
isLoading = false,
healthResponse = response
)
} catch (e: Exception) {
uiState = uiState.copy(
isLoading = false,
errorMessage = "Health check failed: ${e.message}"
)
}
}
}
fun clearError() {
uiState = uiState.copy(errorMessage = null)
}
/**
* Erweiterte Methode: Echte API-Tests für Reitsport-Rollen
*/
fun testReitsportRole(role: ReitsportRole) {
viewModelScope.launch {
uiState = uiState.copy(
isLoading = true,
errorMessage = null
)
try {
// Echte API-Tests durchführen
val apiClient = ReitsportTestApi()
val testResults = apiClient.testRole(role)
// Erfolgs-Statistiken berechnen
val successful = testResults.count { it.success }
val total = testResults.size
val successRate = if (total > 0) (successful * 100 / total) else 0
// Test-Summary erstellen
val summary = buildString {
appendLine("🎯 ${role.displayName} - Test Abgeschlossen")
appendLine("📊 Erfolgsrate: $successful/$total Tests ($successRate%)")
appendLine("⏱️ Durchschnittsdauer: ${testResults.map { it.duration }.average().toInt()}ms")
appendLine("🔑 Berechtigungen: ${role.permissions.size}")
appendLine("")
appendLine("📋 Test-Ergebnisse:")
testResults.forEach { result ->
val icon = if (result.success) "" else ""
val status = if (result.responseCode != null) " (${result.responseCode})" else ""
appendLine("$icon ${result.scenarioName}$status - ${result.duration}ms")
}
}
// Mock-Response für Anzeige
val mockResponse = PingResponse(
status = summary,
timestamp = DateTimeHelper.formatDateTime(DateTimeHelper.now()),
service = "Reitsport-Auth-Test"
)
uiState = uiState.copy(
isLoading = false,
simplePingResponse = mockResponse
)
println("[DEBUG] Reitsport-API-Test: ${role.displayName}")
println("[DEBUG] Ergebnisse: $successful/$total erfolgreich")
} catch (e: Exception) {
uiState = uiState.copy(
isLoading = false,
errorMessage = "Reitsport-API-Test fehlgeschlagen: ${e.message}"
)
println("[ERROR] Reitsport-Test-Fehler: ${e.message}")
}
fun performSimplePing() {
viewModelScope.launch {
uiState = uiState.copy(isLoading = true, errorMessage = null)
try {
val response = apiClient.simplePing()
uiState = uiState.copy(
isLoading = false,
simplePingResponse = response
)
} catch (e: Exception) {
uiState = uiState.copy(
isLoading = false,
errorMessage = "Simple ping failed: ${e.message}"
)
}
}
}
fun performEnhancedPing(simulate: Boolean = false) {
viewModelScope.launch {
uiState = uiState.copy(isLoading = true, errorMessage = null)
try {
val response = apiClient.enhancedPing(simulate)
uiState = uiState.copy(
isLoading = false,
enhancedPingResponse = response
)
} catch (e: Exception) {
uiState = uiState.copy(
isLoading = false,
errorMessage = "Enhanced ping failed: ${e.message}"
)
}
}
}
fun performHealthCheck() {
viewModelScope.launch {
uiState = uiState.copy(isLoading = true, errorMessage = null)
try {
val response = apiClient.healthCheck()
uiState = uiState.copy(
isLoading = false,
healthResponse = response
)
} catch (e: Exception) {
uiState = uiState.copy(
isLoading = false,
errorMessage = "Health check failed: ${e.message}"
)
}
}
}
fun clearError() {
uiState = uiState.copy(errorMessage = null)
}
/**
* Erweiterte Methode: Echte API-Tests für Reitsport-Rollen
*/
fun testReitsportRole(role: ReitsportRole) {
viewModelScope.launch {
uiState = uiState.copy(
isLoading = true,
errorMessage = null
)
try {
// Echte API-Tests durchführen
val apiClient = ReitsportTestApi()
val testResults = apiClient.testRole(role)
// Erfolgs-Statistiken berechnen
val successful = testResults.count { it.success }
val total = testResults.size
val successRate = if (total > 0) (successful * 100 / total) else 0
// Test-Summary erstellen
val summary = buildString {
appendLine("🎯 ${role.displayName} - Test Abgeschlossen")
appendLine("📊 Erfolgsrate: $successful/$total Tests ($successRate%)")
appendLine("⏱️ Durchschnittsdauer: ${testResults.map { it.duration }.average().toInt()}ms")
appendLine("🔑 Berechtigungen: ${role.permissions.size}")
appendLine("")
appendLine("📋 Test-Ergebnisse:")
testResults.forEach { result ->
val icon = if (result.success) "" else ""
val status = if (result.responseCode != null) " (${result.responseCode})" else ""
appendLine("$icon ${result.scenarioName}$status - ${result.duration}ms")
}
}
// Mock-Response für Anzeige
val mockResponse = PingResponse(
status = summary,
timestamp = DateTimeHelper.formatDateTime(DateTimeHelper.now()),
service = "Reitsport-Auth-Test"
)
uiState = uiState.copy(
isLoading = false,
simplePingResponse = mockResponse
)
println("[DEBUG] Reitsport-API-Test: ${role.displayName}")
println("[DEBUG] Ergebnisse: $successful/$total erfolgreich")
} catch (e: Exception) {
uiState = uiState.copy(
isLoading = false,
errorMessage = "Reitsport-API-Test fehlgeschlagen: ${e.message}"
)
println("[ERROR] Reitsport-Test-Fehler: ${e.message}")
}
}
}
}
@@ -8,254 +8,258 @@ import kotlinx.coroutines.delay
/**
* API-Client für Reitsport-Authentication-Testing
* Testet verschiedene Services mit rollenbasierten Tokens
* testet verschiedene Services mit rollenbasierten Tokens
*/
class ReitsportTestApi {
companion object {
// URLs der verfügbaren Services
private const val PING_SERVICE_URL = "http://localhost:8082"
private const val GATEWAY_URL = "http://localhost:8081"
companion object {
// URLs der verfügbaren Services
private const val PING_SERVICE_URL = "http://localhost:8082"
private const val GATEWAY_URL = "http://localhost:8081"
// Mock URLs für auskommentierte Services
private const val MEMBERS_SERVICE_URL = "http://localhost:8083" // Auskommentiert
private const val HORSES_SERVICE_URL = "http://localhost:8084" // Auskommentiert
private const val EVENTS_SERVICE_URL = "http://localhost:8085" // Auskommentiert
// Mock URLs für auskommentierte Services
private const val MEMBERS_SERVICE_URL = "http://localhost:8083" // Auskommentiert
private const val HORSES_SERVICE_URL = "http://localhost:8084" // Auskommentiert
private const val EVENTS_SERVICE_URL = "http://localhost:8085" // Auskommentiert
}
/**
* Teste eine Rolle gegen verfügbare Services
*/
suspend fun testRole(role: ReitsportRole): List<ApiTestResult> {
val results = mutableListOf<ApiTestResult>()
// 1. Test Ping-Service (immer verfügbar)
results.add(testPingService(role))
// 2. Test Gateway Health (immer verfügbar)
results.add(testGatewayHealth(role))
// 3. Test rollenspezifische Services
when (role.roleType) {
RolleE.ADMIN, RolleE.VEREINS_ADMIN -> {
results.add(testMembersService(role))
results.add(testSystemAccess(role))
}
RolleE.FUNKTIONAER -> {
results.add(testEventsService(role))
results.add(testMembersService(role))
}
RolleE.TIERARZT, RolleE.TRAINER -> {
results.add(testHorsesService(role))
}
RolleE.REITER -> {
results.add(testMembersService(role))
}
RolleE.RICHTER, RolleE.ZUSCHAUER, RolleE.GAST -> {
results.add(testPublicAccess(role))
}
}
/**
* Teste eine Rolle gegen verfügbare Services
*/
suspend fun testRole(role: ReitsportRole): List<ApiTestResult> {
val results = mutableListOf<ApiTestResult>()
return results
}
// 1. Test Ping-Service (immer verfügbar)
results.add(testPingService(role))
/**
* Test 1: Ping-Service (verfügbar)
*/
private suspend fun testPingService(role: ReitsportRole): ApiTestResult {
val startTime = DateTimeHelper.now()
// 2. Test Gateway Health (immer verfügbar)
results.add(testGatewayHealth(role))
return try {
// Simuliere HTTP-Call zum Ping-Service
delay(200)
// 3. Test rollenspezifische Services
when (role.roleType) {
RolleE.ADMIN, RolleE.VEREINS_ADMIN -> {
results.add(testMembersService(role))
results.add(testSystemAccess(role))
}
RolleE.FUNKTIONAER -> {
results.add(testEventsService(role))
results.add(testMembersService(role))
}
RolleE.TIERARZT, RolleE.TRAINER -> {
results.add(testHorsesService(role))
}
RolleE.REITER -> {
results.add(testMembersService(role))
}
RolleE.RICHTER, RolleE.ZUSCHAUER, RolleE.GAST -> {
results.add(testPublicAccess(role))
}
}
val duration = DateTimeHelper.now() - startTime
val endpoint = "$PING_SERVICE_URL/health"
return results
ApiTestResult(
scenarioId = "ping-health",
scenarioName = "Ping Service Health",
endpoint = endpoint,
method = "GET",
expectedResult = "Service erreichbar",
actualResult = "✅ Ping-Service läuft (HTTP 200)",
success = true,
responseCode = 200,
duration = duration,
token = generateMockToken(role),
responseData = """{"status":"pong","service":"ping-service","healthy":true}"""
)
} catch (e: Exception) {
ApiTestResult(
scenarioId = "ping-health",
scenarioName = "Ping Service Health",
endpoint = "$PING_SERVICE_URL/health",
method = "GET",
expectedResult = "Service erreichbar",
actualResult = "❌ Fehler: ${e.message}",
success = false,
duration = DateTimeHelper.now() - startTime,
errorMessage = e.message
)
}
}
/**
* Test 1: Ping-Service (verfügbar)
*/
private suspend fun testPingService(role: ReitsportRole): ApiTestResult {
val startTime = DateTimeHelper.now()
/**
* Test 2: Gateway Health (verfügbar)
*/
private suspend fun testGatewayHealth(role: ReitsportRole): ApiTestResult {
val startTime = DateTimeHelper.now()
return try {
// Simuliere HTTP-Call zum Ping-Service
delay(200)
return try {
delay(150)
val duration = DateTimeHelper.now() - startTime
val endpoint = "$PING_SERVICE_URL/health"
val duration = DateTimeHelper.now() - startTime
val endpoint = "$GATEWAY_URL/actuator/health"
ApiTestResult(
scenarioId = "ping-health",
scenarioName = "Ping Service Health",
endpoint = endpoint,
method = "GET",
expectedResult = "Service erreichbar",
actualResult = "Ping-Service läuft (HTTP 200)",
success = true,
responseCode = 200,
duration = duration,
token = generateMockToken(role),
responseData = """{"status":"pong","service":"ping-service","healthy":true}"""
)
} catch (e: Exception) {
ApiTestResult(
scenarioId = "ping-health",
scenarioName = "Ping Service Health",
endpoint = "$PING_SERVICE_URL/health",
method = "GET",
expectedResult = "Service erreichbar",
actualResult = "Fehler: ${e.message}",
success = false,
duration = DateTimeHelper.now() - startTime,
errorMessage = e.message
)
}
ApiTestResult(
scenarioId = "gateway-health",
scenarioName = "API Gateway Health",
endpoint = endpoint,
method = "GET",
expectedResult = "Gateway gesund",
actualResult = "Gateway erreichbar, Service Discovery aktiv",
success = true,
responseCode = 200,
duration = duration,
token = generateMockToken(role),
responseData = """{"status":"UP","components":{"consul":{"status":"UP"}}}"""
)
} catch (e: Exception) {
ApiTestResult(
scenarioId = "gateway-health",
scenarioName = "API Gateway Health",
endpoint = "$GATEWAY_URL/actuator/health",
method = "GET",
expectedResult = "Gateway gesund",
actualResult = "Gateway nicht erreichbar: ${e.message}",
success = false,
duration = DateTimeHelper.now() - startTime,
errorMessage = e.message
)
}
}
/**
* Test 2: Gateway Health (verfügbar)
*/
private suspend fun testGatewayHealth(role: ReitsportRole): ApiTestResult {
val startTime = DateTimeHelper.now()
/**
* Test 3: Members-Service (auskommentiert - Graceful Degradation)
*/
private suspend fun testMembersService(role: ReitsportRole): ApiTestResult {
val startTime = DateTimeHelper.now()
delay(100)
return try {
delay(150)
return ApiTestResult(
scenarioId = "members-unavailable",
scenarioName = "Members Service",
endpoint = "$MEMBERS_SERVICE_URL/api/members",
method = "GET",
expectedResult = "Mitglieder-Daten abrufen",
actualResult = "⚠️ Service temporär deaktiviert (in settings.gradle.kts auskommentiert)",
success = false,
responseCode = 503, // Service Unavailable
duration = DateTimeHelper.now() - startTime,
token = generateMockToken(role),
errorMessage = "Service ist in der aktuellen Konfiguration nicht verfügbar"
)
}
val duration = DateTimeHelper.now() - startTime
val endpoint = "$GATEWAY_URL/actuator/health"
/**
* Test 4: Horses-Service (auskommentiert)
*/
private suspend fun testHorsesService(role: ReitsportRole): ApiTestResult {
val startTime = DateTimeHelper.now()
delay(100)
ApiTestResult(
scenarioId = "gateway-health",
scenarioName = "API Gateway Health",
endpoint = endpoint,
method = "GET",
expectedResult = "Gateway gesund",
actualResult = "✅ Gateway erreichbar, Service Discovery aktiv",
success = true,
responseCode = 200,
duration = duration,
token = generateMockToken(role),
responseData = """{"status":"UP","components":{"consul":{"status":"UP"}}}"""
)
} catch (e: Exception) {
ApiTestResult(
scenarioId = "gateway-health",
scenarioName = "API Gateway Health",
endpoint = "$GATEWAY_URL/actuator/health",
method = "GET",
expectedResult = "Gateway gesund",
actualResult = "❌ Gateway nicht erreichbar: ${e.message}",
success = false,
duration = DateTimeHelper.now() - startTime,
errorMessage = e.message
)
}
}
return ApiTestResult(
scenarioId = "horses-unavailable",
scenarioName = "Horses Service",
endpoint = "$HORSES_SERVICE_URL/api/horses",
method = "GET",
expectedResult = "Pferde-Daten abrufen",
actualResult = "⚠️ Service temporär deaktiviert (in settings.gradle.kts auskommentiert)",
success = false,
responseCode = 503,
duration = DateTimeHelper.now() - startTime,
token = generateMockToken(role),
errorMessage = "Service wird später aktiviert"
)
}
/**
* Test 3: Members-Service (auskommentiert - Graceful Degradation)
*/
private suspend fun testMembersService(role: ReitsportRole): ApiTestResult {
val startTime = DateTimeHelper.now()
delay(100)
/**
* Test 5: Events-Service (auskommentiert)
*/
private suspend fun testEventsService(role: ReitsportRole): ApiTestResult {
val startTime = DateTimeHelper.now()
delay(100)
return ApiTestResult(
scenarioId = "members-unavailable",
scenarioName = "Members Service",
endpoint = "$MEMBERS_SERVICE_URL/api/members",
method = "GET",
expectedResult = "Mitglieder-Daten abrufen",
actualResult = "⚠️ Service temporär deaktiviert (in settings.gradle.kts auskommentiert)",
success = false,
responseCode = 503, // Service Unavailable
duration = DateTimeHelper.now() - startTime,
token = generateMockToken(role),
errorMessage = "Service ist in der aktuellen Konfiguration nicht verfügbar"
)
}
return ApiTestResult(
scenarioId = "events-unavailable",
scenarioName = "Events Service",
endpoint = "$EVENTS_SERVICE_URL/api/events",
method = "GET",
expectedResult = "Veranstaltungs-Daten abrufen",
actualResult = "⚠️ Service temporär deaktiviert (in settings.gradle.kts auskommentiert)",
success = false,
responseCode = 503,
duration = DateTimeHelper.now() - startTime,
token = generateMockToken(role),
errorMessage = "Service in Entwicklung"
)
}
/**
* Test 4: Horses-Service (auskommentiert)
*/
private suspend fun testHorsesService(role: ReitsportRole): ApiTestResult {
val startTime = DateTimeHelper.now()
delay(100)
/**
* Test 6: System-Zugriff (für Admins)
*/
private suspend fun testSystemAccess(role: ReitsportRole): ApiTestResult {
val startTime = DateTimeHelper.now()
delay(300)
return ApiTestResult(
scenarioId = "horses-unavailable",
scenarioName = "Horses Service",
endpoint = "$HORSES_SERVICE_URL/api/horses",
method = "GET",
expectedResult = "Pferde-Daten abrufen",
actualResult = "⚠️ Service temporär deaktiviert (in settings.gradle.kts auskommentiert)",
success = false,
responseCode = 503,
duration = DateTimeHelper.now() - startTime,
token = generateMockToken(role),
errorMessage = "Service wird später aktiviert"
)
}
val hasSystemAccess = role.roleType == RolleE.ADMIN
/**
* Test 5: Events-Service (auskommentiert)
*/
private suspend fun testEventsService(role: ReitsportRole): ApiTestResult {
val startTime = DateTimeHelper.now()
delay(100)
return ApiTestResult(
scenarioId = "system-access",
scenarioName = "System-Administration",
endpoint = "$GATEWAY_URL/actuator/info",
method = "GET",
expectedResult = if (hasSystemAccess) "System-Info verfügbar" else "Zugriff verweigert",
actualResult = if (hasSystemAccess) "✅ System-Informationen zugänglich" else "❌ Insufficient permissions",
success = hasSystemAccess,
responseCode = if (hasSystemAccess) 200 else 403,
duration = DateTimeHelper.now() - startTime,
token = generateMockToken(role)
)
}
return ApiTestResult(
scenarioId = "events-unavailable",
scenarioName = "Events Service",
endpoint = "$EVENTS_SERVICE_URL/api/events",
method = "GET",
expectedResult = "Veranstaltungs-Daten abrufen",
actualResult = "⚠️ Service temporär deaktiviert (in settings.gradle.kts auskommentiert)",
success = false,
responseCode = 503,
duration = DateTimeHelper.now() - startTime,
token = generateMockToken(role),
errorMessage = "Service in Entwicklung"
)
}
/**
* Test 7: Öffentlicher Zugriff
*/
private suspend fun testPublicAccess(role: ReitsportRole): ApiTestResult {
val startTime = DateTimeHelper.now()
delay(150)
/**
* Test 6: System-Zugriff (für Admins)
*/
private suspend fun testSystemAccess(role: ReitsportRole): ApiTestResult {
val startTime = DateTimeHelper.now()
delay(300)
return ApiTestResult(
scenarioId = "public-access",
scenarioName = "Öffentliche Informationen",
endpoint = "$GATEWAY_URL/api/public/info",
method = "GET",
expectedResult = "Öffentliche Daten verfügbar",
actualResult = "✅ Öffentliche Informationen zugänglich (kein Token erforderlich)",
success = true,
responseCode = 200,
duration = DateTimeHelper.now() - startTime,
token = null // Kein Token für öffentlichen Zugriff
)
}
val hasSystemAccess = role.roleType == RolleE.ADMIN
return ApiTestResult(
scenarioId = "system-access",
scenarioName = "System-Administration",
endpoint = "$GATEWAY_URL/actuator/info",
method = "GET",
expectedResult = if (hasSystemAccess) "System-Info verfügbar" else "Zugriff verweigert",
actualResult = if (hasSystemAccess) "✅ System-Informationen zugänglich" else "❌ Insufficient permissions",
success = hasSystemAccess,
responseCode = if (hasSystemAccess) 200 else 403,
duration = DateTimeHelper.now() - startTime,
token = generateMockToken(role)
)
}
/**
* Test 7: Öffentlicher Zugriff
*/
private suspend fun testPublicAccess(role: ReitsportRole): ApiTestResult {
val startTime = DateTimeHelper.now()
delay(150)
return ApiTestResult(
scenarioId = "public-access",
scenarioName = "Öffentliche Informationen",
endpoint = "$GATEWAY_URL/api/public/info",
method = "GET",
expectedResult = "Öffentliche Daten verfügbar",
actualResult = "✅ Öffentliche Informationen zugänglich (kein Token erforderlich)",
success = true,
responseCode = 200,
duration = DateTimeHelper.now() - startTime,
token = null // Kein Token für öffentlichen Zugriff
)
}
/**
* Generiere Mock-Token für Tests
*/
private fun generateMockToken(role: ReitsportRole): String {
// Phase 3: Mock-Token (später echte Keycloak-Integration)
val mockPayload = """{"role":"${role.roleType}","permissions":${role.permissions.size}}"""
return "mock.token.${DateTimeHelper.now()}.${role.roleType}"
}
/**
* Generiere Mock-Token für Tests
*/
private fun generateMockToken(role: ReitsportRole): String {
// Phase 3: Mock-Token (später echte Keycloak-Integration)
val mockPayload = """{"role":"${role.roleType}","permissions":${role.permissions.size}}"""
return "mock.token.${DateTimeHelper.now()}.${role.roleType}"
}
}
@@ -8,15 +8,15 @@ import kotlinx.serialization.Serializable
*/
@Serializable
enum class RolleE {
ADMIN, // System administrator
VEREINS_ADMIN, // Club administrator
FUNKTIONAER, // Official/functionary
REITER, // Rider
TRAINER, // Trainer
RICHTER, // Judge
TIERARZT, // Veterinarian
ZUSCHAUER, // Spectator
GAST // Guest
ADMIN, // System administrator
VEREINS_ADMIN, // Club administrator
FUNKTIONAER, // Official/functionary
REITER, // Rider
TRAINER, // Trainer
RICHTER, // Judge
TIERARZT, // Veterinarian
ZUSCHAUER, // Spectator
GAST // Guest
}
/**
@@ -25,27 +25,27 @@ enum class RolleE {
*/
@Serializable
enum class BerechtigungE {
// Person management
PERSON_READ,
PERSON_CREATE,
PERSON_UPDATE,
PERSON_DELETE,
// Person management
PERSON_READ,
PERSON_CREATE,
PERSON_UPDATE,
PERSON_DELETE,
// Club management
VEREIN_READ,
VEREIN_CREATE,
VEREIN_UPDATE,
VEREIN_DELETE,
// Club management
VEREIN_READ,
VEREIN_CREATE,
VEREIN_UPDATE,
VEREIN_DELETE,
// Event management
VERANSTALTUNG_READ,
VERANSTALTUNG_CREATE,
VERANSTALTUNG_UPDATE,
VERANSTALTUNG_DELETE,
// Event management
VERANSTALTUNG_READ,
VERANSTALTUNG_CREATE,
VERANSTALTUNG_UPDATE,
VERANSTALTUNG_DELETE,
// Horse management
PFERD_READ,
PFERD_CREATE,
PFERD_UPDATE,
PFERD_DELETE
// Horse management
PFERD_READ,
PFERD_CREATE,
PFERD_UPDATE,
PFERD_DELETE
}
@@ -1,93 +0,0 @@
package at.mocode.clients.pingfeature.model
/**
* Phase 1 Validierung für Reitsport-Authentication-Testing
* Testet alle Erfolgs-Kriterien aus der Aufgabenstellung
*/
object Phase1Validation {
/**
* Führt alle Phase 1 Validierungen durch
*/
fun validatePhase1(): String {
val results = mutableListOf<String>()
// ✅ Test 1: Anzahl Rollen (erwartet: 9)
val roleCount = ReitsportRoles.ALL_ROLES.size
results.add("✅ Rollen-Anzahl: $roleCount (erwartet: 9) - ${if (roleCount == 9) "ERFOLG" else "FEHLER"}")
// ✅ Test 2: Admin-Rolle verfügbar
val adminRole = ReitsportRoles.ADMIN
results.add("✅ Admin-Rolle: ${adminRole.displayName} - ERFOLG")
// ✅ Test 3: Alle Kategorien verfügbar
val categories = ReitsportRoles.ROLES_BY_CATEGORY.keys
results.add("✅ Kategorien: $categories - ERFOLG")
results.add(" - SYSTEM: ${ReitsportRoles.ROLES_BY_CATEGORY[RoleCategory.SYSTEM]?.size ?: 0} Rollen")
results.add(" - OFFICIAL: ${ReitsportRoles.ROLES_BY_CATEGORY[RoleCategory.OFFICIAL]?.size ?: 0} Rollen")
results.add(" - ACTIVE: ${ReitsportRoles.ROLES_BY_CATEGORY[RoleCategory.ACTIVE]?.size ?: 0} Rollen")
results.add(" - PASSIVE: ${ReitsportRoles.ROLES_BY_CATEGORY[RoleCategory.PASSIVE]?.size ?: 0} Rollen")
// ✅ Test 4: DateTime funktioniert
val timestamp = DateTimeHelper.now()
results.add("✅ DateTime funktioniert: $timestamp - ERFOLG")
// ✅ Test 5: Test-ID generiert
val testId = getTimeMillis().toString()
results.add("✅ Test-ID generiert: $testId - ERFOLG")
// ✅ Test 6: Enum-Zugriff funktioniert
results.add("✅ RolleE Enum: ${RolleE.entries.size} Einträge - ERFOLG")
results.add("✅ BerechtigungE Enum: ${BerechtigungE.entries.size} Einträge - ERFOLG")
// ✅ Test 7: Alle 9 Rollen einzeln prüfen
results.add("✅ Alle Rollen-Definitionen:")
ReitsportRoles.ALL_ROLES.forEachIndexed { index, role ->
results.add(" ${index + 1}. ${role.displayName} (${role.roleType}) - ${role.permissions.size} Berechtigungen")
}
// ✅ Test 8: Berechtigungen-Zuordnung testen
val adminPermissions = ReitsportRoles.ADMIN.permissions.size
val guestPermissions = ReitsportRoles.GAST.permissions.size
results.add("✅ Admin-Berechtigungen: $adminPermissions (max)")
results.add("✅ Gast-Berechtigungen: $guestPermissions (min)")
// ✅ Test 9: Hilfsfunktionen testen
val roleByType = ReitsportRoles.getRoleByType(RolleE.RICHTER)
results.add("✅ Rolle per Type: ${roleByType?.displayName} - ERFOLG")
val rolesWithRead = ReitsportRoles.getRolesWithPermission(BerechtigungE.PERSON_READ)
results.add("✅ Rollen mit PERSON_READ: ${rolesWithRead.size} - ERFOLG")
return results.joinToString("\n")
}
/**
* Führt Performance-Test durch
*/
fun performanceTest(): String {
val start = DateTimeHelper.now()
// Simuliere mehrere Rollen-Abfragen
repeat(100) {
ReitsportRoles.getAllRoles()
ReitsportRoles.getRoleByType(RolleE.ADMIN)
ReitsportRoles.getRolesWithPermission(BerechtigungE.PERSON_READ)
}
val end = DateTimeHelper.now()
val duration = end - start
return "✅ Performance-Test: $duration Zeiteinheiten für 300 Operationen - ERFOLG"
}
}
/**
* Hilfsfunktion für externe Zeitabfrage
*/
private fun getTimeMillis(): Long = DateTimeHelper.now()
/**
* Extension für einfacheren Zugriff
*/
private fun ReitsportRoles.getAllRoles() = ALL_ROLES
@@ -13,27 +13,27 @@ import kotlinx.serialization.Serializable
*/
@Serializable
data class ReitsportRole(
val roleType: RolleE,
val displayName: String,
val description: String,
val icon: String,
val permissions: List<BerechtigungE>,
val priority: Int, // Für Sortierung in UI (1 = höchste Priorität)
val category: RoleCategory
val roleType: RolleE,
val displayName: String,
val description: String,
val icon: String,
val permissions: List<BerechtigungE>,
val priority: Int, // Für Sortierung in UI (1 = höchste Priorität)
val category: RoleCategory
) {
/**
* Hilfsfunktion: Prüft, ob diese Rolle eine bestimmte Berechtigung hat
*/
fun hasPermission(permission: BerechtigungE): Boolean {
return permissions.contains(permission)
}
/**
* Hilfsfunktion: Prüft, ob diese Rolle eine bestimmte Berechtigung hat
*/
fun hasPermission(permission: BerechtigungE): Boolean {
return permissions.contains(permission)
}
/**
* Hilfsfunktion: Gibt alle fehlenden Berechtigungen für eine Liste zurück
*/
fun getMissingPermissions(requiredPermissions: List<BerechtigungE>): List<BerechtigungE> {
return requiredPermissions.filter { !permissions.contains(it) }
}
/**
* Hilfsfunktion: Gibt alle fehlenden Berechtigungen für eine Liste zurück
*/
fun getMissingPermissions(requiredPermissions: List<BerechtigungE>): List<BerechtigungE> {
return requiredPermissions.filter { !permissions.contains(it) }
}
}
/**
@@ -41,10 +41,10 @@ data class ReitsportRole(
*/
@Serializable
enum class RoleCategory(val displayName: String, val color: String) {
SYSTEM("System-Verwaltung", "#FF5722"), // Rot
OFFICIAL("Offizielle Funktionen", "#3F51B5"), // Indigo
ACTIVE("Aktive Teilnahme", "#4CAF50"), // Grün
PASSIVE("Information & Zugang", "#9E9E9E") // Grau
SYSTEM("System-Verwaltung", "#FF5722"), // Rot
OFFICIAL("Offizielle Funktionen", "#3F51B5"), // Indigo
ACTIVE("Aktive Teilnahme", "#4CAF50"), // Grün
PASSIVE("Information & Zugang", "#9E9E9E") // Grau
}
/**
@@ -52,17 +52,17 @@ enum class RoleCategory(val displayName: String, val color: String) {
*/
@Serializable
data class AuthTestScenario(
val id: String,
val name: String,
val businessProcess: String,
val description: String,
val expectedBehavior: String,
val requiredRole: RolleE,
val requiredPermissions: List<BerechtigungE>,
val testEndpoint: String,
val testMethod: String = "GET",
val priority: TestPriority = TestPriority.NORMAL,
val category: ScenarioCategory
val id: String,
val name: String,
val businessProcess: String,
val description: String,
val expectedBehavior: String,
val requiredRole: RolleE,
val requiredPermissions: List<BerechtigungE>,
val testEndpoint: String,
val testMethod: String = "GET",
val priority: TestPriority = TestPriority.NORMAL,
val category: ScenarioCategory
)
/**
@@ -70,37 +70,37 @@ data class AuthTestScenario(
*/
@Serializable
enum class ScenarioCategory(val displayName: String, val icon: String) {
// Kern-Geschäftsprozesse
VERANSTALTUNG_SETUP("Veranstaltungs-Einrichtung", "🏟️"),
TURNIER_MANAGEMENT("Turnier-Verwaltung", "🎪"),
BEWERB_KONFIGURATION("Bewerb-Konfiguration", "🏇"),
// Kern-Geschäftsprozesse
VERANSTALTUNG_SETUP("Veranstaltungs-Einrichtung", "🏟️"),
TURNIER_MANAGEMENT("Turnier-Verwaltung", "🎪"),
BEWERB_KONFIGURATION("Bewerb-Konfiguration", "🏇"),
// Finanzen
KASSABUCH("Kassabuch-Führung", "💰"),
ABRECHNUNG("Turnier-Abrechnung", "🧾"),
// Finanzen
KASSABUCH("Kassabuch-Führung", "💰"),
ABRECHNUNG("Turnier-Abrechnung", "🧾"),
// Nennsystem
NENNUNG_WEBFORMULAR("Nenn-Web-Formular", "📝"),
NENNUNG_MOBILE("Mobile Nennung", "📱"),
NENNTAUSCH("Nenntausch-System", "🔄"),
// Nennsystem
NENNUNG_WEBFORMULAR("Nenn-Web-Formular", "📝"),
NENNUNG_MOBILE("Mobile Nennung", "📱"),
NENNTAUSCH("Nenntausch-System", "🔄"),
// Startlisten & Zeitplan
ZEITPLAN_ERSTELLUNG("Zeitplan-Erstellung", ""),
STARTERLISTE_FLEXIBEL("Flexible Starterlisten", "📋"),
RICHTER_VALIDATION("Richter-Lizenz-Validierung", "⚖️"),
// Startlisten & Zeitplan
ZEITPLAN_ERSTELLUNG("Zeitplan-Erstellung", ""),
STARTERLISTE_FLEXIBEL("Flexible Starterlisten", "📋"),
RICHTER_VALIDATION("Richter-Lizenz-Validierung", "⚖️"),
// Ergebnisse
ERGEBNIS_DRESSUR("Ergebnis-Erfassung Dressur", "🎭"),
ERGEBNIS_SPRINGEN("Ergebnis-Erfassung Springen", "🚀"),
ERGEBNIS_VIELSEITIGKEIT("Ergebnis-Erfassung Vielseitigkeit", "🎯"),
// Ergebnisse
ERGEBNIS_DRESSUR("Ergebnis-Erfassung Dressur", "🎭"),
ERGEBNIS_SPRINGEN("Ergebnis-Erfassung Springen", "🚀"),
ERGEBNIS_VIELSEITIGKEIT("Ergebnis-Erfassung Vielseitigkeit", "🎯"),
// OEPS Integration
OEPS_SYNC("OEPS-Synchronisation", "🔗"),
TURNIER_NUMMER("Turnier-Nummer-Verwaltung", "🔢"),
// OEPS Integration
OEPS_SYNC("OEPS-Synchronisation", "🔗"),
TURNIER_NUMMER("Turnier-Nummer-Verwaltung", "🔢"),
// System
SYSTEM_ADMIN("System-Administration", "🔧"),
BENUTZER_VERWALTUNG("Benutzer-Verwaltung", "👥")
// System
SYSTEM_ADMIN("System-Administration", "🔧"),
BENUTZER_VERWALTUNG("Benutzer-Verwaltung", "👥")
}
/**
@@ -108,29 +108,29 @@ enum class ScenarioCategory(val displayName: String, val icon: String) {
*/
@Serializable
data class ComplexAuthTestScenario(
val id: String,
val name: String,
val businessProcess: String,
val description: String,
val subProcesses: List<String>, // Multi-Step-Prozesse
val requiredRole: RolleE,
val requiredPermissions: List<BerechtigungE>,
val testEndpoints: List<TestEndpoint>, // Mehrere API-Calls
val mockData: Map<String, String> = emptyMap(),
val expectedOutcome: String,
val priority: TestPriority = TestPriority.NORMAL,
val category: ScenarioCategory,
val oepsIntegrationRequired: Boolean = false
val id: String,
val name: String,
val businessProcess: String,
val description: String,
val subProcesses: List<String>, // Multi-Step-Prozesse
val requiredRole: RolleE,
val requiredPermissions: List<BerechtigungE>,
val testEndpoints: List<TestEndpoint>, // Mehrere API-Calls
val mockData: Map<String, String> = emptyMap(),
val expectedOutcome: String,
val priority: TestPriority = TestPriority.NORMAL,
val category: ScenarioCategory,
val oepsIntegrationRequired: Boolean = false
)
@Serializable
data class TestEndpoint(
val name: String,
val url: String,
val method: String = "GET",
val payload: String? = null,
val expectedResponseCode: Int = 200,
val description: String
val name: String,
val url: String,
val method: String = "GET",
val payload: String? = null,
val expectedResponseCode: Int = 200,
val description: String
)
/**
@@ -138,10 +138,10 @@ data class TestEndpoint(
*/
@Serializable
enum class TestPriority(val displayName: String, val level: Int) {
CRITICAL("Kritisch", 1),
HIGH("Hoch", 2),
NORMAL("Normal", 3),
LOW("Niedrig", 4)
CRITICAL("Kritisch", 1),
HIGH("Hoch", 2),
NORMAL("Normal", 3),
LOW("Niedrig", 4)
}
/**
@@ -149,29 +149,29 @@ enum class TestPriority(val displayName: String, val level: Int) {
*/
@Serializable
data class ApiTestResult(
val scenarioId: String,
val scenarioName: String,
val endpoint: String,
val method: String,
val expectedResult: String,
val actualResult: String,
val success: Boolean,
val responseCode: Int? = null,
val duration: Long, // in Millisekunden
val timestamp: Long = getTimeMillis(),
val token: String? = null, // Gekürzte Token-Info für Debugging
val errorMessage: String? = null,
val responseData: String? = null
val scenarioId: String,
val scenarioName: String,
val endpoint: String,
val method: String,
val expectedResult: String,
val actualResult: String,
val success: Boolean,
val responseCode: Int? = null,
val duration: Long, // in Millisekunden
val timestamp: Long = getTimeMillis(),
val token: String? = null, // Gekürzte Token-Info für Debugging
val errorMessage: String? = null,
val responseData: String? = null
) {
/**
* Hilfsfunktion: Formatiert die Dauer für UI-Anzeige
*/
fun formatDuration(): String = "${duration}ms"
/**
* Hilfsfunktion: Formatiert die Dauer für UI-Anzeige
*/
fun formatDuration(): String = "${duration}ms"
/**
* Hilfsfunktion: Status-Icon für UI
*/
fun getStatusIcon(): String = if (success) "" else ""
/**
* Hilfsfunktion: Status-Icon für UI
*/
fun getStatusIcon(): String = if (success) "" else ""
}
/**
@@ -179,33 +179,33 @@ data class ApiTestResult(
*/
@Serializable
data class ReitsportTestResult(
val testId: String = getTimeMillis().toString(),
val role: ReitsportRole,
val scenarios: List<AuthTestScenario>,
val apiResults: List<ApiTestResult>,
val startTime: Long,
val endTime: Long? = null,
val overallSuccess: Boolean = false,
val summary: TestSummary? = null
val testId: String = getTimeMillis().toString(),
val role: ReitsportRole,
val scenarios: List<AuthTestScenario>,
val apiResults: List<ApiTestResult>,
val startTime: Long,
val endTime: Long? = null,
val overallSuccess: Boolean = false,
val summary: TestSummary? = null
) {
/**
* Berechnet die Gesamtdauer des Tests
*/
fun getTotalDuration(): Long = (endTime ?: getTimeMillis()) - startTime
/**
* Berechnet die Gesamtdauer des Tests
*/
fun getTotalDuration(): Long = (endTime ?: getTimeMillis()) - startTime
/**
* Berechnet Erfolgsrate in Prozent
*/
fun getSuccessRate(): Double {
if (apiResults.isEmpty()) return 0.0
val successful = apiResults.count { it.success }
return (successful.toDouble() / apiResults.size) * 100
}
/**
* Berechnet Erfolgsrate in Prozent
*/
fun getSuccessRate(): Double {
if (apiResults.isEmpty()) return 0.0
val successful = apiResults.count { it.success }
return (successful.toDouble() / apiResults.size) * 100
}
/**
* Gibt alle fehlgeschlagenen Tests zurück
*/
fun getFailedTests(): List<ApiTestResult> = apiResults.filter { !it.success }
/**
* Gibt alle fehlgeschlagenen Tests zurück
*/
fun getFailedTests(): List<ApiTestResult> = apiResults.filter { !it.success }
}
/**
@@ -213,15 +213,15 @@ data class ReitsportTestResult(
*/
@Serializable
data class TestSummary(
val totalTests: Int,
val successfulTests: Int,
val failedTests: Int,
val averageDuration: Long,
val criticalFailures: List<String> = emptyList(),
val recommendations: List<String> = emptyList()
val totalTests: Int,
val successfulTests: Int,
val failedTests: Int,
val averageDuration: Long,
val criticalFailures: List<String> = emptyList(),
val recommendations: List<String> = emptyList()
) {
val successRate: Double
get() = if (totalTests > 0) (successfulTests.toDouble() / totalTests) * 100 else 0.0
val successRate: Double
get() = if (totalTests > 0) (successfulTests.toDouble() / totalTests) * 100 else 0.0
}
/**
@@ -229,17 +229,17 @@ data class TestSummary(
*/
@Serializable
data class TestNennung(
val reiterId: String,
val pferdId: String,
val bewerbId: String,
val nennungsDatum: Long = getTimeMillis()
val reiterId: String,
val pferdId: String,
val bewerbId: String,
val nennungsDatum: Long = getTimeMillis()
)
@Serializable
data class TestStartbereitschaft(
val nennungId: String,
val confirmed: Boolean = true,
val confirmationTime: Long = getTimeMillis()
val nennungId: String,
val confirmed: Boolean = true,
val confirmationTime: Long = getTimeMillis()
)
/**
@@ -247,14 +247,14 @@ data class TestStartbereitschaft(
* Temporäre Lösung für Phase 1 mit incrementellem Counter
*/
object DateTimeHelper {
private var counter = 1000000000L // Start mit einer realistischen Timestamp
private var counter = 1000000000L // Start mit einer realistischen Timestamp
fun now(): Long = counter++
fun now(): Long = counter++
fun formatDateTime(timestamp: Long): String {
// Einfache ISO-ähnliche Formatierung ohne kotlinx-datetime
return "Timestamp: $timestamp" // Temporäre Lösung für Phase 1
}
fun formatDateTime(timestamp: Long): String {
// Einfache ISO-ähnliche Formatierung ohne kotlinx-datetime
return "Timestamp: $timestamp" // Temporäre Lösung für Phase 1
}
}
/**
@@ -6,215 +6,215 @@ package at.mocode.clients.pingfeature.model
*/
object ReitsportRoles {
/**
* System-Administrator - Vollzugriff auf alle Bounded Contexts
*/
val ADMIN = ReitsportRole(
roleType = RolleE.ADMIN,
displayName = "System-Administrator",
description = "Vollzugriff auf alle Microservices und System-Konfiguration",
icon = "🔧",
permissions = BerechtigungE.entries, // Alle verfügbaren Berechtigungen
priority = 1,
category = RoleCategory.SYSTEM
)
/**
* System-Administrator - Vollzugriff auf alle Bounded Contexts
*/
val ADMIN = ReitsportRole(
roleType = RolleE.ADMIN,
displayName = "System-Administrator",
description = "Vollzugriff auf alle Microservices und System-Konfiguration",
icon = "🔧",
permissions = BerechtigungE.entries, // Alle verfügbaren Berechtigungen
priority = 1,
category = RoleCategory.SYSTEM
)
/**
* Vereins-Administrator - Vereins-Bounded-Context
*/
val VEREINS_ADMIN = ReitsportRole(
roleType = RolleE.VEREINS_ADMIN,
displayName = "Vereins-Administrator",
description = "Vereinsverwaltung und Mitglieder-Management",
icon = "🏛️",
permissions = listOf(
// Personen (Mitglieder)
BerechtigungE.PERSON_READ,
BerechtigungE.PERSON_CREATE,
BerechtigungE.PERSON_UPDATE,
BerechtigungE.PERSON_DELETE,
// Verein
BerechtigungE.VEREIN_READ,
BerechtigungE.VEREIN_UPDATE,
// Veranstaltungen organisieren
BerechtigungE.VERANSTALTUNG_READ,
BerechtigungE.VERANSTALTUNG_CREATE,
BerechtigungE.VERANSTALTUNG_UPDATE,
// Pferde (für Vereinsmitglieder)
BerechtigungE.PFERD_READ
),
priority = 2,
category = RoleCategory.SYSTEM
)
/**
* Vereins-Administrator - Vereins-Bounded-Context
*/
val VEREINS_ADMIN = ReitsportRole(
roleType = RolleE.VEREINS_ADMIN,
displayName = "Vereins-Administrator",
description = "Vereinsverwaltung und Mitglieder-Management",
icon = "🏛️",
permissions = listOf(
// Personen (Mitglieder)
BerechtigungE.PERSON_READ,
BerechtigungE.PERSON_CREATE,
BerechtigungE.PERSON_UPDATE,
BerechtigungE.PERSON_DELETE,
// Verein
BerechtigungE.VEREIN_READ,
BerechtigungE.VEREIN_UPDATE,
// Veranstaltungen organisieren
BerechtigungE.VERANSTALTUNG_READ,
BerechtigungE.VERANSTALTUNG_CREATE,
BerechtigungE.VERANSTALTUNG_UPDATE,
// Pferde (für Vereinsmitglieder)
BerechtigungE.PFERD_READ
),
priority = 2,
category = RoleCategory.SYSTEM
)
/**
* Funktionär - Event-Management-Bounded-Context
*/
val FUNKTIONAER = ReitsportRole(
roleType = RolleE.FUNKTIONAER,
displayName = "Funktionär (Meldestelle)",
description = "Turnierorganisation: Nennungen, Starterlisten, Meldestellen-Workflows",
icon = "⚖️",
permissions = listOf(
// Lesen aller relevanten Daten
BerechtigungE.PERSON_READ,
BerechtigungE.PFERD_READ,
BerechtigungE.VERANSTALTUNG_READ,
BerechtigungE.VERANSTALTUNG_UPDATE, // Turnier-Management
// Erweiterte Rechte in Veranstaltungs-Context
// (Hier werden später Nennung-, Startlisten-Berechtigungen hinzugefügt)
),
priority = 3,
category = RoleCategory.OFFICIAL
)
/**
* Funktionär - Event-Management-Bounded-Context
*/
val FUNKTIONAER = ReitsportRole(
roleType = RolleE.FUNKTIONAER,
displayName = "Funktionär (Meldestelle)",
description = "Turnierorganisation: Nennungen, Starterlisten, Meldestellen-Workflows",
icon = "⚖️",
permissions = listOf(
// Lesen aller relevanten Daten
BerechtigungE.PERSON_READ,
BerechtigungE.PFERD_READ,
BerechtigungE.VERANSTALTUNG_READ,
BerechtigungE.VERANSTALTUNG_UPDATE, // Turnier-Management
// Erweiterte Rechte in Veranstaltungs-Context
// (Hier werden später Nennung-, Startlisten-Berechtigungen hinzugefügt)
),
priority = 3,
category = RoleCategory.OFFICIAL
)
/**
* Richter - Spezialisierte Bewertungs-Rolle
*/
val RICHTER = ReitsportRole(
roleType = RolleE.RICHTER,
displayName = "Richter",
description = "Prüfungs-Bewertung und Ergebnis-Eingabe (ReadOnly-Zugriff auf Stammdaten)",
icon = "⚖️",
permissions = listOf(
// Nur Lese-Zugriff auf relevante Daten
BerechtigungE.PERSON_READ, // Starter-Info
BerechtigungE.PFERD_READ, // Pferde-Info
BerechtigungE.VERANSTALTUNG_READ // Prüfungs-Details
// Ergebnis-Eingabe wird später als eigener Bounded Context hinzugefügt
),
priority = 4,
category = RoleCategory.OFFICIAL
)
/**
* Richter - Spezialisierte Bewertungs-Rolle
*/
val RICHTER = ReitsportRole(
roleType = RolleE.RICHTER,
displayName = "Richter",
description = "Prüfungs-Bewertung und Ergebnis-Eingabe (ReadOnly-Zugriff auf Stammdaten)",
icon = "⚖️",
permissions = listOf(
// Nur Lese-Zugriff auf relevante Daten
BerechtigungE.PERSON_READ, // Starter-Info
BerechtigungE.PFERD_READ, // Pferde-Info
BerechtigungE.VERANSTALTUNG_READ // Prüfungs-Details
// Ergebnis-Eingabe wird später als eigener Bounded Context hinzugefügt
),
priority = 4,
category = RoleCategory.OFFICIAL
)
/**
* Tierarzt - Veterinär-Bounded-Context
*/
val TIERARZT = ReitsportRole(
roleType = RolleE.TIERARZT,
displayName = "Tierarzt",
description = "Veterinärkontrollen und Pferde-Gesundheits-Management",
icon = "🩺",
permissions = listOf(
BerechtigungE.PFERD_READ,
BerechtigungE.PFERD_UPDATE, // Gesundheitsdaten, Vet-Checks
BerechtigungE.PERSON_READ, // Besitzer-Kontakt
BerechtigungE.VERANSTALTUNG_READ // Turnier-Context für Kontrollen
),
priority = 5,
category = RoleCategory.OFFICIAL
)
/**
* Tierarzt - Veterinär-Bounded-Context
*/
val TIERARZT = ReitsportRole(
roleType = RolleE.TIERARZT,
displayName = "Tierarzt",
description = "Veterinärkontrollen und Pferde-Gesundheits-Management",
icon = "🩺",
permissions = listOf(
BerechtigungE.PFERD_READ,
BerechtigungE.PFERD_UPDATE, // Gesundheitsdaten, Vet-Checks
BerechtigungE.PERSON_READ, // Besitzer-Kontakt
BerechtigungE.VERANSTALTUNG_READ // Turnier-Context für Kontrollen
),
priority = 5,
category = RoleCategory.OFFICIAL
)
/**
* Trainer - Training-Bounded-Context (zukünftig)
*/
val TRAINER = ReitsportRole(
roleType = RolleE.TRAINER,
displayName = "Trainer",
description = "Schützlings-Betreuung und Training-Management",
icon = "🏃‍♂️",
permissions = listOf(
BerechtigungE.PERSON_READ, // Schützlinge
BerechtigungE.PFERD_READ, // Trainingspferde
BerechtigungE.VERANSTALTUNG_READ // Turnier-Planung für Schützlinge
// Training-spezifische Berechtigungen kommen später
),
priority = 6,
category = RoleCategory.ACTIVE
)
/**
* Trainer - Training-Bounded-Context (zukünftig)
*/
val TRAINER = ReitsportRole(
roleType = RolleE.TRAINER,
displayName = "Trainer",
description = "Schützlings-Betreuung und Training-Management",
icon = "🏃‍♂️",
permissions = listOf(
BerechtigungE.PERSON_READ, // Schützlinge
BerechtigungE.PFERD_READ, // Trainingspferde
BerechtigungE.VERANSTALTUNG_READ // Turnier-Planung für Schützlinge
// Training-spezifische Berechtigungen kommen später
),
priority = 6,
category = RoleCategory.ACTIVE
)
/**
* Reiter - Persönlicher Bounded Context
*/
val REITER = ReitsportRole(
roleType = RolleE.REITER,
displayName = "Reiter",
description = "Persönliche Daten, eigene Pferde und Turnier-Teilnahme",
icon = "🐎",
permissions = listOf(
BerechtigungE.PERSON_READ, // Nur eigene Daten
BerechtigungE.PFERD_READ, // Nur eigene Pferde
BerechtigungE.VERANSTALTUNG_READ // Öffentliche Turnier-Infos
// Eigene Daten ändern: Später als PERSON_UPDATE_OWN, PFERD_UPDATE_OWN
),
priority = 7,
category = RoleCategory.ACTIVE
)
/**
* Reiter - Persönlicher Bounded Context
*/
val REITER = ReitsportRole(
roleType = RolleE.REITER,
displayName = "Reiter",
description = "Persönliche Daten, eigene Pferde und Turnier-Teilnahme",
icon = "🐎",
permissions = listOf(
BerechtigungE.PERSON_READ, // Nur eigene Daten
BerechtigungE.PFERD_READ, // Nur eigene Pferde
BerechtigungE.VERANSTALTUNG_READ // Öffentliche Turnier-Infos
// Eigene Daten ändern: Später als PERSON_UPDATE_OWN, PFERD_UPDATE_OWN
),
priority = 7,
category = RoleCategory.ACTIVE
)
/**
* Zuschauer - Public-Read-Only Bounded Context
*/
val ZUSCHAUER = ReitsportRole(
roleType = RolleE.ZUSCHAUER,
displayName = "Zuschauer",
description = "Öffentliche Informationen: Starterlisten, Ergebnisse, Zeitpläne",
icon = "👁️",
permissions = listOf(
BerechtigungE.VERANSTALTUNG_READ // Nur öffentliche Turnier-Daten
// Später: STARTERLISTE_READ_PUBLIC, ERGEBNIS_READ_PUBLIC
),
priority = 8,
category = RoleCategory.PASSIVE
)
/**
* Zuschauer - Public-Read-Only Bounded Context
*/
val ZUSCHAUER = ReitsportRole(
roleType = RolleE.ZUSCHAUER,
displayName = "Zuschauer",
description = "Öffentliche Informationen: Starterlisten, Ergebnisse, Zeitpläne",
icon = "👁️",
permissions = listOf(
BerechtigungE.VERANSTALTUNG_READ // Nur öffentliche Turnier-Daten
// Später: STARTERLISTE_READ_PUBLIC, ERGEBNIS_READ_PUBLIC
),
priority = 8,
category = RoleCategory.PASSIVE
)
/**
* Gast - Keine Authentifizierung erforderlich
*/
val GAST = ReitsportRole(
roleType = RolleE.GAST,
displayName = "Gast",
description = "Öffentliche Basis-Informationen ohne Registrierung",
icon = "🔓",
permissions = emptyList(), // Nur völlig öffentliche Endpunkte
priority = 9,
category = RoleCategory.PASSIVE
)
/**
* Gast - Keine Authentifizierung erforderlich
*/
val GAST = ReitsportRole(
roleType = RolleE.GAST,
displayName = "Gast",
description = "Öffentliche Basis-Informationen ohne Registrierung",
icon = "🔓",
permissions = emptyList(), // Nur völlig öffentliche Endpunkte
priority = 9,
category = RoleCategory.PASSIVE
)
/**
* Alle definierten Rollen in organisatorischer Reihenfolge
*/
val ALL_ROLES = listOf(
ADMIN,
VEREINS_ADMIN,
FUNKTIONAER,
RICHTER,
TIERARZT,
TRAINER,
REITER,
ZUSCHAUER,
GAST
)
/**
* Alle definierten Rollen in organisatorischer Reihenfolge
*/
val ALL_ROLES = listOf(
ADMIN,
VEREINS_ADMIN,
FUNKTIONAER,
RICHTER,
TIERARZT,
TRAINER,
REITER,
ZUSCHAUER,
GAST
)
/**
* Rollen nach Bounded Context / Microservice gruppiert
*/
val ROLES_BY_BOUNDED_CONTEXT = mapOf(
"System Management" to listOf(ADMIN),
"Vereins-Service" to listOf(VEREINS_ADMIN),
"Event-Service" to listOf(FUNKTIONAER),
"Bewertungs-Service" to listOf(RICHTER),
"Vet-Service" to listOf(TIERARZT),
"Training-Service" to listOf(TRAINER),
"Member-Service" to listOf(REITER),
"Public-Service" to listOf(ZUSCHAUER, GAST)
)
/**
* Rollen nach Bounded Context / Microservice gruppiert
*/
val ROLES_BY_BOUNDED_CONTEXT = mapOf(
"System Management" to listOf(ADMIN),
"Vereins-Service" to listOf(VEREINS_ADMIN),
"Event-Service" to listOf(FUNKTIONAER),
"Bewertungs-Service" to listOf(RICHTER),
"Vet-Service" to listOf(TIERARZT),
"Training-Service" to listOf(TRAINER),
"Member-Service" to listOf(REITER),
"Public-Service" to listOf(ZUSCHAUER, GAST)
)
/**
* Rollen nach UI-Kategorie (für Ping-Dashboard)
*/
val ROLES_BY_CATEGORY = ALL_ROLES.groupBy { it.category }
/**
* Rollen nach UI-Kategorie (für Ping-Dashboard)
*/
val ROLES_BY_CATEGORY = ALL_ROLES.groupBy { it.category }
/**
* Hilfsfunktion: Rolle nach RolleE-Typ finden
*/
fun getRoleByType(roleType: RolleE): ReitsportRole? {
return ALL_ROLES.find { it.roleType == roleType }
}
/**
* Hilfsfunktion: Rolle nach RolleE-Typ finden
*/
fun getRoleByType(roleType: RolleE): ReitsportRole? {
return ALL_ROLES.find { it.roleType == roleType }
}
/**
* Hilfsfunktion: Alle Rollen mit einer bestimmten Berechtigung
*/
fun getRolesWithPermission(permission: BerechtigungE): List<ReitsportRole> {
return ALL_ROLES.filter { it.hasPermission(permission) }
}
/**
* Hilfsfunktion: Alle Rollen mit einer bestimmten Berechtigung
*/
fun getRolesWithPermission(permission: BerechtigungE): List<ReitsportRole> {
return ALL_ROLES.filter { it.hasPermission(permission) }
}
}
@@ -12,190 +12,190 @@ import kotlin.test.assertEquals
class PingApiClientTest {
private fun createMockApiClient(mockEngine: MockEngine): PingApiClient {
return PingApiClient("http://localhost:8081")
private fun createMockApiClient(mockEngine: MockEngine): PingApiClient {
return PingApiClient("http://localhost:8081")
}
@Test
fun `simplePing should return correct response`() = runTest {
// Given
val expectedResponse = PingResponse(
status = "OK",
timestamp = "2025-09-27T21:27:00Z",
service = "ping-service"
)
val mockEngine = MockEngine { request ->
assertEquals("http://localhost:8081/api/ping/simple", request.url.toString())
assertEquals(HttpMethod.Get, request.method)
respond(
content = Json.encodeToString(PingResponse.serializer(), expectedResponse),
status = HttpStatusCode.OK,
headers = headersOf(HttpHeaders.ContentType, "application/json")
)
}
@Test
fun `simplePing should return correct response`() = runTest {
// Given
val expectedResponse = PingResponse(
status = "OK",
timestamp = "2025-09-27T21:27:00Z",
service = "ping-service"
)
// When
val apiClient = PingApiClient("http://localhost:8081")
// Note: This is a limitation - we can't easily inject the mock engine
// This test demonstrates the structure but would need refactoring of PingApiClient
// to accept HttpClient as dependency for full testability
}
val mockEngine = MockEngine { request ->
assertEquals("http://localhost:8081/api/ping/simple", request.url.toString())
assertEquals(HttpMethod.Get, request.method)
@Test
fun `enhancedPing should include simulate parameter`() = runTest {
// Given
val expectedResponse = EnhancedPingResponse(
status = "OK",
timestamp = "2025-09-27T21:27:00Z",
service = "ping-service",
circuitBreakerState = "CLOSED",
responseTime = 42L
)
respond(
content = Json.encodeToString(PingResponse.serializer(), expectedResponse),
status = HttpStatusCode.OK,
headers = headersOf(HttpHeaders.ContentType, "application/json")
)
}
val mockEngine = MockEngine { request ->
assertEquals("http://localhost:8081/api/ping/enhanced", request.url.encodedPath)
assertEquals("true", request.url.parameters["simulate"])
assertEquals(HttpMethod.Get, request.method)
// When
val apiClient = PingApiClient("http://localhost:8081")
// Note: This is a limitation - we can't easily inject the mock engine
// This test demonstrates the structure but would need refactoring of PingApiClient
// to accept HttpClient as dependency for full testability
respond(
content = Json.encodeToString(EnhancedPingResponse.serializer(), expectedResponse),
status = HttpStatusCode.OK,
headers = headersOf(HttpHeaders.ContentType, "application/json")
)
}
@Test
fun `enhancedPing should include simulate parameter`() = runTest {
// Given
val expectedResponse = EnhancedPingResponse(
status = "OK",
timestamp = "2025-09-27T21:27:00Z",
service = "ping-service",
circuitBreakerState = "CLOSED",
responseTime = 42L
)
// When - This test shows the intended structure
// val apiClient = PingApiClient(httpClient = HttpClient(mockEngine))
// val response = apiClient.enhancedPing(simulate = true)
val mockEngine = MockEngine { request ->
assertEquals("http://localhost:8081/api/ping/enhanced", request.url.encodedPath)
assertEquals("true", request.url.parameters["simulate"])
assertEquals(HttpMethod.Get, request.method)
// Then
// assertEquals(expectedResponse, response)
}
respond(
content = Json.encodeToString(EnhancedPingResponse.serializer(), expectedResponse),
status = HttpStatusCode.OK,
headers = headersOf(HttpHeaders.ContentType, "application/json")
)
}
@Test
fun `healthCheck should return health response`() = runTest {
// Given
val expectedResponse = HealthResponse(
status = "UP",
timestamp = "2025-09-27T21:27:00Z",
service = "ping-service",
healthy = true
)
// When - This test shows the intended structure
// val apiClient = PingApiClient(httpClient = HttpClient(mockEngine))
// val response = apiClient.enhancedPing(simulate = true)
val mockEngine = MockEngine { request ->
assertEquals("http://localhost:8081/api/ping/health", request.url.toString())
assertEquals(HttpMethod.Get, request.method)
// Then
// assertEquals(expectedResponse, response)
respond(
content = Json.encodeToString(HealthResponse.serializer(), expectedResponse),
status = HttpStatusCode.OK,
headers = headersOf(HttpHeaders.ContentType, "application/json")
)
}
@Test
fun `healthCheck should return health response`() = runTest {
// Given
val expectedResponse = HealthResponse(
status = "UP",
timestamp = "2025-09-27T21:27:00Z",
service = "ping-service",
healthy = true
)
// When - Test structure demonstration
// val apiClient = PingApiClient(httpClient = HttpClient(mockEngine))
// val response = apiClient.healthCheck()
val mockEngine = MockEngine { request ->
assertEquals("http://localhost:8081/api/ping/health", request.url.toString())
assertEquals(HttpMethod.Get, request.method)
// Then
// assertEquals(expectedResponse, response)
}
respond(
content = Json.encodeToString(HealthResponse.serializer(), expectedResponse),
status = HttpStatusCode.OK,
headers = headersOf(HttpHeaders.ContentType, "application/json")
)
}
// When - Test structure demonstration
// val apiClient = PingApiClient(httpClient = HttpClient(mockEngine))
// val response = apiClient.healthCheck()
// Then
// assertEquals(expectedResponse, response)
@Test
fun `API client should handle HTTP errors correctly`() = runTest {
val mockEngine = MockEngine { request ->
respond(
content = """{"error": "Internal Server Error"}""",
status = HttpStatusCode.InternalServerError,
headers = headersOf(HttpHeaders.ContentType, "application/json")
)
}
@Test
fun `API client should handle HTTP errors correctly`() = runTest {
val mockEngine = MockEngine { request ->
respond(
content = """{"error": "Internal Server Error"}""",
status = HttpStatusCode.InternalServerError,
headers = headersOf(HttpHeaders.ContentType, "application/json")
)
}
// Test structure for error handling
// val apiClient = PingApiClient(httpClient = HttpClient(mockEngine))
// assertFailsWith<Exception> {
// apiClient.simplePing()
// }
}
// Test structure for error handling
// val apiClient = PingApiClient(httpClient = HttpClient(mockEngine))
// assertFailsWith<Exception> {
// apiClient.simplePing()
// }
@Test
fun `API client should handle network errors`() = runTest {
val mockEngine = MockEngine { request ->
throw Exception("Network unreachable")
}
@Test
fun `API client should handle network errors`() = runTest {
val mockEngine = MockEngine { request ->
throw Exception("Network unreachable")
}
// Test structure for network error handling
// val apiClient = PingApiClient(httpClient = HttpClient(mockEngine))
// assertFailsWith<Exception> {
// apiClient.simplePing()
// }
}
// Test structure for network error handling
// val apiClient = PingApiClient(httpClient = HttpClient(mockEngine))
// assertFailsWith<Exception> {
// apiClient.simplePing()
// }
}
@Test
fun `JSON serialization should work correctly`() {
// Given
val pingResponse = PingResponse(
status = "OK",
timestamp = "2025-09-27T21:27:00Z",
service = "test-service"
)
@Test
fun `JSON serialization should work correctly`() {
// Given
val pingResponse = PingResponse(
status = "OK",
timestamp = "2025-09-27T21:27:00Z",
service = "test-service"
)
// When
val json = Json.encodeToString(PingResponse.serializer(), pingResponse)
val deserializedResponse = Json.decodeFromString(PingResponse.serializer(), json)
// When
val json = Json.encodeToString(PingResponse.serializer(), pingResponse)
val deserializedResponse = Json.decodeFromString(PingResponse.serializer(), json)
// Then
assertEquals(pingResponse, deserializedResponse)
}
// Then
assertEquals(pingResponse, deserializedResponse)
}
@Test
fun `Enhanced ping response serialization should work correctly`() {
// Given
val enhancedResponse = EnhancedPingResponse(
status = "OK",
timestamp = "2025-09-27T21:27:00Z",
service = "test-service",
circuitBreakerState = "CLOSED",
responseTime = 123L
)
@Test
fun `Enhanced ping response serialization should work correctly`() {
// Given
val enhancedResponse = EnhancedPingResponse(
status = "OK",
timestamp = "2025-09-27T21:27:00Z",
service = "test-service",
circuitBreakerState = "CLOSED",
responseTime = 123L
)
// When
val json = Json.encodeToString(EnhancedPingResponse.serializer(), enhancedResponse)
val deserializedResponse = Json.decodeFromString(EnhancedPingResponse.serializer(), json)
// When
val json = Json.encodeToString(EnhancedPingResponse.serializer(), enhancedResponse)
val deserializedResponse = Json.decodeFromString(EnhancedPingResponse.serializer(), json)
// Then
assertEquals(enhancedResponse, deserializedResponse)
}
// Then
assertEquals(enhancedResponse, deserializedResponse)
}
@Test
fun `Health response serialization should work correctly`() {
// Given
val healthResponse = HealthResponse(
status = "UP",
timestamp = "2025-09-27T21:27:00Z",
service = "test-service",
healthy = true
)
@Test
fun `Health response serialization should work correctly`() {
// Given
val healthResponse = HealthResponse(
status = "UP",
timestamp = "2025-09-27T21:27:00Z",
service = "test-service",
healthy = true
)
// When
val json = Json.encodeToString(HealthResponse.serializer(), healthResponse)
val deserializedResponse = Json.decodeFromString(HealthResponse.serializer(), json)
// When
val json = Json.encodeToString(HealthResponse.serializer(), healthResponse)
val deserializedResponse = Json.decodeFromString(HealthResponse.serializer(), json)
// Then
assertEquals(healthResponse, deserializedResponse)
}
// Then
assertEquals(healthResponse, deserializedResponse)
}
// Note: The HTTP request tests above demonstrate the test structure but are commented out
// because the current PingApiClient implementation doesn't support dependency injection
// of HttpClient. To make these tests fully functional, PingApiClient would need to be
// refactored to accept HttpClient as a constructor parameter:
//
// class PingApiClient(
// private val baseUrl: String = "http://localhost:8081",
// private val httpClient: HttpClient = HttpClient { ... }
// )
//
// This would enable full HTTP mocking and testing capabilities.
// Note: The HTTP request tests above demonstrate the test structure but are commented out
// because the current PingApiClient implementation doesn't support dependency injection
// of HttpClient. To make these tests fully functional, PingApiClient would need to be
// refactored to accept HttpClient as a constructor parameter:
//
// class PingApiClient(
// private val baseUrl: String = "http://localhost:8081",
// private val httpClient: HttpClient = HttpClient { ... }
// )
//
// This would enable full HTTP mocking and testing capabilities.
}
@@ -10,253 +10,253 @@ import kotlin.test.*
@OptIn(ExperimentalCoroutinesApi::class)
class PingViewModelTest {
private lateinit var viewModel: PingViewModel
private lateinit var testApiClient: TestPingApiClient
private val testDispatcher = StandardTestDispatcher()
private lateinit var viewModel: PingViewModel
private lateinit var testApiClient: TestPingApiClient
private val testDispatcher = StandardTestDispatcher()
@BeforeTest
fun setup() {
Dispatchers.setMain(testDispatcher)
testApiClient = TestPingApiClient()
viewModel = PingViewModel(testApiClient)
@BeforeTest
fun setup() {
Dispatchers.setMain(testDispatcher)
testApiClient = TestPingApiClient()
viewModel = PingViewModel(testApiClient)
}
@AfterTest
fun tearDown() {
Dispatchers.resetMain()
testApiClient.reset()
}
@Test
fun `initial state should be empty`() {
// Given & When - initial state
val initialState = viewModel.uiState
// Then
assertFalse(initialState.isLoading)
assertNull(initialState.simplePingResponse)
assertNull(initialState.enhancedPingResponse)
assertNull(initialState.healthResponse)
assertNull(initialState.errorMessage)
}
@Test
fun `performSimplePing should update state with success response`() = runTest(testDispatcher) {
// Given
val expectedResponse = PingResponse(
status = "OK",
timestamp = "2025-09-27T21:27:00Z",
service = "test-service"
)
testApiClient.simplePingResponse = expectedResponse
// When
viewModel.performSimplePing()
testDispatcher.scheduler.advanceUntilIdle()
// Then
val finalState = viewModel.uiState
assertFalse(finalState.isLoading)
assertEquals(expectedResponse, finalState.simplePingResponse)
assertNull(finalState.errorMessage)
assertTrue(testApiClient.simplePingCalled)
}
@Test
fun `performSimplePing should set loading state during execution`() = runTest(testDispatcher) {
// Given
testApiClient.simulateDelay = true
testApiClient.delayMs = 100
// When
viewModel.performSimplePing()
testDispatcher.scheduler.advanceTimeBy(1) // Allow the coroutine to start
// Then - should be loading during execution
assertTrue(viewModel.uiState.isLoading)
assertNull(viewModel.uiState.errorMessage)
// When - complete the operation
testDispatcher.scheduler.advanceUntilIdle()
// Then - should not be loading anymore
assertFalse(viewModel.uiState.isLoading)
}
@Test
fun `performSimplePing should handle error and update state`() = runTest(testDispatcher) {
// Given
val errorMessage = "Network error"
testApiClient.shouldThrowException = true
testApiClient.exceptionMessage = errorMessage
// When
viewModel.performSimplePing()
testDispatcher.scheduler.advanceUntilIdle()
// Then
val finalState = viewModel.uiState
assertFalse(finalState.isLoading)
assertNull(finalState.simplePingResponse)
assertEquals("Simple ping failed: $errorMessage", finalState.errorMessage)
assertTrue(testApiClient.simplePingCalled)
}
@Test
fun `performEnhancedPing should update state with success response`() = runTest(testDispatcher) {
// Given
val expectedResponse = EnhancedPingResponse(
status = "OK",
timestamp = "2025-09-27T21:27:00Z",
service = "test-service",
circuitBreakerState = "CLOSED",
responseTime = 42L
)
testApiClient.enhancedPingResponse = expectedResponse
// When
viewModel.performEnhancedPing(simulate = false)
testDispatcher.scheduler.advanceUntilIdle()
// Then
val finalState = viewModel.uiState
assertFalse(finalState.isLoading)
assertEquals(expectedResponse, finalState.enhancedPingResponse)
assertNull(finalState.errorMessage)
assertEquals(false, testApiClient.enhancedPingCalledWith)
}
@Test
fun `performEnhancedPing should handle simulate parameter correctly`() = runTest(testDispatcher) {
// When
viewModel.performEnhancedPing(simulate = true)
testDispatcher.scheduler.advanceUntilIdle()
// Then
assertEquals(true, testApiClient.enhancedPingCalledWith)
}
@Test
fun `performEnhancedPing should handle error and update state`() = runTest(testDispatcher) {
// Given
val errorMessage = "Enhanced ping error"
testApiClient.shouldThrowException = true
testApiClient.exceptionMessage = errorMessage
// When
viewModel.performEnhancedPing()
testDispatcher.scheduler.advanceUntilIdle()
// Then
val finalState = viewModel.uiState
assertFalse(finalState.isLoading)
assertNull(finalState.enhancedPingResponse)
assertEquals("Enhanced ping failed: $errorMessage", finalState.errorMessage)
}
@Test
fun `performHealthCheck should update state with success response`() = runTest(testDispatcher) {
// Given
val expectedResponse = HealthResponse(
status = "UP",
timestamp = "2025-09-27T21:27:00Z",
service = "test-service",
healthy = true
)
testApiClient.healthResponse = expectedResponse
// When
viewModel.performHealthCheck()
testDispatcher.scheduler.advanceUntilIdle()
// Then
val finalState = viewModel.uiState
assertFalse(finalState.isLoading)
assertEquals(expectedResponse, finalState.healthResponse)
assertNull(finalState.errorMessage)
assertTrue(testApiClient.healthCheckCalled)
}
@Test
fun `performHealthCheck should handle error and update state`() = runTest(testDispatcher) {
// Given
val errorMessage = "Health check error"
testApiClient.shouldThrowException = true
testApiClient.exceptionMessage = errorMessage
// When
viewModel.performHealthCheck()
testDispatcher.scheduler.advanceUntilIdle()
// Then
val finalState = viewModel.uiState
assertFalse(finalState.isLoading)
assertNull(finalState.healthResponse)
assertEquals("Health check failed: $errorMessage", finalState.errorMessage)
}
@Test
fun `clearError should remove error message from state`() {
// Given - set up an error state by simulating an error
testApiClient.shouldThrowException = true
runTest(testDispatcher) {
viewModel.performSimplePing()
testDispatcher.scheduler.advanceUntilIdle()
}
@AfterTest
fun tearDown() {
Dispatchers.resetMain()
testApiClient.reset()
}
// Verify error is present
assertNotNull(viewModel.uiState.errorMessage)
@Test
fun `initial state should be empty`() {
// Given & When - initial state
val initialState = viewModel.uiState
// When
viewModel.clearError()
// Then
assertFalse(initialState.isLoading)
assertNull(initialState.simplePingResponse)
assertNull(initialState.enhancedPingResponse)
assertNull(initialState.healthResponse)
assertNull(initialState.errorMessage)
}
// Then
assertNull(viewModel.uiState.errorMessage)
assertFalse(viewModel.uiState.isLoading)
}
@Test
fun `performSimplePing should update state with success response`() = runTest(testDispatcher) {
// Given
val expectedResponse = PingResponse(
status = "OK",
timestamp = "2025-09-27T21:27:00Z",
service = "test-service"
)
testApiClient.simplePingResponse = expectedResponse
@Test
fun `multiple operations should clear previous error messages`() = runTest(testDispatcher) {
// Given - first operation fails
testApiClient.shouldThrowException = true
viewModel.performSimplePing()
testDispatcher.scheduler.advanceUntilIdle()
assertNotNull(viewModel.uiState.errorMessage)
// When
viewModel.performSimplePing()
testDispatcher.scheduler.advanceUntilIdle()
// When - second operation succeeds
testApiClient.shouldThrowException = false
val successResponse = PingResponse("SUCCESS", "2025-09-27T21:27:00Z", "test-service")
testApiClient.simplePingResponse = successResponse
viewModel.performSimplePing()
testDispatcher.scheduler.advanceUntilIdle()
// Then
val finalState = viewModel.uiState
assertFalse(finalState.isLoading)
assertEquals(expectedResponse, finalState.simplePingResponse)
assertNull(finalState.errorMessage)
assertTrue(testApiClient.simplePingCalled)
}
// Then - error should be cleared
assertNull(viewModel.uiState.errorMessage)
assertEquals(successResponse, viewModel.uiState.simplePingResponse)
}
@Test
fun `performSimplePing should set loading state during execution`() = runTest(testDispatcher) {
// Given
testApiClient.simulateDelay = true
testApiClient.delayMs = 100
@Test
fun `loading state should be false after successful operation`() = runTest(testDispatcher) {
// Given
viewModel.performSimplePing()
testDispatcher.scheduler.advanceUntilIdle()
// When
viewModel.performSimplePing()
testDispatcher.scheduler.advanceTimeBy(1) // Allow the coroutine to start
// Then
assertFalse(viewModel.uiState.isLoading)
}
// Then - should be loading during execution
assertTrue(viewModel.uiState.isLoading)
assertNull(viewModel.uiState.errorMessage)
@Test
fun `all operations should call respective API methods`() = runTest(testDispatcher) {
// When
viewModel.performSimplePing()
viewModel.performEnhancedPing(true)
viewModel.performHealthCheck()
testDispatcher.scheduler.advanceUntilIdle()
// When - complete the operation
testDispatcher.scheduler.advanceUntilIdle()
// Then - should not be loading anymore
assertFalse(viewModel.uiState.isLoading)
}
@Test
fun `performSimplePing should handle error and update state`() = runTest(testDispatcher) {
// Given
val errorMessage = "Network error"
testApiClient.shouldThrowException = true
testApiClient.exceptionMessage = errorMessage
// When
viewModel.performSimplePing()
testDispatcher.scheduler.advanceUntilIdle()
// Then
val finalState = viewModel.uiState
assertFalse(finalState.isLoading)
assertNull(finalState.simplePingResponse)
assertEquals("Simple ping failed: $errorMessage", finalState.errorMessage)
assertTrue(testApiClient.simplePingCalled)
}
@Test
fun `performEnhancedPing should update state with success response`() = runTest(testDispatcher) {
// Given
val expectedResponse = EnhancedPingResponse(
status = "OK",
timestamp = "2025-09-27T21:27:00Z",
service = "test-service",
circuitBreakerState = "CLOSED",
responseTime = 42L
)
testApiClient.enhancedPingResponse = expectedResponse
// When
viewModel.performEnhancedPing(simulate = false)
testDispatcher.scheduler.advanceUntilIdle()
// Then
val finalState = viewModel.uiState
assertFalse(finalState.isLoading)
assertEquals(expectedResponse, finalState.enhancedPingResponse)
assertNull(finalState.errorMessage)
assertEquals(false, testApiClient.enhancedPingCalledWith)
}
@Test
fun `performEnhancedPing should handle simulate parameter correctly`() = runTest(testDispatcher) {
// When
viewModel.performEnhancedPing(simulate = true)
testDispatcher.scheduler.advanceUntilIdle()
// Then
assertEquals(true, testApiClient.enhancedPingCalledWith)
}
@Test
fun `performEnhancedPing should handle error and update state`() = runTest(testDispatcher) {
// Given
val errorMessage = "Enhanced ping error"
testApiClient.shouldThrowException = true
testApiClient.exceptionMessage = errorMessage
// When
viewModel.performEnhancedPing()
testDispatcher.scheduler.advanceUntilIdle()
// Then
val finalState = viewModel.uiState
assertFalse(finalState.isLoading)
assertNull(finalState.enhancedPingResponse)
assertEquals("Enhanced ping failed: $errorMessage", finalState.errorMessage)
}
@Test
fun `performHealthCheck should update state with success response`() = runTest(testDispatcher) {
// Given
val expectedResponse = HealthResponse(
status = "UP",
timestamp = "2025-09-27T21:27:00Z",
service = "test-service",
healthy = true
)
testApiClient.healthResponse = expectedResponse
// When
viewModel.performHealthCheck()
testDispatcher.scheduler.advanceUntilIdle()
// Then
val finalState = viewModel.uiState
assertFalse(finalState.isLoading)
assertEquals(expectedResponse, finalState.healthResponse)
assertNull(finalState.errorMessage)
assertTrue(testApiClient.healthCheckCalled)
}
@Test
fun `performHealthCheck should handle error and update state`() = runTest(testDispatcher) {
// Given
val errorMessage = "Health check error"
testApiClient.shouldThrowException = true
testApiClient.exceptionMessage = errorMessage
// When
viewModel.performHealthCheck()
testDispatcher.scheduler.advanceUntilIdle()
// Then
val finalState = viewModel.uiState
assertFalse(finalState.isLoading)
assertNull(finalState.healthResponse)
assertEquals("Health check failed: $errorMessage", finalState.errorMessage)
}
@Test
fun `clearError should remove error message from state`() {
// Given - set up an error state by simulating an error
testApiClient.shouldThrowException = true
runTest(testDispatcher) {
viewModel.performSimplePing()
testDispatcher.scheduler.advanceUntilIdle()
}
// Verify error is present
assertNotNull(viewModel.uiState.errorMessage)
// When
viewModel.clearError()
// Then
assertNull(viewModel.uiState.errorMessage)
assertFalse(viewModel.uiState.isLoading)
}
@Test
fun `multiple operations should clear previous error messages`() = runTest(testDispatcher) {
// Given - first operation fails
testApiClient.shouldThrowException = true
viewModel.performSimplePing()
testDispatcher.scheduler.advanceUntilIdle()
assertNotNull(viewModel.uiState.errorMessage)
// When - second operation succeeds
testApiClient.shouldThrowException = false
val successResponse = PingResponse("SUCCESS", "2025-09-27T21:27:00Z", "test-service")
testApiClient.simplePingResponse = successResponse
viewModel.performSimplePing()
testDispatcher.scheduler.advanceUntilIdle()
// Then - error should be cleared
assertNull(viewModel.uiState.errorMessage)
assertEquals(successResponse, viewModel.uiState.simplePingResponse)
}
@Test
fun `loading state should be false after successful operation`() = runTest(testDispatcher) {
// Given
viewModel.performSimplePing()
testDispatcher.scheduler.advanceUntilIdle()
// Then
assertFalse(viewModel.uiState.isLoading)
}
@Test
fun `all operations should call respective API methods`() = runTest(testDispatcher) {
// When
viewModel.performSimplePing()
viewModel.performEnhancedPing(true)
viewModel.performHealthCheck()
testDispatcher.scheduler.advanceUntilIdle()
// Then
assertTrue(testApiClient.simplePingCalled)
assertEquals(true, testApiClient.enhancedPingCalledWith)
assertTrue(testApiClient.healthCheckCalled)
assertEquals(3, testApiClient.callCount)
}
// Then
assertTrue(testApiClient.simplePingCalled)
assertEquals(true, testApiClient.enhancedPingCalledWith)
assertTrue(testApiClient.healthCheckCalled)
assertEquals(3, testApiClient.callCount)
}
}
@@ -11,95 +11,95 @@ import at.mocode.ping.api.HealthResponse
*/
class TestPingApiClient : PingApi {
// Test configuration properties
var shouldThrowException = false
var exceptionMessage = "Test exception"
var simulateDelay = false
var delayMs = 100L
// Test configuration properties
var shouldThrowException = false
var exceptionMessage = "Test exception"
var simulateDelay = false
var delayMs = 100L
// Response configuration
var simplePingResponse: PingResponse? = null
var enhancedPingResponse: EnhancedPingResponse? = null
var healthResponse: HealthResponse? = null
// Response configuration
var simplePingResponse: PingResponse? = null
var enhancedPingResponse: EnhancedPingResponse? = null
var healthResponse: HealthResponse? = null
// Call tracking
var simplePingCalled = false
var enhancedPingCalledWith: Boolean? = null
var healthCheckCalled = false
var callCount = 0
// Call tracking
var simplePingCalled = false
var enhancedPingCalledWith: Boolean? = null
var healthCheckCalled = false
var callCount = 0
override suspend fun simplePing(): PingResponse {
simplePingCalled = true
callCount++
override suspend fun simplePing(): PingResponse {
simplePingCalled = true
callCount++
if (simulateDelay) {
kotlinx.coroutines.delay(delayMs)
}
if (shouldThrowException) {
throw Exception(exceptionMessage)
}
return simplePingResponse ?: PingResponse(
status = "OK",
timestamp = "2025-09-27T21:27:00Z",
service = "test-ping-service"
)
if (simulateDelay) {
kotlinx.coroutines.delay(delayMs)
}
override suspend fun enhancedPing(simulate: Boolean): EnhancedPingResponse {
enhancedPingCalledWith = simulate
callCount++
if (simulateDelay) {
kotlinx.coroutines.delay(delayMs)
}
if (shouldThrowException) {
throw Exception(exceptionMessage)
}
return enhancedPingResponse ?: EnhancedPingResponse(
status = "OK",
timestamp = "2025-09-27T21:27:00Z",
service = "test-ping-service",
circuitBreakerState = "CLOSED",
responseTime = 42L
)
if (shouldThrowException) {
throw Exception(exceptionMessage)
}
override suspend fun healthCheck(): HealthResponse {
healthCheckCalled = true
callCount++
return simplePingResponse ?: PingResponse(
status = "OK",
timestamp = "2025-09-27T21:27:00Z",
service = "test-ping-service"
)
}
if (simulateDelay) {
kotlinx.coroutines.delay(delayMs)
}
override suspend fun enhancedPing(simulate: Boolean): EnhancedPingResponse {
enhancedPingCalledWith = simulate
callCount++
if (shouldThrowException) {
throw Exception(exceptionMessage)
}
return healthResponse ?: HealthResponse(
status = "UP",
timestamp = "2025-09-27T21:27:00Z",
service = "test-ping-service",
healthy = true
)
if (simulateDelay) {
kotlinx.coroutines.delay(delayMs)
}
// Test utilities
fun reset() {
shouldThrowException = false
exceptionMessage = "Test exception"
simulateDelay = false
delayMs = 100L
simplePingResponse = null
enhancedPingResponse = null
healthResponse = null
simplePingCalled = false
enhancedPingCalledWith = null
healthCheckCalled = false
callCount = 0
if (shouldThrowException) {
throw Exception(exceptionMessage)
}
return enhancedPingResponse ?: EnhancedPingResponse(
status = "OK",
timestamp = "2025-09-27T21:27:00Z",
service = "test-ping-service",
circuitBreakerState = "CLOSED",
responseTime = 42L
)
}
override suspend fun healthCheck(): HealthResponse {
healthCheckCalled = true
callCount++
if (simulateDelay) {
kotlinx.coroutines.delay(delayMs)
}
if (shouldThrowException) {
throw Exception(exceptionMessage)
}
return healthResponse ?: HealthResponse(
status = "UP",
timestamp = "2025-09-27T21:27:00Z",
service = "test-ping-service",
healthy = true
)
}
// Test utilities
fun reset() {
shouldThrowException = false
exceptionMessage = "Test exception"
simulateDelay = false
delayMs = 100L
simplePingResponse = null
enhancedPingResponse = null
healthResponse = null
simplePingCalled = false
enhancedPingCalledWith = null
healthCheckCalled = false
callCount = 0
}
}