chore: migriere ping-feature Modul auf Module Structure Blueprint, füge Fake-Repository und neue Integrationstests hinzu, dokumentiere Änderungen
This commit is contained in:
@@ -0,0 +1,29 @@
|
|||||||
|
# Journal: Fehlerbehebung PingSyncIntegrationTest nach Blueprint-Migration
|
||||||
|
|
||||||
|
**Datum:** 19. April 2026
|
||||||
|
**Status:** Abgeschlossen
|
||||||
|
**Agent:** 🧐 [QA Specialist] | 🏗️ [Lead Architect]
|
||||||
|
|
||||||
|
## 🎯 Problemstellung
|
||||||
|
Nach der großflächigen Umbenennung der Pakete und der Migration der Feature-Module auf den neuen Blueprint traten Kompilierfehler im Modul `ping-feature` auf, speziell im `PingSyncIntegrationTest.kt`.
|
||||||
|
|
||||||
|
### Fehlermeldungen:
|
||||||
|
* `Unresolved reference 'FakePingEventRepository'`: Die Mock-Klasse für den Test fehlte.
|
||||||
|
* `Unresolved reference 'it'`: Typ-Inferenz-Fehler aufgrund der fehlenden Repository-Klasse.
|
||||||
|
|
||||||
|
## 🛠️ Durchgeführte Änderungen
|
||||||
|
|
||||||
|
### 1. Wiederherstellung der Test-Infrastruktur
|
||||||
|
* Die Klasse `FakePingEventRepository` wurde im Verzeichnis `frontend/features/ping-feature/src/commonTest/kotlin/at/mocode/frontend/features/ping/integration/` neu erstellt.
|
||||||
|
* Sie implementiert das `SyncableRepository<PingEvent>` Interface und ermöglicht die Verifizierung der synchronisierten Daten im Integrationstest.
|
||||||
|
|
||||||
|
### 2. Korrektur des Integrationstests
|
||||||
|
* In `PingSyncIntegrationTest.kt` wurden die fehlenden Importe (insbesondere `at.mocode.ping.api.PingEvent`) hinzugefügt.
|
||||||
|
* Die Lambda-Ausdrücke in den Assertions wurden verifiziert; durch die Anwesenheit der `FakePingEventRepository` Klasse funktioniert die Typ-Inferenz von Kotlin nun wieder korrekt, und die Referenzen auf `it.id` und `it.message` werden aufgelöst.
|
||||||
|
|
||||||
|
## ✅ Verifizierung
|
||||||
|
* `./gradlew :frontend:features:ping-feature:compileTestKotlinJvm`: **ERFOLGREICH**
|
||||||
|
* `./gradlew :frontend:features:ping-feature:jvmTest`: **ERFOLGREICH** (Alle Tests bestanden)
|
||||||
|
|
||||||
|
## 🧹 Fazit
|
||||||
|
Die Test-Suite für das `ping-feature` ist nun wieder vollständig und blueprint-konform. Die Entkopplung durch das `SyncableRepository` wurde im Test erfolgreich durch das Fake-Repository validiert.
|
||||||
+173
@@ -0,0 +1,173 @@
|
|||||||
|
package at.mocode.frontend.features.ping.data
|
||||||
|
|
||||||
|
import at.mocode.ping.api.EnhancedPingResponse
|
||||||
|
import at.mocode.ping.api.HealthResponse
|
||||||
|
import at.mocode.ping.api.PingResponse
|
||||||
|
import io.ktor.client.*
|
||||||
|
import io.ktor.client.engine.mock.*
|
||||||
|
import io.ktor.client.plugins.contentnegotiation.*
|
||||||
|
import io.ktor.http.*
|
||||||
|
import io.ktor.serialization.kotlinx.json.*
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
import kotlin.test.Test
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
|
class PingApiKoinClientTest {
|
||||||
|
|
||||||
|
// Hilfe zur Erstellung eines testbaren Clients mithilfe der neuen DI-freundlichen Implementierung
|
||||||
|
private fun createTestClient(mockEngine: MockEngine): PingApiKoinClient {
|
||||||
|
val client = HttpClient(mockEngine) {
|
||||||
|
install(ContentNegotiation) {
|
||||||
|
json(Json {
|
||||||
|
prettyPrint = true
|
||||||
|
isLenient = true
|
||||||
|
ignoreUnknownKeys = true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return PingApiKoinClient(client)
|
||||||
|
}
|
||||||
|
|
||||||
|
@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("/api/ping/simple", request.url.encodedPath)
|
||||||
|
assertEquals(HttpMethod.Get, request.method)
|
||||||
|
|
||||||
|
respond(
|
||||||
|
content = Json.encodeToString(PingResponse.serializer(), expectedResponse),
|
||||||
|
status = HttpStatusCode.OK,
|
||||||
|
headers = headersOf(HttpHeaders.ContentType, "application/json")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// When
|
||||||
|
val apiClient = createTestClient(mockEngine)
|
||||||
|
val response = apiClient.simplePing()
|
||||||
|
|
||||||
|
// Then
|
||||||
|
assertEquals(expectedResponse, response)
|
||||||
|
}
|
||||||
|
|
||||||
|
@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
|
||||||
|
)
|
||||||
|
|
||||||
|
val mockEngine = MockEngine { request ->
|
||||||
|
assertEquals("/api/ping/enhanced", request.url.encodedPath)
|
||||||
|
assertEquals("true", request.url.parameters["simulate"])
|
||||||
|
assertEquals(HttpMethod.Get, request.method)
|
||||||
|
|
||||||
|
respond(
|
||||||
|
content = Json.encodeToString(EnhancedPingResponse.serializer(), expectedResponse),
|
||||||
|
status = HttpStatusCode.OK,
|
||||||
|
headers = headersOf(HttpHeaders.ContentType, "application/json")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// When
|
||||||
|
val apiClient = createTestClient(mockEngine)
|
||||||
|
val response = apiClient.enhancedPing(simulate = true)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
assertEquals(expectedResponse, response)
|
||||||
|
}
|
||||||
|
|
||||||
|
@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
|
||||||
|
)
|
||||||
|
|
||||||
|
val mockEngine = MockEngine { request ->
|
||||||
|
assertEquals("/api/ping/health", request.url.encodedPath)
|
||||||
|
assertEquals(HttpMethod.Get, request.method)
|
||||||
|
|
||||||
|
respond(
|
||||||
|
content = Json.encodeToString(HealthResponse.serializer(), expectedResponse),
|
||||||
|
status = HttpStatusCode.OK,
|
||||||
|
headers = headersOf(HttpHeaders.ContentType, "application/json")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// When
|
||||||
|
val apiClient = createTestClient(mockEngine)
|
||||||
|
val response = apiClient.healthCheck()
|
||||||
|
|
||||||
|
// Then
|
||||||
|
assertEquals(expectedResponse, response)
|
||||||
|
}
|
||||||
|
|
||||||
|
@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)
|
||||||
|
|
||||||
|
// 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
|
||||||
|
)
|
||||||
|
|
||||||
|
// When
|
||||||
|
val json = Json.encodeToString(EnhancedPingResponse.serializer(), enhancedResponse)
|
||||||
|
val deserializedResponse = Json.decodeFromString(EnhancedPingResponse.serializer(), json)
|
||||||
|
|
||||||
|
// 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
|
||||||
|
)
|
||||||
|
|
||||||
|
// When
|
||||||
|
val json = Json.encodeToString(HealthResponse.serializer(), healthResponse)
|
||||||
|
val deserializedResponse = Json.decodeFromString(HealthResponse.serializer(), json)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
assertEquals(healthResponse, deserializedResponse)
|
||||||
|
}
|
||||||
|
}
|
||||||
+15
@@ -0,0 +1,15 @@
|
|||||||
|
package at.mocode.frontend.features.ping.integration
|
||||||
|
|
||||||
|
import at.mocode.frontend.core.sync.SyncableRepository
|
||||||
|
import at.mocode.ping.api.PingEvent
|
||||||
|
|
||||||
|
class FakePingEventRepository : SyncableRepository<PingEvent> {
|
||||||
|
val storedEvents = mutableListOf<PingEvent>()
|
||||||
|
var latestSince: String? = null
|
||||||
|
|
||||||
|
override suspend fun getLatestSince(): String? = latestSince
|
||||||
|
|
||||||
|
override suspend fun upsert(items: List<PingEvent>) {
|
||||||
|
storedEvents.addAll(items)
|
||||||
|
}
|
||||||
|
}
|
||||||
+70
@@ -0,0 +1,70 @@
|
|||||||
|
package at.mocode.frontend.features.ping.integration
|
||||||
|
|
||||||
|
import at.mocode.frontend.core.sync.SyncManager
|
||||||
|
import at.mocode.frontend.features.ping.domain.PingSyncServiceImpl
|
||||||
|
import at.mocode.ping.api.PingEvent
|
||||||
|
import io.ktor.client.*
|
||||||
|
import io.ktor.client.engine.mock.*
|
||||||
|
import io.ktor.client.plugins.contentnegotiation.*
|
||||||
|
import io.ktor.http.*
|
||||||
|
import io.ktor.serialization.kotlinx.json.*
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
import kotlin.test.Test
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
import kotlin.test.assertTrue
|
||||||
|
|
||||||
|
class PingSyncIntegrationTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `syncPings should fetch data from API and store in repository`() = runTest {
|
||||||
|
// Given
|
||||||
|
val fakeRepo = FakePingEventRepository()
|
||||||
|
|
||||||
|
// Mock API Response
|
||||||
|
val mockEngine = MockEngine { request ->
|
||||||
|
assertEquals("/api/ping/sync", request.url.encodedPath)
|
||||||
|
|
||||||
|
val responseBody = """
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"id": "event-1",
|
||||||
|
"message": "Ping 1",
|
||||||
|
"lastModified": 1000
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "event-2",
|
||||||
|
"message": "Ping 2",
|
||||||
|
"lastModified": 2000
|
||||||
|
}
|
||||||
|
]
|
||||||
|
""".trimIndent()
|
||||||
|
|
||||||
|
respond(
|
||||||
|
content = responseBody,
|
||||||
|
status = HttpStatusCode.OK,
|
||||||
|
headers = headersOf(HttpHeaders.ContentType, "application/json")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val httpClient = HttpClient(mockEngine) {
|
||||||
|
install(ContentNegotiation) {
|
||||||
|
json(Json {
|
||||||
|
ignoreUnknownKeys = true
|
||||||
|
isLenient = true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val syncManager = SyncManager(httpClient)
|
||||||
|
val syncService = PingSyncServiceImpl(syncManager, fakeRepo)
|
||||||
|
|
||||||
|
// When
|
||||||
|
syncService.syncPings()
|
||||||
|
|
||||||
|
// Then
|
||||||
|
assertEquals(2, fakeRepo.storedEvents.size)
|
||||||
|
assertTrue(fakeRepo.storedEvents.any { it.id == "event-1" && it.message == "Ping 1" })
|
||||||
|
assertTrue(fakeRepo.storedEvents.any { it.id == "event-2" && it.message == "Ping 2" })
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user