From 263f182bab983ca6e3034a3bc7920f70f8dc665b Mon Sep 17 00:00:00 2001 From: Oliver Wallisch Date: Wed, 9 Sep 2026 23:31:19 +0200 Subject: [PATCH] fix: added new tests with gemini --- src/test/kotlin/ServerTest.kt | 136 +++++++++++++++++++++++++++++----- 1 file changed, 117 insertions(+), 19 deletions(-) diff --git a/src/test/kotlin/ServerTest.kt b/src/test/kotlin/ServerTest.kt index 4771973..0acddd7 100644 --- a/src/test/kotlin/ServerTest.kt +++ b/src/test/kotlin/ServerTest.kt @@ -1,14 +1,16 @@ package com.oliver.kidio.backend +import com.oliver.kidio.backend.domain.repository.InMemoryKidioRepository +import com.oliver.kidio.backend.plugins.configureRouting +import com.oliver.kidio.backend.plugins.configureSecurity +import com.oliver.kidio.backend.security.JwtService import io.ktor.client.request.* import io.ktor.client.statement.* import io.ktor.http.* -import io.ktor.server.application.* -import io.ktor.server.testing.* -import com.oliver.kidio.backend.plugins.configureRouting -import com.oliver.kidio.backend.domain.repository.InMemoryKidioRepository -import io.ktor.server.plugins.contentnegotiation.ContentNegotiation as ServerContentNegotiation import io.ktor.serialization.kotlinx.json.* +import io.ktor.server.application.* +import io.ktor.server.plugins.contentnegotiation.ContentNegotiation as ServerContentNegotiation +import io.ktor.server.testing.* import kotlinx.serialization.json.* import kotlin.test.* @@ -16,11 +18,15 @@ class ServerTest { @Test fun `test health endpoint`() = testApplication { + val jwtService = JwtService() + val repository = InMemoryKidioRepository() + application { - this.install(ServerContentNegotiation) { + install(ServerContentNegotiation) { json() } - configureRouting(InMemoryKidioRepository()) + configureSecurity(jwtService) + configureRouting(repository, jwtService) } val response = client.get("/api/v1/health") @@ -29,16 +35,87 @@ class ServerTest { } @Test - fun `test tasks endpoint and verify`() = testApplication { + fun `test protected endpoints reject unauthorized requests`() = testApplication { + val jwtService = JwtService() + val repository = InMemoryKidioRepository() + application { - this.install(ServerContentNegotiation) { + install(ServerContentNegotiation) { json() } - configureRouting(InMemoryKidioRepository()) + configureSecurity(jwtService) + configureRouting(repository, jwtService) } - // 1. Get Tasks + // Ohne Token muss 401 Unauthorized kommen val tasksResponse = client.get("/api/v1/tasks") + assertEquals(HttpStatusCode.Unauthorized, tasksResponse.status) + } + + @Test + fun `test registration and login flow`() = testApplication { + val jwtService = JwtService() + val repository = InMemoryKidioRepository() + + application { + install(ServerContentNegotiation) { + json() + } + configureSecurity(jwtService) + configureRouting(repository, jwtService) + } + + // 1. Registrierung + val registerResponse = client.post("/api/v1/auth/register") { + header(HttpHeaders.ContentType, ContentType.Application.Json.toString()) + setBody( + """{ + "email": "test@family.de", + "passwordPlain": "strengGeheim1", + "familyName": "Familie Schmidt", + "childAge": 8, + "parentPin": "4321" + }""" + ) + } + assertEquals(HttpStatusCode.Created, registerResponse.status) + assertTrue(registerResponse.bodyAsText().contains("token")) + + // 2. Login mit denselben Daten + val loginResponse = client.post("/api/v1/auth/login") { + header(HttpHeaders.ContentType, ContentType.Application.Json.toString()) + setBody( + """{ + "email": "test@family.de", + "passwordPlain": "strengGeheim1" + }""" + ) + } + assertEquals(HttpStatusCode.OK, loginResponse.status) + val loginJson = Json.decodeFromString(loginResponse.bodyAsText()) + assertNotNull(loginJson.token) + assertEquals("Familie Schmidt", loginJson.familyName) + } + + @Test + fun `test tasks endpoint and verify with jwt`() = testApplication { + val jwtService = JwtService() + val repository = InMemoryKidioRepository() + + application { + install(ServerContentNegotiation) { + json() + } + configureSecurity(jwtService) + configureRouting(repository, jwtService) + } + + val token = jwtService.generateToken("default_user") + + // 1. Get Tasks mit Token + val tasksResponse = client.get("/api/v1/tasks") { + header(HttpHeaders.Authorization, "Bearer $token") + } assertEquals(HttpStatusCode.OK, tasksResponse.status) val tasksJson = Json.decodeFromString>(tasksResponse.bodyAsText()) assertEquals(2, tasksJson.size) @@ -47,6 +124,7 @@ class ServerTest { // 2. Verify Task correct answer val verifyResponse = client.post("/api/v1/tasks/verify") { + header(HttpHeaders.Authorization, "Bearer $token") header(HttpHeaders.ContentType, ContentType.Application.Json.toString()) setBody("""{"taskId":"t1","selectedAnswer":"27"}""") } @@ -57,6 +135,7 @@ class ServerTest { // 3. Verify Task incorrect answer val verifyWrongResponse = client.post("/api/v1/tasks/verify") { + header(HttpHeaders.Authorization, "Bearer $token") header(HttpHeaders.ContentType, ContentType.Application.Json.toString()) setBody("""{"taskId":"t1","selectedAnswer":"99"}""") } @@ -67,16 +146,23 @@ class ServerTest { } @Test - fun `test screentime sync`() = testApplication { + fun `test screentime sync deducts time repeatedly`() = testApplication { + val jwtService = JwtService() + val repository = InMemoryKidioRepository() + application { - this.install(ServerContentNegotiation) { + install(ServerContentNegotiation) { json() } - configureRouting(InMemoryKidioRepository()) + configureSecurity(jwtService) + configureRouting(repository, jwtService) } - // Sync first time (user default_user has 1800s seeded) + val token = jwtService.generateToken("default_user") + + // 1. Sync: 200 Sekunden abziehen (1800 -> 1600) val syncResponse1 = client.post("/api/v1/screentime/sync") { + header(HttpHeaders.Authorization, "Bearer $token") header(HttpHeaders.ContentType, ContentType.Application.Json.toString()) setBody("""{"userId":"default_user","usedSecondsSinceLastSync":200}""") } @@ -85,14 +171,26 @@ class ServerTest { assertEquals(1600, syncJson1.remainingSeconds) assertFalse(syncJson1.isBlocked) - // Sync and block + // 2. Erneuter Sync: Weitere 300 Sekunden abziehen (1600 -> 1300) - darf nicht auf 1800 zurückspringen! val syncResponse2 = client.post("/api/v1/screentime/sync") { + header(HttpHeaders.Authorization, "Bearer $token") header(HttpHeaders.ContentType, ContentType.Application.Json.toString()) - setBody("""{"userId":"default_user","usedSecondsSinceLastSync":2000}""") + setBody("""{"userId":"default_user","usedSecondsSinceLastSync":300}""") } assertEquals(HttpStatusCode.OK, syncResponse2.status) val syncJson2 = Json.decodeFromString(syncResponse2.bodyAsText()) - assertEquals(0, syncJson2.remainingSeconds) - assertTrue(syncJson2.isBlocked) + assertEquals(1300, syncJson2.remainingSeconds) + assertFalse(syncJson2.isBlocked) + + // 3. Sync bis zur Sperre (weitere 1500 Sekunden abziehen -> 0) + val syncResponse3 = client.post("/api/v1/screentime/sync") { + header(HttpHeaders.Authorization, "Bearer $token") + header(HttpHeaders.ContentType, ContentType.Application.Json.toString()) + setBody("""{"userId":"default_user","usedSecondsSinceLastSync":1500}""") + } + assertEquals(HttpStatusCode.OK, syncResponse3.status) + val syncJson3 = Json.decodeFromString(syncResponse3.bodyAsText()) + assertEquals(0, syncJson3.remainingSeconds) + assertTrue(syncJson3.isBlocked) } }