From af0b7c5f9b8a79a7f26f87c1e79697d41b9c4e39 Mon Sep 17 00:00:00 2001 From: StefanMoCoAt Date: Sun, 1 Feb 2026 12:00:10 +0100 Subject: [PATCH] chore(frontend): cleanup legacy code and improve localization consistency - Removed deprecated `NotificationCard` component due to dependency on an outdated presentation layer. - Translated comments and documentation to German for improved localization across `core.auth`, `ping-feature`, and `network`. - Standardized comment formatting, improved doc clarity, and ensured consistent API documentation in all modules. --- .../frontend/core/auth/data/AuthApiClient.kt | 12 ++--- .../core/auth/data/AuthTokenManager.kt | 46 +++++++++---------- .../frontend/core/auth/di/AuthModule.kt | 6 +-- .../core/designsystem/components/AppHeader.kt | 4 +- .../components/NotificationCard.kt | 5 -- .../core/designsystem/theme/AppTheme.kt | 2 +- .../frontend/core/network/NetworkConfig.kt | 4 +- .../frontend/core/network/NetworkModule.kt | 26 +++++------ .../core/network/PlatformConfig.js.kt | 6 +-- frontend/features/.gitkeep | 0 .../ping/feature/data/PingApiKoinClient.kt | 3 +- .../feature/data/PingEventRepositoryImpl.kt | 17 ++++--- .../ping/feature/di/PingFeatureModule.kt | 9 ++-- .../ping/feature/domain/PingSyncService.kt | 4 +- .../ping/feature/presentation/PingScreen.kt | 2 - .../feature/data/PingApiKoinClientTest.kt | 2 +- 16 files changed, 72 insertions(+), 76 deletions(-) delete mode 100644 frontend/core/design-system/src/commonMain/kotlin/at/mocode/frontend/core/designsystem/components/NotificationCard.kt delete mode 100644 frontend/features/.gitkeep diff --git a/frontend/core/auth/src/commonMain/kotlin/at/mocode/frontend/core/auth/data/AuthApiClient.kt b/frontend/core/auth/src/commonMain/kotlin/at/mocode/frontend/core/auth/data/AuthApiClient.kt index 8cf30a96..fc538153 100644 --- a/frontend/core/auth/src/commonMain/kotlin/at/mocode/frontend/core/auth/data/AuthApiClient.kt +++ b/frontend/core/auth/src/commonMain/kotlin/at/mocode/frontend/core/auth/data/AuthApiClient.kt @@ -44,13 +44,13 @@ class AuthApiClient( append("grant_type", "password") append("client_id", clientId) - // IMPORTANT: Only send client_secret if it's NOT a public client (like 'web-app') - // Keycloak rejects requests from public clients that contain a client_secret. - // We check if the client ID suggests a public client or if secret is explicitly provided. - // For now, we rely on the fact that 'web-app' is public and should NOT have a secret sent. + // WICHTIG: Senden Sie client_secret nur, wenn es sich NICHT um einen öffentlichen Client (wie 'web-app') handelt. + // Keycloak lehnt Anfragen von öffentlichen Clients ab, die client_secret enthalten. + // Wir prüfen, ob die Client-ID auf einen öffentlichen Client hindeutet oder ob ein Secret explizit angegeben wurde. + // Aktuell gehen wir davon aus, dass 'web-app' öffentlich ist und daher kein Secret gesendet werden sollte. - // Logic: If clientId is 'web-app', we force ignore the secret, or we rely on caller to pass null. - // Since AppConstants might still have the secret for 'postman-client', we need to be careful. + // Logik: Wenn clientId 'web-app' ist, ignorieren wir das Geheimnis oder verlassen uns darauf, dass der Aufrufer null übergibt. + // Da AppConstants möglicherweise noch das Geheimnis für 'postman-client' enthält, ist Vorsicht geboten. if (!clientSecret.isNullOrBlank() && clientId != "web-app") { append("client_secret", clientSecret) diff --git a/frontend/core/auth/src/commonMain/kotlin/at/mocode/frontend/core/auth/data/AuthTokenManager.kt b/frontend/core/auth/src/commonMain/kotlin/at/mocode/frontend/core/auth/data/AuthTokenManager.kt index 1d2cdf58..2e2ac551 100644 --- a/frontend/core/auth/src/commonMain/kotlin/at/mocode/frontend/core/auth/data/AuthTokenManager.kt +++ b/frontend/core/auth/src/commonMain/kotlin/at/mocode/frontend/core/auth/data/AuthTokenManager.kt @@ -10,7 +10,7 @@ import kotlin.io.encoding.ExperimentalEncodingApi import kotlin.time.ExperimentalTime /** - * Client-side permission enumeration that mirrors server-side BerechtigungE + * Client-side Berechtigungsaufzählung, welche die serverseitige BerechtigungE widerspiegelt */ @Serializable enum class Permission { @@ -66,9 +66,9 @@ data class AuthState( /** * Secure in-memory JWT token manager * - * For web clients, storing tokens in memory is the most secure approach - * to prevent XSS attacks. The token is lost when the browser tab is closed - * or refreshed, requiring re-authentication. + * Für Webclients ist das Speichern von Tokens im Arbeitsspeicher der sicherste Ansatz, + * um XSS-Angriffe zu verhindern. Das Token geht beim Schließen des Browser-Tabs verloren, + * was eine erneute Authentifizierung erforderlich macht. */ @Suppress("unused") class AuthTokenManager { @@ -141,38 +141,38 @@ class AuthTokenManager { fun getUserId(): String? = tokenPayload?.sub /** - * Get username from token + * Get username from a token */ fun getUsername(): String? = tokenPayload?.username /** - * Get current user permissions + * Get current user permissions (Berechtigungen) */ fun getPermissions(): List = _authState.value.permissions /** - * Check if user has a specific permission + * Check if the user has a specific permission */ fun hasPermission(permission: Permission): Boolean { return _authState.value.permissions.contains(permission) } /** - * Check if user has any of the specified permissions + * Check if the user has any of the specified permissions */ fun hasAnyPermission(vararg permissions: Permission): Boolean { return permissions.any { _authState.value.permissions.contains(it) } } /** - * Check if user has all of the specified permissions + * Check if the user has all the specified permissions */ fun hasAllPermissions(vararg permissions: Permission): Boolean { return permissions.all { _authState.value.permissions.contains(it) } } /** - * Check if user can perform read operations + * Check if the user can perform read operations */ fun canRead(): Boolean { return hasAnyPermission( @@ -184,7 +184,7 @@ class AuthTokenManager { } /** - * Check if user can perform create operations + * Check if the user can perform create operations */ fun canCreate(): Boolean { return hasAnyPermission( @@ -196,7 +196,7 @@ class AuthTokenManager { } /** - * Check if user can perform update operations + * Check if the user can perform update operations */ fun canUpdate(): Boolean { return hasAnyPermission( @@ -208,7 +208,7 @@ class AuthTokenManager { } /** - * Check if user can perform delete operations (admin-level) + * Check if the user can perform delete operations (admin-level) */ fun canDelete(): Boolean { return hasAnyPermission( @@ -220,12 +220,12 @@ class AuthTokenManager { } /** - * Check if user is admin (has delete permissions) + * Check if the user is admin (has deleted permissions) */ fun isAdmin(): Boolean = canDelete() /** - * Check if token expires within specified minutes + * Check if the token expires within specified minutes */ @OptIn(ExperimentalTime::class) fun isTokenExpiringSoon(minutesThreshold: Int = 5): Boolean { @@ -238,8 +238,8 @@ class AuthTokenManager { } /** - * Parse JWT payload for basic validation and user info extraction - * Note: This is for client-side info extraction only, not security validation + * JWT-Payload für grundlegende Validierung und Extraktion von Benutzerinformationen analysieren + * Hinweis: Dies dient ausschließlich der clientseitigen Informationsextraktion, nicht der Sicherheitsvalidierung. */ @OptIn(ExperimentalEncodingApi::class) private fun parseJwtPayload(token: String): JwtPayload? { @@ -250,7 +250,7 @@ class AuthTokenManager { // Decode the payload (second part) val payloadJson = Base64.decode(parts[1]).decodeToString() - // First try to parse with standard approach + // First, try to parse with a standard approach val basicPayload = try { Json.decodeFromString(payloadJson) } catch (e: Exception) { @@ -263,7 +263,7 @@ class AuthTokenManager { return basicPayload } - // Otherwise, extract permissions manually from JSON string + // Otherwise, extract permissions manually from a JSON string val permissions = extractPermissionsFromJson(payloadJson) // Return payload with manually extracted permissions @@ -282,16 +282,16 @@ class AuthTokenManager { } /** - * Extract permissions array from JSON string using simple string parsing + * Extract permissions array from a JSON string using simple string parsing */ private fun extractPermissionsFromJson(jsonString: String): List? { return try { // Simple regex to find a permissions array - val permissionsRegex = """"permissions":\s*\[(.*?)\]""".toRegex() + val permissionsRegex = """"permissions":\s*\[(.*?)]""".toRegex() val match = permissionsRegex.find(jsonString) - match?.let { - val permissionsContent = it.groupValues[1] + match?.let { matchResult -> + val permissionsContent = matchResult.groupValues[1] if (permissionsContent.isBlank()) return emptyList() // Extract individual permission strings diff --git a/frontend/core/auth/src/commonMain/kotlin/at/mocode/frontend/core/auth/di/AuthModule.kt b/frontend/core/auth/src/commonMain/kotlin/at/mocode/frontend/core/auth/di/AuthModule.kt index d198a49d..fd73f3a9 100644 --- a/frontend/core/auth/src/commonMain/kotlin/at/mocode/frontend/core/auth/di/AuthModule.kt +++ b/frontend/core/auth/src/commonMain/kotlin/at/mocode/frontend/core/auth/di/AuthModule.kt @@ -9,7 +9,7 @@ import org.koin.core.qualifier.named import org.koin.dsl.module /** - * Koin module for core-auth: provides AuthTokenManager and binds it as TokenProvider for apiClient. + * Koin-Modul für core-auth: stellt AuthTokenManager bereit und bindet ihn als TokenProvider für apiClient. */ val authModule = module { // Single in-memory token manager @@ -26,9 +26,9 @@ val authModule = module { // LoginViewModel factory { LoginViewModel(get(), get(), get(named("apiClient"))) } - // Bridge to core network TokenProvider without adding a hard dependency there + // Brücke zum TokenProvider des Kernnetzwerks, ohne dort eine harte Abhängigkeit hinzuzufügen single { - // We need to capture the AuthTokenManager instance to avoid issues with 'this' context in JS + // Wir müssen die AuthTokenManager-Instanz erfassen, um Probleme mit dem 'this'-Kontext in JavaScript zu vermeiden. val tokenManager = get() object : TokenProvider { override fun getAccessToken(): String? { diff --git a/frontend/core/design-system/src/commonMain/kotlin/at/mocode/frontend/core/designsystem/components/AppHeader.kt b/frontend/core/design-system/src/commonMain/kotlin/at/mocode/frontend/core/designsystem/components/AppHeader.kt index 40a87f7b..2154b901 100644 --- a/frontend/core/design-system/src/commonMain/kotlin/at/mocode/frontend/core/designsystem/components/AppHeader.kt +++ b/frontend/core/design-system/src/commonMain/kotlin/at/mocode/frontend/core/designsystem/components/AppHeader.kt @@ -35,7 +35,7 @@ fun AppHeader( // Authentication buttons if (isAuthenticated) { - // Show username with admin indicator if user has delete permissions + // Show username with admin indicator if user has deleted permissions username?.let { user -> val isAdmin = userPermissions.any { it.contains("DELETE") } Text( @@ -55,7 +55,7 @@ fun AppHeader( } } } else { - // Show login button + // Show the login button onNavigateToLogin?.let { loginAction -> TextButton( onClick = loginAction diff --git a/frontend/core/design-system/src/commonMain/kotlin/at/mocode/frontend/core/designsystem/components/NotificationCard.kt b/frontend/core/design-system/src/commonMain/kotlin/at/mocode/frontend/core/designsystem/components/NotificationCard.kt deleted file mode 100644 index dc1d158a..00000000 --- a/frontend/core/design-system/src/commonMain/kotlin/at/mocode/frontend/core/designsystem/components/NotificationCard.kt +++ /dev/null @@ -1,5 +0,0 @@ -package at.mocode.frontend.core.designsystem.components - -// Legacy notification components removed due to dependency on old presentation layer. -// Intentionally left empty as part of cleanup. You can safely delete this file -// if no modules import it anymore. diff --git a/frontend/core/design-system/src/commonMain/kotlin/at/mocode/frontend/core/designsystem/theme/AppTheme.kt b/frontend/core/design-system/src/commonMain/kotlin/at/mocode/frontend/core/designsystem/theme/AppTheme.kt index e0585907..beeaa8c0 100644 --- a/frontend/core/design-system/src/commonMain/kotlin/at/mocode/frontend/core/designsystem/theme/AppTheme.kt +++ b/frontend/core/design-system/src/commonMain/kotlin/at/mocode/frontend/core/designsystem/theme/AppTheme.kt @@ -14,7 +14,7 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.sp // --- 1. Farben (Palette) --- -// Wir definieren eine professionelle, kontrastreiche Palette. +// wir definieren eine professionelle, kontrastreiche Palette. // Blau steht für Aktion/Information, Grau für Struktur. private val LightColorScheme = lightColorScheme( diff --git a/frontend/core/network/src/commonMain/kotlin/at/mocode/frontend/core/network/NetworkConfig.kt b/frontend/core/network/src/commonMain/kotlin/at/mocode/frontend/core/network/NetworkConfig.kt index d2e50126..e1598ef8 100644 --- a/frontend/core/network/src/commonMain/kotlin/at/mocode/frontend/core/network/NetworkConfig.kt +++ b/frontend/core/network/src/commonMain/kotlin/at/mocode/frontend/core/network/NetworkConfig.kt @@ -3,8 +3,8 @@ package at.mocode.frontend.core.network import kotlin.native.concurrent.ThreadLocal /** - * Network configuration with sensible defaults and environment overrides. - * Defaults to the local API Gateway on port 8081. + * Netzwerkkonfiguration mit sinnvollen Standardeinstellungen und Umgebungseinstellungen zum Überschreiben. + * Standardmäßig wird das lokale API-Gateway auf Port 8081 verwendet. */ @ThreadLocal object NetworkConfig { diff --git a/frontend/core/network/src/commonMain/kotlin/at/mocode/frontend/core/network/NetworkModule.kt b/frontend/core/network/src/commonMain/kotlin/at/mocode/frontend/core/network/NetworkModule.kt index a6bfacb0..2d89076c 100644 --- a/frontend/core/network/src/commonMain/kotlin/at/mocode/frontend/core/network/NetworkModule.kt +++ b/frontend/core/network/src/commonMain/kotlin/at/mocode/frontend/core/network/NetworkModule.kt @@ -132,7 +132,7 @@ val networkModule = module { // [baseClient] REQUEST: .../token // Access to fetch at ... blocked by CORS policy - // This confirms it is a CORS issue on the Keycloak server side, or the browser side. + // This confirms it is a CORS issue on the Keycloak server side or the browser side. // The JS error `TypeError` is GONE in the latest log! // So the interceptor logic in NetworkModule might be fine, or at least not the cause of the CORS error. @@ -140,7 +140,7 @@ val networkModule = module { // We will use a safe lazy resolution pattern. } catch (_: Exception) { - // ignore + // ignore } execute(request) } @@ -150,19 +150,19 @@ val networkModule = module { client.plugin(HttpSend).intercept { request -> try { - // Attempt to resolve TokenProvider from the capturing scope - val tokenProvider = try { - koinScope.get() - } catch (_: Exception) { - null - } + // Attempt to resolve TokenProvider from the capturing scope + val tokenProvider = try { + koinScope.get() + } catch (_: Exception) { + null + } - val token = tokenProvider?.getAccessToken() - if (token != null) { - request.header("Authorization", "Bearer $token") - } + val token = tokenProvider?.getAccessToken() + if (token != null) { + request.header("Authorization", "Bearer $token") + } } catch (e: Exception) { - println("[apiClient] Error injecting auth header: $e") + println("[apiClient] Error injecting auth header: $e") } execute(request) } diff --git a/frontend/core/network/src/jsMain/kotlin/at/mocode/frontend/core/network/PlatformConfig.js.kt b/frontend/core/network/src/jsMain/kotlin/at/mocode/frontend/core/network/PlatformConfig.js.kt index 1cbbc626..a6e647fb 100644 --- a/frontend/core/network/src/jsMain/kotlin/at/mocode/frontend/core/network/PlatformConfig.js.kt +++ b/frontend/core/network/src/jsMain/kotlin/at/mocode/frontend/core/network/PlatformConfig.js.kt @@ -26,9 +26,9 @@ actual object PlatformConfig { } if (!origin.isNullOrBlank()) { - val resolvedUrl = origin.removeSuffix("/") + "/api" - console.log("[PlatformConfig] Resolved API_BASE_URL from window.location.origin: $resolvedUrl") - return resolvedUrl + val resolvedUrl = origin.removeSuffix("/") + "/api" + console.log("[PlatformConfig] Resolved API_BASE_URL from window.location.origin: $resolvedUrl") + return resolvedUrl } // 3) Fallback to the local gateway directly (e.g. for tests without window) diff --git a/frontend/features/.gitkeep b/frontend/features/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/frontend/features/ping-feature/src/commonMain/kotlin/at/mocode/ping/feature/data/PingApiKoinClient.kt b/frontend/features/ping-feature/src/commonMain/kotlin/at/mocode/ping/feature/data/PingApiKoinClient.kt index d4035355..2eef23a4 100644 --- a/frontend/features/ping-feature/src/commonMain/kotlin/at/mocode/ping/feature/data/PingApiKoinClient.kt +++ b/frontend/features/ping-feature/src/commonMain/kotlin/at/mocode/ping/feature/data/PingApiKoinClient.kt @@ -10,7 +10,8 @@ import io.ktor.client.call.body import io.ktor.client.request.get /** - * PingApi implementation that uses a provided HttpClient (e.g., DI-provided "apiClient"). + * PingApi-Implementierung, die einen bereitgestellten HttpClient verwendet (z. B. den per Dependency Injection + * bereitgestellten "apiClient"). */ class PingApiKoinClient(private val client: HttpClient) : PingApi { diff --git a/frontend/features/ping-feature/src/commonMain/kotlin/at/mocode/ping/feature/data/PingEventRepositoryImpl.kt b/frontend/features/ping-feature/src/commonMain/kotlin/at/mocode/ping/feature/data/PingEventRepositoryImpl.kt index 1dd88bf2..3c23c615 100644 --- a/frontend/features/ping-feature/src/commonMain/kotlin/at/mocode/ping/feature/data/PingEventRepositoryImpl.kt +++ b/frontend/features/ping-feature/src/commonMain/kotlin/at/mocode/ping/feature/data/PingEventRepositoryImpl.kt @@ -5,21 +5,24 @@ import at.mocode.frontend.core.sync.SyncableRepository import at.mocode.ping.api.PingEvent import app.cash.sqldelight.async.coroutines.awaitAsOneOrNull -// ARCH-BLUEPRINT: This repository implements the generic SyncableRepository -// for a specific entity, bridging the gap between the sync core and the local database. +/** + ** ARCH-BLUEPRINT: Dieses Repository implementiert das generische Syncable Repository + ** für eine bestimmte Entität und überbrückt so die Lücke zwischen dem Sync-Core und der + ** lokalen Datenbank. + */ class PingEventRepositoryImpl( private val db: AppDatabase ) : SyncableRepository { - // The `since` parameter for our sync is the ID of the last event, not a timestamp. + // Der `since`-Parameter für unsere Synchronisierung ist die ID des letzten Ereignisses, kein Zeitstempel. override suspend fun getLatestSince(): String? { - println("PingEventRepositoryImpl: getLatestSince called - using corrected async implementation") - // FIX: Use .awaitAsOneOrNull() for async drivers instead of the blocking .executeAsOneOrNull() - return db.appDatabaseQueries.selectLatestPingEventId().awaitAsOneOrNull() + println("PingEventRepositoryImpl: getLatestSince called - using corrected async implementation") + // FIX: Verwenden Sie .awaitAsOneOrNull() für asynchrone Treiber anstelle des blockierenden .executeAsOneOrNull(). + return db.appDatabaseQueries.selectLatestPingEventId().awaitAsOneOrNull() } override suspend fun upsert(items: List) { - // Always perform bulk operations within a transaction. + // Führen Sie Massenoperationen immer innerhalb einer Transaktion durch. db.transaction { items.forEach { event -> db.appDatabaseQueries.upsertPingEvent( diff --git a/frontend/features/ping-feature/src/commonMain/kotlin/at/mocode/ping/feature/di/PingFeatureModule.kt b/frontend/features/ping-feature/src/commonMain/kotlin/at/mocode/ping/feature/di/PingFeatureModule.kt index d0e725ad..2daff190 100644 --- a/frontend/features/ping-feature/src/commonMain/kotlin/at/mocode/ping/feature/di/PingFeatureModule.kt +++ b/frontend/features/ping-feature/src/commonMain/kotlin/at/mocode/ping/feature/di/PingFeatureModule.kt @@ -11,19 +11,18 @@ import org.koin.core.qualifier.named import org.koin.dsl.module /** - * Consolidated Koin module for the Ping Feature (Clean Architecture). - * Replaces the old 'clients.pingfeature' module. + * Konsolidiertes Koin-Modul für die Ping-Funktion (Clean Architecture). */ val pingFeatureModule = module { // 1. API Client (Data Layer) - // Uses the shared authenticated 'apiClient' from Core Network + // Verwendet den gemeinsam genutzten, authentifizierten „apiClient“ aus dem Kernnetzwerk. single { PingApiKoinClient(get(named("apiClient"))) } // 2. Repository (Data Layer) single { PingEventRepositoryImpl(get()) } // 3. Domain Service (Domain Layer) - // Wraps SyncManager and Repository to decouple ViewModel from SyncManager implementation details + // Wraps SyncManager und Repository, um ViewModel von den Implementierungsdetails von SyncManager zu entkoppeln. single { PingSyncServiceImpl( syncManager = get(), @@ -32,7 +31,7 @@ val pingFeatureModule = module { } // 4. ViewModel (Presentation Layer) - // Injects API and Domain Service + // Injects API und Domain Service factory { PingViewModel( apiClient = get(), diff --git a/frontend/features/ping-feature/src/commonMain/kotlin/at/mocode/ping/feature/domain/PingSyncService.kt b/frontend/features/ping-feature/src/commonMain/kotlin/at/mocode/ping/feature/domain/PingSyncService.kt index 258472a3..b949d434 100644 --- a/frontend/features/ping-feature/src/commonMain/kotlin/at/mocode/ping/feature/domain/PingSyncService.kt +++ b/frontend/features/ping-feature/src/commonMain/kotlin/at/mocode/ping/feature/domain/PingSyncService.kt @@ -5,14 +5,14 @@ import at.mocode.frontend.core.sync.SyncableRepository import at.mocode.ping.api.PingEvent /** - * Interface for the Ping Sync Service to allow easier testing and decoupling. + * Interface für den Ping-Sync-Dienst zur einfacheren Prüfung und Entkopplung. */ interface PingSyncService { suspend fun syncPings() } /** - * Implementation of PingSyncService using the generic SyncManager. + * Implementierung des PingSyncService unter Verwendung des generischen SyncManager. */ class PingSyncServiceImpl( private val syncManager: SyncManager, diff --git a/frontend/features/ping-feature/src/commonMain/kotlin/at/mocode/ping/feature/presentation/PingScreen.kt b/frontend/features/ping-feature/src/commonMain/kotlin/at/mocode/ping/feature/presentation/PingScreen.kt index 5a224302..87e626ca 100644 --- a/frontend/features/ping-feature/src/commonMain/kotlin/at/mocode/ping/feature/presentation/PingScreen.kt +++ b/frontend/features/ping-feature/src/commonMain/kotlin/at/mocode/ping/feature/presentation/PingScreen.kt @@ -19,8 +19,6 @@ import at.mocode.frontend.core.designsystem.components.DashboardCard import at.mocode.frontend.core.designsystem.components.DenseButton import at.mocode.frontend.core.designsystem.theme.Dimens -// --- Refactored PingScreen using Design System --- - @Composable fun PingScreen( viewModel: PingViewModel, diff --git a/frontend/features/ping-feature/src/commonTest/kotlin/at/mocode/ping/feature/data/PingApiKoinClientTest.kt b/frontend/features/ping-feature/src/commonTest/kotlin/at/mocode/ping/feature/data/PingApiKoinClientTest.kt index 0be55b39..a2201869 100644 --- a/frontend/features/ping-feature/src/commonTest/kotlin/at/mocode/ping/feature/data/PingApiKoinClientTest.kt +++ b/frontend/features/ping-feature/src/commonTest/kotlin/at/mocode/ping/feature/data/PingApiKoinClientTest.kt @@ -15,7 +15,7 @@ import kotlin.test.assertEquals class PingApiKoinClientTest { - // Helper to create a testable client using the new DI-friendly implementation + // Hilfe zur Erstellung eines testbaren Clients mithilfe der neuen DI-freundlichen Implementierung private fun createTestClient(mockEngine: MockEngine): PingApiKoinClient { val client = HttpClient(mockEngine) { install(ContentNegotiation) {