chore: migriere ping-feature Modul auf Module Structure Blueprint, füge Fake-Repository und neue Integrationstests hinzu, dokumentiere Änderungen
This commit is contained in:
parent
691861a706
commit
a645bb4dbc
29
docs/99_Journal/2026-04-19_PingFeature_TestFix.md
Normal file
29
docs/99_Journal/2026-04-19_PingFeature_TestFix.md
Normal file
|
|
@ -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.
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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" })
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user