style: fix missing assertion imports in ServerTest
Some checks failed
CI Pipeline / lint-and-test (pull_request) Failing after 43s
Some checks failed
CI Pipeline / lint-and-test (pull_request) Failing after 43s
This commit is contained in:
parent
809d559562
commit
0d8fdec26a
4
.editorconfig
Normal file
4
.editorconfig
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
[*.{kt,kts}]
|
||||
# Erlaubt Wildcard-Imports für das gesamte Projekt:
|
||||
ij_kotlin_allow_trailing_comma = true
|
||||
disabled_rules = no-wildcard-imports
|
||||
|
|
@ -1,12 +1,13 @@
|
|||
import com.oliver.kidio.backend.plugins.configureRouting
|
||||
import com.oliver.kidio.backend.data.database.DatabaseFactory
|
||||
import com.oliver.kidio.backend.data.database.RedisFactory
|
||||
import com.oliver.kidio.backend.domain.repository.ExposedKidioRepository
|
||||
import com.oliver.kidio.backend.plugins.configureRouting
|
||||
import com.oliver.kidio.backend.plugins.configureSecurity
|
||||
import com.oliver.kidio.backend.security.JwtService
|
||||
import io.ktor.serialization.kotlinx.json.*
|
||||
import io.ktor.server.application.*
|
||||
import io.ktor.server.plugins.contentnegotiation.*
|
||||
import io.ktor.serialization.kotlinx.json.json
|
||||
import io.ktor.server.application.Application
|
||||
import io.ktor.server.application.install
|
||||
import io.ktor.server.plugins.contentnegotiation.ContentNegotiation
|
||||
|
||||
fun Application.module() {
|
||||
// Datenbank initialisieren
|
||||
|
|
@ -25,6 +26,6 @@ fun Application.module() {
|
|||
|
||||
configureRouting(
|
||||
repository = repository,
|
||||
jwtService = jwtService
|
||||
jwtService = jwtService,
|
||||
)
|
||||
}
|
||||
|
|
@ -22,6 +22,7 @@ object ScreentimeTable : Table("user_screentime") {
|
|||
|
||||
override val primaryKey = PrimaryKey(userId)
|
||||
}
|
||||
|
||||
object UserTable : Table("users") {
|
||||
val id = varchar("id", 36)
|
||||
val email = varchar("email", 255).uniqueIndex()
|
||||
|
|
@ -30,7 +31,6 @@ object UserTable : Table("users") {
|
|||
val childAge = integer("child_age")
|
||||
val parentPin = varchar("parent_pin", 4)
|
||||
|
||||
|
||||
override val primaryKey = PrimaryKey(id)
|
||||
}
|
||||
|
||||
|
|
@ -46,12 +46,13 @@ object DatabaseFactory {
|
|||
|
||||
val jdbcURL = "jdbc:postgresql://$host:$port/$dbName"
|
||||
|
||||
val database = Database.connect(
|
||||
url = jdbcURL,
|
||||
driver = driverClassName,
|
||||
user = dbUsername,
|
||||
password = password
|
||||
)
|
||||
val database =
|
||||
Database.connect(
|
||||
url = jdbcURL,
|
||||
driver = driverClassName,
|
||||
user = dbUsername,
|
||||
password = password,
|
||||
)
|
||||
|
||||
transaction(database) {
|
||||
SchemaUtils.create(TasksTable, ScreentimeTable, UserTable)
|
||||
|
|
@ -93,6 +94,5 @@ object DatabaseFactory {
|
|||
}
|
||||
}
|
||||
|
||||
suspend fun <T> dbQuery(block: suspend () -> T): T =
|
||||
newSuspendedTransaction(Dispatchers.IO) { block() }
|
||||
suspend fun <T> dbQuery(block: suspend () -> T): T = newSuspendedTransaction(Dispatchers.IO) { block() }
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
package com.oliver.kidio.backend.data.database
|
||||
import io.ktor.server.config.*
|
||||
import io.ktor.server.config.ApplicationConfig
|
||||
import io.ktor.server.routing.get
|
||||
import redis.clients.jedis.JedisPool
|
||||
import redis.clients.jedis.JedisPoolConfig
|
||||
|
||||
|
|
@ -7,22 +8,28 @@ object RedisFactory {
|
|||
private var jedisPool: JedisPool? = null
|
||||
|
||||
fun init(config: ApplicationConfig) {
|
||||
val host = System.getenv("REDIS_HOST")
|
||||
?: config.propertyOrNull("storage.redisHost")?.getString()
|
||||
?: "localhost"
|
||||
val host =
|
||||
System.getenv("REDIS_HOST")
|
||||
?: config.propertyOrNull("storage.redisHost")?.getString()
|
||||
?: "localhost"
|
||||
|
||||
val port = (System.getenv("REDIS_PORT")
|
||||
?: config.propertyOrNull("storage.redisPort")?.getString()
|
||||
?: "6379").toInt()
|
||||
val port =
|
||||
(
|
||||
System.getenv("REDIS_PORT")
|
||||
?: config.propertyOrNull("storage.redisPort")?.getString()
|
||||
?: "6379"
|
||||
).toInt()
|
||||
|
||||
val poolConfig = JedisPoolConfig().apply {
|
||||
maxTotal = 32
|
||||
maxIdle = 8
|
||||
minIdle = 2
|
||||
}
|
||||
val poolConfig =
|
||||
JedisPoolConfig().apply {
|
||||
maxTotal = 32
|
||||
maxIdle = 8
|
||||
minIdle = 2
|
||||
}
|
||||
jedisPool = JedisPool(poolConfig, host, port)
|
||||
}
|
||||
|
||||
fun getResource() = jedisPool?.resource
|
||||
?: throw IllegalStateException("Redis Verbindungspool ist nicht initzialisiert")
|
||||
fun getResource() =
|
||||
jedisPool?.resource
|
||||
?: throw IllegalStateException("Redis Verbindungspool ist nicht initzialisiert")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,14 +8,14 @@ data class Task(
|
|||
val id: String,
|
||||
val question: String,
|
||||
val options: List<String>,
|
||||
val rewardMinutes: Int // Wie viel Belohnung es gibt
|
||||
val rewardMinutes: Int,
|
||||
)
|
||||
|
||||
// Anfrage des Kindes, wenn es eine Aufgabe löst
|
||||
@Serializable
|
||||
data class TaskVerifyRequest(
|
||||
val taskId: String,
|
||||
val selectedAnswer: String
|
||||
val selectedAnswer: String,
|
||||
)
|
||||
|
||||
// Antwort vom Server nach der Aufgaben-Prüfung
|
||||
|
|
@ -23,20 +23,20 @@ data class TaskVerifyRequest(
|
|||
data class TaskVerifyResponse(
|
||||
val isCorrect: Boolean,
|
||||
val earnedMinutes: Int,
|
||||
val message: String
|
||||
val message: String,
|
||||
)
|
||||
|
||||
// Datenabgleich für die Bildschirmzeit (Sync)
|
||||
@Serializable
|
||||
data class SyncRequest(
|
||||
val userId: String,
|
||||
val usedSecondsSinceLastSync: Int
|
||||
val usedSecondsSinceLastSync: Int,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class SyncResponse(
|
||||
val remainingSeconds: Int,
|
||||
val isBlocked: Boolean
|
||||
val isBlocked: Boolean,
|
||||
)
|
||||
|
||||
// --- NEU: AUTHENTIFIZIERUNG ---
|
||||
|
|
@ -47,21 +47,21 @@ data class RegisterRequest(
|
|||
val passwordPlain: String,
|
||||
val familyName: String,
|
||||
val childAge: Int,
|
||||
val parentPin: String
|
||||
val parentPin: String,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class LoginRequest(
|
||||
val email: String,
|
||||
val passwordPlain: String
|
||||
val passwordPlain: String,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class AuthResponse(
|
||||
val token: String, // Der digitale "Ausweis" (JWT)
|
||||
val userId: String, // Wichtig für die App, um Aufgaben zuzuordnen
|
||||
val familyName: String, // Für "Hallo Familie Müller!"
|
||||
val childAge: Int
|
||||
val token: String,
|
||||
val userId: String,
|
||||
val familyName: String,
|
||||
val childAge: Int,
|
||||
)
|
||||
|
||||
// Interne Backend-Klasse (Domain Model).
|
||||
|
|
@ -71,5 +71,5 @@ data class User(
|
|||
val passwordHash: String,
|
||||
val familyName: String,
|
||||
val childAge: Int,
|
||||
val parentPin: String
|
||||
val parentPin: String,
|
||||
)
|
||||
|
|
@ -12,53 +12,68 @@ import org.mindrot.jbcrypt.BCrypt
|
|||
|
||||
interface KidioRepository {
|
||||
suspend fun getAllTasks(): List<Task>
|
||||
|
||||
suspend fun getTaskAnswerAndReward(taskId: String): Pair<String, Int>?
|
||||
|
||||
suspend fun getScreentime(userId: String): Int
|
||||
suspend fun updateScreentime(userId: String, seconds: Int)
|
||||
|
||||
suspend fun updateScreentime(
|
||||
userId: String,
|
||||
seconds: Int,
|
||||
)
|
||||
|
||||
suspend fun findByEmail(email: String): User?
|
||||
suspend fun verifyPassword(password: String, passwordHash: String): Boolean
|
||||
|
||||
suspend fun verifyPassword(
|
||||
password: String,
|
||||
passwordHash: String,
|
||||
): Boolean
|
||||
|
||||
suspend fun registerUser(user: User): Boolean
|
||||
}
|
||||
|
||||
class ExposedKidioRepository : KidioRepository {
|
||||
override suspend fun getAllTasks(): List<Task> = DatabaseFactory.dbQuery {
|
||||
TasksTable.selectAll().map {
|
||||
Task(
|
||||
id = it[TasksTable.id],
|
||||
question = it[TasksTable.question],
|
||||
options = it[TasksTable.options].split(",").map { opt -> opt.trim() },
|
||||
rewardMinutes = it[TasksTable.rewardMinutes]
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun registerUser(user: User): Boolean = DatabaseFactory.dbQuery {
|
||||
try {
|
||||
UserTable.insert {
|
||||
it[id] = user.id
|
||||
it[email] = user.email
|
||||
it[passwordHash] = user.passwordHash
|
||||
it[familyName] = user.familyName
|
||||
it[childAge] = user.childAge
|
||||
it[parentPin] = user.parentPin
|
||||
override suspend fun getAllTasks(): List<Task> =
|
||||
DatabaseFactory.dbQuery {
|
||||
TasksTable.selectAll().map {
|
||||
Task(
|
||||
id = it[TasksTable.id],
|
||||
question = it[TasksTable.question],
|
||||
options = it[TasksTable.options].split(",").map { opt -> opt.trim() },
|
||||
rewardMinutes = it[TasksTable.rewardMinutes],
|
||||
)
|
||||
}
|
||||
ScreentimeTable.insert {
|
||||
it[userId] = user.id
|
||||
it[remainingSeconds] = 1800 // Startguthaben: 30 Minuten
|
||||
}
|
||||
true
|
||||
} catch (e: Exception) {
|
||||
println("FEHLER bei Registrierung: ${e.message}")
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getTaskAnswerAndReward(taskId: String): Pair<String, Int>? = DatabaseFactory.dbQuery {
|
||||
TasksTable.selectAll()
|
||||
.where { TasksTable.id eq taskId }
|
||||
.map { it[TasksTable.correctAnswer] to it[TasksTable.rewardMinutes] }
|
||||
.singleOrNull()
|
||||
}
|
||||
override suspend fun registerUser(user: User): Boolean =
|
||||
DatabaseFactory.dbQuery {
|
||||
try {
|
||||
UserTable.insert {
|
||||
it[id] = user.id
|
||||
it[email] = user.email
|
||||
it[passwordHash] = user.passwordHash
|
||||
it[familyName] = user.familyName
|
||||
it[childAge] = user.childAge
|
||||
it[parentPin] = user.parentPin
|
||||
}
|
||||
ScreentimeTable.insert {
|
||||
it[userId] = user.id
|
||||
it[remainingSeconds] = 1800 // Startguthaben: 30 Minuten
|
||||
}
|
||||
true
|
||||
} catch (e: Exception) {
|
||||
println("FEHLER bei Registrierung: ${e.message}")
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getTaskAnswerAndReward(taskId: String): Pair<String, Int>? =
|
||||
DatabaseFactory.dbQuery {
|
||||
TasksTable.selectAll()
|
||||
.where { TasksTable.id eq taskId }
|
||||
.map { it[TasksTable.correctAnswer] to it[TasksTable.rewardMinutes] }
|
||||
.singleOrNull()
|
||||
}
|
||||
|
||||
override suspend fun getScreentime(userId: String): Int {
|
||||
val redisKey = "screentime:$userId"
|
||||
|
|
@ -75,12 +90,13 @@ class ExposedKidioRepository : KidioRepository {
|
|||
println("WARNUNG: Konnte nicht aus Redis lesen (Postgres-Fallback genutzt): ${e.message}")
|
||||
}
|
||||
|
||||
val dbSeconds = DatabaseFactory.dbQuery {
|
||||
ScreentimeTable.selectAll()
|
||||
.where { ScreentimeTable.userId eq userId }
|
||||
.map { it[ScreentimeTable.remainingSeconds] }
|
||||
.singleOrNull() ?: 1800
|
||||
}
|
||||
val dbSeconds =
|
||||
DatabaseFactory.dbQuery {
|
||||
ScreentimeTable.selectAll()
|
||||
.where { ScreentimeTable.userId eq userId }
|
||||
.map { it[ScreentimeTable.remainingSeconds] }
|
||||
.singleOrNull() ?: 1800
|
||||
}
|
||||
|
||||
try {
|
||||
RedisFactory.getResource().use { jedis ->
|
||||
|
|
@ -93,11 +109,15 @@ class ExposedKidioRepository : KidioRepository {
|
|||
return dbSeconds
|
||||
}
|
||||
|
||||
override suspend fun updateScreentime(userId: String, seconds: Int) {
|
||||
override suspend fun updateScreentime(
|
||||
userId: String,
|
||||
seconds: Int,
|
||||
) {
|
||||
DatabaseFactory.dbQuery {
|
||||
val rowsUpdated = ScreentimeTable.update({ ScreentimeTable.userId eq userId }) {
|
||||
it[remainingSeconds] = seconds
|
||||
}
|
||||
val rowsUpdated =
|
||||
ScreentimeTable.update({ ScreentimeTable.userId eq userId }) {
|
||||
it[remainingSeconds] = seconds
|
||||
}
|
||||
if (rowsUpdated == 0) {
|
||||
ScreentimeTable.insert {
|
||||
it[ScreentimeTable.userId] = userId
|
||||
|
|
@ -116,39 +136,46 @@ class ExposedKidioRepository : KidioRepository {
|
|||
}
|
||||
}
|
||||
|
||||
override suspend fun findByEmail(email: String): User? = DatabaseFactory.dbQuery {
|
||||
UserTable.selectAll()
|
||||
.where { UserTable.email eq email }
|
||||
.map {
|
||||
User(
|
||||
id = it[UserTable.id],
|
||||
email = it[UserTable.email],
|
||||
passwordHash = it[UserTable.passwordHash],
|
||||
familyName = it[UserTable.familyName],
|
||||
childAge = it[UserTable.childAge],
|
||||
parentPin = it[UserTable.parentPin]
|
||||
)
|
||||
}
|
||||
.singleOrNull()
|
||||
}
|
||||
override suspend fun findByEmail(email: String): User? =
|
||||
DatabaseFactory.dbQuery {
|
||||
UserTable.selectAll()
|
||||
.where { UserTable.email eq email }
|
||||
.map {
|
||||
User(
|
||||
id = it[UserTable.id],
|
||||
email = it[UserTable.email],
|
||||
passwordHash = it[UserTable.passwordHash],
|
||||
familyName = it[UserTable.familyName],
|
||||
childAge = it[UserTable.childAge],
|
||||
parentPin = it[UserTable.parentPin],
|
||||
)
|
||||
}
|
||||
.singleOrNull()
|
||||
}
|
||||
|
||||
override suspend fun verifyPassword(password: String, passwordHash: String): Boolean {
|
||||
override suspend fun verifyPassword(
|
||||
password: String,
|
||||
passwordHash: String,
|
||||
): Boolean {
|
||||
return BCrypt.checkpw(password, passwordHash)
|
||||
}
|
||||
}
|
||||
|
||||
class InMemoryKidioRepository : KidioRepository {
|
||||
private val tasks = mutableListOf(
|
||||
Task("t1", "Was ist 12 + 15?", listOf("25", "27", "30", "22"), 10),
|
||||
Task("t2", "Was ist 8 x 7?", listOf("54", "56", "64", "48"), 15)
|
||||
)
|
||||
private val answers = mutableMapOf(
|
||||
"t1" to ("27" to 10),
|
||||
"t2" to ("56" to 15)
|
||||
)
|
||||
private val screentimes = mutableMapOf(
|
||||
"default_user" to 1800
|
||||
)
|
||||
private val tasks =
|
||||
mutableListOf(
|
||||
Task("t1", "Was ist 12 + 15?", listOf("25", "27", "30", "22"), 10),
|
||||
Task("t2", "Was ist 8 x 7?", listOf("54", "56", "64", "48"), 15),
|
||||
)
|
||||
private val answers =
|
||||
mutableMapOf(
|
||||
"t1" to ("27" to 10),
|
||||
"t2" to ("56" to 15),
|
||||
)
|
||||
private val screentimes =
|
||||
mutableMapOf(
|
||||
"default_user" to 1800,
|
||||
)
|
||||
private val users = mutableListOf<User>()
|
||||
|
||||
override suspend fun getAllTasks(): List<Task> = tasks
|
||||
|
|
@ -157,7 +184,10 @@ class InMemoryKidioRepository : KidioRepository {
|
|||
|
||||
override suspend fun getScreentime(userId: String): Int = screentimes[userId] ?: 1800
|
||||
|
||||
override suspend fun updateScreentime(userId: String, seconds: Int) {
|
||||
override suspend fun updateScreentime(
|
||||
userId: String,
|
||||
seconds: Int,
|
||||
) {
|
||||
screentimes[userId] = seconds
|
||||
}
|
||||
|
||||
|
|
@ -165,7 +195,10 @@ class InMemoryKidioRepository : KidioRepository {
|
|||
return users.find { it.email == email }
|
||||
}
|
||||
|
||||
override suspend fun verifyPassword(password: String, passwordHash: String): Boolean {
|
||||
override suspend fun verifyPassword(
|
||||
password: String,
|
||||
passwordHash: String,
|
||||
): Boolean {
|
||||
return BCrypt.checkpw(password, passwordHash)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -13,7 +13,10 @@ import io.ktor.server.routing.*
|
|||
import org.mindrot.jbcrypt.BCrypt
|
||||
import java.util.UUID
|
||||
|
||||
fun Application.configureRouting(repository: KidioRepository, jwtService : JwtService) {
|
||||
fun Application.configureRouting(
|
||||
repository: KidioRepository,
|
||||
jwtService: JwtService,
|
||||
) {
|
||||
routing {
|
||||
post("/api/v1/auth/register") {
|
||||
val registerRequest = call.receive<RegisterRequest>()
|
||||
|
|
@ -24,7 +27,7 @@ fun Application.configureRouting(repository: KidioRepository, jwtService : JwtSe
|
|||
if (registerRequest.parentPin.length != 4) {
|
||||
call.respond(HttpStatusCode.BadRequest, "Die Eltern-PIN muss genau 4 Ziffern lang sein")
|
||||
return@post
|
||||
}
|
||||
}
|
||||
val existingUser = repository.findByEmail(registerRequest.email)
|
||||
if (existingUser != null) {
|
||||
call.respond(HttpStatusCode.Conflict, "Ein Benutzer mit dieser E-Mail-Adresse existiert bereits")
|
||||
|
|
@ -35,8 +38,17 @@ fun Application.configureRouting(repository: KidioRepository, jwtService : JwtSe
|
|||
val userId = UUID.randomUUID().toString()
|
||||
val passwordHash = BCrypt.hashpw(registerRequest.passwordPlain, BCrypt.gensalt())
|
||||
|
||||
//datenbank insert hinzufügen
|
||||
repository.registerUser(User(userId, registerRequest.email, passwordHash, registerRequest.familyName, registerRequest.childAge, registerRequest.parentPin))
|
||||
// datenbank insert hinzufügen
|
||||
repository.registerUser(
|
||||
User(
|
||||
userId,
|
||||
registerRequest.email,
|
||||
passwordHash,
|
||||
registerRequest.familyName,
|
||||
registerRequest.childAge,
|
||||
registerRequest.parentPin,
|
||||
),
|
||||
)
|
||||
|
||||
val token = jwtService.generateToken(userId)
|
||||
|
||||
|
|
@ -45,8 +57,8 @@ fun Application.configureRouting(repository: KidioRepository, jwtService : JwtSe
|
|||
mapOf(
|
||||
"token" to token,
|
||||
"userId" to userId,
|
||||
"message" to "Registrierung erfolgreich"
|
||||
)
|
||||
"message" to "Registrierung erfolgreich",
|
||||
),
|
||||
)
|
||||
}
|
||||
// Health-Check
|
||||
|
|
@ -54,33 +66,31 @@ fun Application.configureRouting(repository: KidioRepository, jwtService : JwtSe
|
|||
call.respond(mapOf("status" to "OK", "message" to "Kidio Backend läuft!"))
|
||||
}
|
||||
|
||||
post ("/api/v1/auth/login" ) {
|
||||
post("/api/v1/auth/login") {
|
||||
val loginRequest = call.receive<LoginRequest>()
|
||||
if(loginRequest.email.isBlank() || loginRequest.passwordPlain.isBlank()){
|
||||
if (loginRequest.email.isBlank() || loginRequest.passwordPlain.isBlank()) {
|
||||
call.respond(HttpStatusCode.BadRequest, "Bitte füllen Sie alle Felder aus")
|
||||
return@post
|
||||
}
|
||||
val getUser = repository.findByEmail(loginRequest.email)
|
||||
if (getUser == null ||!repository.verifyPassword(loginRequest.passwordPlain, getUser.passwordHash)){
|
||||
if (getUser == null || !repository.verifyPassword(loginRequest.passwordPlain, getUser.passwordHash)) {
|
||||
call.respond(HttpStatusCode.Unauthorized, "Ungültige Anmeldedaten")
|
||||
return@post
|
||||
}
|
||||
val token = jwtService.generateToken(getUser.id)
|
||||
call.respond(HttpStatusCode.OK, AuthResponse(
|
||||
token,
|
||||
userId = getUser.id,
|
||||
familyName = getUser.familyName,
|
||||
childAge = getUser.childAge
|
||||
))
|
||||
call.respond(
|
||||
HttpStatusCode.OK,
|
||||
AuthResponse(
|
||||
token,
|
||||
userId = getUser.id,
|
||||
familyName = getUser.familyName,
|
||||
childAge = getUser.childAge,
|
||||
),
|
||||
)
|
||||
return@post
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
authenticate("auth-jwt") {
|
||||
|
||||
// 1. Alle verfügbaren Aufgaben abrufen
|
||||
get("/api/v1/tasks") {
|
||||
val tasks = repository.getAllTasks()
|
||||
|
|
@ -88,67 +98,67 @@ fun Application.configureRouting(repository: KidioRepository, jwtService : JwtSe
|
|||
}
|
||||
|
||||
// 2. Antwort auf eine Aufgabe überprüfen
|
||||
post("/api/v1/tasks/verify") {
|
||||
val request = call.receive<TaskVerifyRequest>()
|
||||
val principal = call.principal<UserPrincipal>()
|
||||
val userId = principal?.userId
|
||||
if (userId == null){
|
||||
call.respond(HttpStatusCode.Unauthorized, mapOf("error" to "Ungültiges oder abgelaufenes Token"))
|
||||
post("/api/v1/tasks/verify") {
|
||||
val request = call.receive<TaskVerifyRequest>()
|
||||
val principal = call.principal<UserPrincipal>()
|
||||
val userId = principal?.userId
|
||||
if (userId == null) {
|
||||
call.respond(HttpStatusCode.Unauthorized, mapOf("error" to "Ungültiges oder abgelaufenes Token"))
|
||||
return@post
|
||||
}
|
||||
val taskInfo = repository.getTaskAnswerAndReward(request.taskId)
|
||||
if (taskInfo == null) {
|
||||
call.respond(
|
||||
TaskVerifyResponse(
|
||||
isCorrect = false,
|
||||
earnedMinutes = 0,
|
||||
message = "Aufgabe nicht gefunden!"
|
||||
)
|
||||
)
|
||||
return@post
|
||||
}
|
||||
|
||||
val (correctAnswer, reward) = taskInfo
|
||||
val isCorrect = request.selectedAnswer.trim() == correctAnswer.trim()
|
||||
|
||||
if (isCorrect) {
|
||||
val currentScreentime = repository.getScreentime(userId)
|
||||
val newSeconds = currentScreentime + (reward * 60)
|
||||
repository.updateScreentime(userId, newSeconds)
|
||||
|
||||
call.respond(
|
||||
TaskVerifyResponse(
|
||||
isCorrect = true,
|
||||
earnedMinutes = reward,
|
||||
message = "Super gemacht! Du hast $reward Minuten gewonnen."
|
||||
)
|
||||
)
|
||||
} else {
|
||||
call.respond(
|
||||
TaskVerifyResponse(
|
||||
isCorrect = false,
|
||||
earnedMinutes = 0,
|
||||
message = "Schade, das war leider falsch. Versuche es nochmal!"
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
val taskInfo = repository.getTaskAnswerAndReward(request.taskId)
|
||||
if (taskInfo == null) {
|
||||
call.respond(
|
||||
TaskVerifyResponse(
|
||||
isCorrect = false,
|
||||
earnedMinutes = 0,
|
||||
message = "Aufgabe nicht gefunden!",
|
||||
),
|
||||
)
|
||||
return@post
|
||||
}
|
||||
|
||||
val (correctAnswer, reward) = taskInfo
|
||||
val isCorrect = request.selectedAnswer.trim() == correctAnswer.trim()
|
||||
|
||||
if (isCorrect) {
|
||||
val currentScreentime = repository.getScreentime(userId)
|
||||
val newSeconds = currentScreentime + (reward * 60)
|
||||
repository.updateScreentime(userId, newSeconds)
|
||||
|
||||
call.respond(
|
||||
TaskVerifyResponse(
|
||||
isCorrect = true,
|
||||
earnedMinutes = reward,
|
||||
message = "Super gemacht! Du hast $reward Minuten gewonnen.",
|
||||
),
|
||||
)
|
||||
} else {
|
||||
call.respond(
|
||||
TaskVerifyResponse(
|
||||
isCorrect = false,
|
||||
earnedMinutes = 0,
|
||||
message = "Schade, das war leider falsch. Versuche es nochmal!",
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
get("/api/v1/screentime") {
|
||||
val principal = call.principal<UserPrincipal>()
|
||||
val userid = principal?.userId?: run {
|
||||
call.respond(HttpStatusCode.Unauthorized, mapOf("error" to "Ungültiger Token"))
|
||||
return@get
|
||||
}
|
||||
val userid =
|
||||
principal?.userId
|
||||
?: run {
|
||||
call.respond(HttpStatusCode.Unauthorized, mapOf("error" to "Ungültiger Token"))
|
||||
return@get
|
||||
}
|
||||
val remainingSeconds = repository.getScreentime(userid)
|
||||
call.respond(
|
||||
HttpStatusCode.OK,
|
||||
SyncResponse(
|
||||
remainingSeconds = remainingSeconds,
|
||||
isBlocked = remainingSeconds <= 0
|
||||
)
|
||||
isBlocked = remainingSeconds <= 0,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
}
|
||||
|
||||
// 3. Bildschirmzeit synchronisieren
|
||||
|
|
@ -168,11 +178,10 @@ fun Application.configureRouting(repository: KidioRepository, jwtService : JwtSe
|
|||
call.respond(
|
||||
SyncResponse(
|
||||
remainingSeconds = newSeconds,
|
||||
isBlocked = newSeconds <= 0
|
||||
)
|
||||
isBlocked = newSeconds <= 0,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,14 +9,14 @@ class JwtService(
|
|||
private val secret: String = System.getenv("JWT_SECRET") ?: "super-geheimes-secret-fuer-dev",
|
||||
private val issuer: String = "https://kidio.oliver.com/",
|
||||
private val audience: String = "kidio-users",
|
||||
private val expirationInMs: Long = java.util.concurrent.TimeUnit.DAYS.toMillis(30) // 30 Tage
|
||||
private val expirationInMs: Long = java.util.concurrent.TimeUnit.DAYS.toMillis(30),
|
||||
) {
|
||||
|
||||
val verifier: JWTVerifier = JWT
|
||||
.require(Algorithm.HMAC256(secret))
|
||||
.withAudience(audience)
|
||||
.withIssuer(issuer)
|
||||
.build()
|
||||
val verifier: JWTVerifier =
|
||||
JWT
|
||||
.require(Algorithm.HMAC256(secret))
|
||||
.withAudience(audience)
|
||||
.withIssuer(issuer)
|
||||
.build()
|
||||
|
||||
/**
|
||||
* Generiert ein JWT-Token für einen bestimmten Benutzer.
|
||||
|
|
|
|||
|
|
@ -4,220 +4,243 @@ 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.serialization.kotlinx.json.*
|
||||
import io.ktor.server.application.*
|
||||
import io.ktor.client.request.get
|
||||
import io.ktor.client.request.header
|
||||
import io.ktor.client.request.post
|
||||
import io.ktor.client.request.setBody
|
||||
import io.ktor.client.statement.bodyAsText
|
||||
import io.ktor.http.ContentType
|
||||
import io.ktor.http.HttpHeaders
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import io.ktor.serialization.kotlinx.json.json
|
||||
import io.ktor.server.application.install
|
||||
import io.ktor.server.testing.testApplication
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertNotNull
|
||||
import kotlin.test.assertTrue
|
||||
import io.ktor.server.plugins.contentnegotiation.ContentNegotiation as ServerContentNegotiation
|
||||
import io.ktor.server.testing.*
|
||||
import kotlinx.serialization.json.*
|
||||
import kotlin.test.*
|
||||
|
||||
class ServerTest {
|
||||
|
||||
@Test
|
||||
fun `test health endpoint`() = testApplication {
|
||||
val jwtService = JwtService()
|
||||
val repository = InMemoryKidioRepository()
|
||||
fun `test health endpoint`() =
|
||||
testApplication {
|
||||
val jwtService = JwtService()
|
||||
val repository = InMemoryKidioRepository()
|
||||
|
||||
application {
|
||||
install(ServerContentNegotiation) {
|
||||
json()
|
||||
application {
|
||||
install(ServerContentNegotiation) {
|
||||
json()
|
||||
}
|
||||
configureSecurity(jwtService)
|
||||
configureRouting(repository, jwtService)
|
||||
}
|
||||
configureSecurity(jwtService)
|
||||
configureRouting(repository, jwtService)
|
||||
}
|
||||
|
||||
val response = client.get("/api/v1/health")
|
||||
assertEquals(HttpStatusCode.OK, response.status)
|
||||
assertTrue(response.bodyAsText().contains("Kidio Backend läuft!"))
|
||||
}
|
||||
val response = client.get("/api/v1/health")
|
||||
assertEquals(HttpStatusCode.OK, response.status)
|
||||
assertTrue(response.bodyAsText().contains("Kidio Backend läuft!"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test get screentime returns current status`() = testApplication {
|
||||
val jwtService = JwtService()
|
||||
val repository = InMemoryKidioRepository()
|
||||
fun `test get screentime returns current status`() =
|
||||
testApplication {
|
||||
val jwtService = JwtService()
|
||||
val repository = InMemoryKidioRepository()
|
||||
|
||||
application {
|
||||
install(ServerContentNegotiation) { json() }
|
||||
configureSecurity(jwtService)
|
||||
configureRouting(repository, jwtService)
|
||||
}
|
||||
|
||||
val token = jwtService.generateToken("default_user")
|
||||
|
||||
// Ohne Token -> 401 Unauthorized
|
||||
val unauthResponse = client.get("/api/v1/screentime")
|
||||
assertEquals(HttpStatusCode.Unauthorized, unauthResponse.status)
|
||||
|
||||
// Mit Token -> 200 OK
|
||||
val response = client.get("/api/v1/screentime") {
|
||||
header(HttpHeaders.Authorization, "Bearer $token")
|
||||
}
|
||||
assertEquals(HttpStatusCode.OK, response.status)
|
||||
val json = Json.decodeFromString<SyncResponse>(response.bodyAsText())
|
||||
assertEquals(1800, json.remainingSeconds)
|
||||
assertFalse(json.isBlocked)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test protected endpoints reject unauthorized requests`() = testApplication {
|
||||
val jwtService = JwtService()
|
||||
val repository = InMemoryKidioRepository()
|
||||
|
||||
application {
|
||||
install(ServerContentNegotiation) {
|
||||
json()
|
||||
application {
|
||||
install(ServerContentNegotiation) { json() }
|
||||
configureSecurity(jwtService)
|
||||
configureRouting(repository, jwtService)
|
||||
}
|
||||
configureSecurity(jwtService)
|
||||
configureRouting(repository, jwtService)
|
||||
}
|
||||
|
||||
// Ohne Token muss 401 Unauthorized kommen
|
||||
val tasksResponse = client.get("/api/v1/tasks")
|
||||
assertEquals(HttpStatusCode.Unauthorized, tasksResponse.status)
|
||||
}
|
||||
val token = jwtService.generateToken("default_user")
|
||||
|
||||
// Ohne Token -> 401 Unauthorized
|
||||
val unauthResponse = client.get("/api/v1/screentime")
|
||||
assertEquals(HttpStatusCode.Unauthorized, unauthResponse.status)
|
||||
|
||||
// Mit Token -> 200 OK
|
||||
val response =
|
||||
client.get("/api/v1/screentime") {
|
||||
header(HttpHeaders.Authorization, "Bearer $token")
|
||||
}
|
||||
assertEquals(HttpStatusCode.OK, response.status)
|
||||
val json = Json.decodeFromString<SyncResponse>(response.bodyAsText())
|
||||
assertEquals(1800, json.remainingSeconds)
|
||||
assertFalse(json.isBlocked)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test registration and login flow`() = testApplication {
|
||||
val jwtService = JwtService()
|
||||
val repository = InMemoryKidioRepository()
|
||||
fun `test protected endpoints reject unauthorized requests`() =
|
||||
testApplication {
|
||||
val jwtService = JwtService()
|
||||
val repository = InMemoryKidioRepository()
|
||||
|
||||
application {
|
||||
install(ServerContentNegotiation) {
|
||||
json()
|
||||
application {
|
||||
install(ServerContentNegotiation) {
|
||||
json()
|
||||
}
|
||||
configureSecurity(jwtService)
|
||||
configureRouting(repository, jwtService)
|
||||
}
|
||||
configureSecurity(jwtService)
|
||||
configureRouting(repository, jwtService)
|
||||
|
||||
// Ohne Token muss 401 Unauthorized kommen
|
||||
val tasksResponse = client.get("/api/v1/tasks")
|
||||
assertEquals(HttpStatusCode.Unauthorized, tasksResponse.status)
|
||||
}
|
||||
|
||||
// 1. Registrierung
|
||||
val registerResponse = client.post("/api/v1/auth/register") {
|
||||
header(HttpHeaders.ContentType, ContentType.Application.Json.toString())
|
||||
setBody(
|
||||
"""{
|
||||
@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"))
|
||||
}""",
|
||||
)
|
||||
}
|
||||
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(
|
||||
"""{
|
||||
// 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<AuthResponse>(loginResponse.bodyAsText())
|
||||
assertNotNull(loginJson.token)
|
||||
assertEquals("Familie Schmidt", loginJson.familyName)
|
||||
}
|
||||
assertEquals(HttpStatusCode.OK, loginResponse.status)
|
||||
val loginJson = Json.decodeFromString<AuthResponse>(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()
|
||||
fun `test tasks endpoint and verify with jwt`() =
|
||||
testApplication {
|
||||
val jwtService = JwtService()
|
||||
val repository = InMemoryKidioRepository()
|
||||
|
||||
application {
|
||||
install(ServerContentNegotiation) {
|
||||
json()
|
||||
application {
|
||||
install(ServerContentNegotiation) {
|
||||
json()
|
||||
}
|
||||
configureSecurity(jwtService)
|
||||
configureRouting(repository, jwtService)
|
||||
}
|
||||
configureSecurity(jwtService)
|
||||
configureRouting(repository, jwtService)
|
||||
}
|
||||
|
||||
val token = jwtService.generateToken("default_user")
|
||||
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<List<Task>>(tasksResponse.bodyAsText())
|
||||
assertEquals(2, tasksJson.size)
|
||||
assertEquals("t1", tasksJson[0].id)
|
||||
assertEquals("Was ist 12 + 15?", tasksJson[0].question)
|
||||
// 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<List<Task>>(tasksResponse.bodyAsText())
|
||||
assertEquals(2, tasksJson.size)
|
||||
assertEquals("t1", tasksJson[0].id)
|
||||
assertEquals("Was ist 12 + 15?", tasksJson[0].question)
|
||||
|
||||
// 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"}""")
|
||||
}
|
||||
assertEquals(HttpStatusCode.OK, verifyResponse.status)
|
||||
val verifyJson = Json.decodeFromString<TaskVerifyResponse>(verifyResponse.bodyAsText())
|
||||
assertTrue(verifyJson.isCorrect)
|
||||
assertEquals(10, verifyJson.earnedMinutes)
|
||||
// 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"}""")
|
||||
}
|
||||
assertEquals(HttpStatusCode.OK, verifyResponse.status)
|
||||
val verifyJson = Json.decodeFromString<TaskVerifyResponse>(verifyResponse.bodyAsText())
|
||||
assertTrue(verifyJson.isCorrect)
|
||||
assertEquals(10, verifyJson.earnedMinutes)
|
||||
|
||||
// 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"}""")
|
||||
// 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"}""")
|
||||
}
|
||||
assertEquals(HttpStatusCode.OK, verifyWrongResponse.status)
|
||||
val verifyWrongJson = Json.decodeFromString<TaskVerifyResponse>(verifyWrongResponse.bodyAsText())
|
||||
assertFalse(verifyWrongJson.isCorrect)
|
||||
assertEquals(0, verifyWrongJson.earnedMinutes)
|
||||
}
|
||||
assertEquals(HttpStatusCode.OK, verifyWrongResponse.status)
|
||||
val verifyWrongJson = Json.decodeFromString<TaskVerifyResponse>(verifyWrongResponse.bodyAsText())
|
||||
assertFalse(verifyWrongJson.isCorrect)
|
||||
assertEquals(0, verifyWrongJson.earnedMinutes)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test screentime sync deducts time repeatedly`() = testApplication {
|
||||
val jwtService = JwtService()
|
||||
val repository = InMemoryKidioRepository()
|
||||
fun `test screentime sync deducts time repeatedly`() =
|
||||
testApplication {
|
||||
val jwtService = JwtService()
|
||||
val repository = InMemoryKidioRepository()
|
||||
|
||||
application {
|
||||
install(ServerContentNegotiation) {
|
||||
json()
|
||||
application {
|
||||
install(ServerContentNegotiation) {
|
||||
json()
|
||||
}
|
||||
configureSecurity(jwtService)
|
||||
configureRouting(repository, jwtService)
|
||||
}
|
||||
configureSecurity(jwtService)
|
||||
configureRouting(repository, jwtService)
|
||||
}
|
||||
|
||||
val token = jwtService.generateToken("default_user")
|
||||
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}""")
|
||||
}
|
||||
assertEquals(HttpStatusCode.OK, syncResponse1.status)
|
||||
val syncJson1 = Json.decodeFromString<SyncResponse>(syncResponse1.bodyAsText())
|
||||
assertEquals(1600, syncJson1.remainingSeconds)
|
||||
assertFalse(syncJson1.isBlocked)
|
||||
// 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}""")
|
||||
}
|
||||
assertEquals(HttpStatusCode.OK, syncResponse1.status)
|
||||
val syncJson1 = Json.decodeFromString<SyncResponse>(syncResponse1.bodyAsText())
|
||||
assertEquals(1600, syncJson1.remainingSeconds)
|
||||
assertFalse(syncJson1.isBlocked)
|
||||
|
||||
// 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":300}""")
|
||||
}
|
||||
assertEquals(HttpStatusCode.OK, syncResponse2.status)
|
||||
val syncJson2 = Json.decodeFromString<SyncResponse>(syncResponse2.bodyAsText())
|
||||
assertEquals(1300, syncJson2.remainingSeconds)
|
||||
assertFalse(syncJson2.isBlocked)
|
||||
// 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":300}""")
|
||||
}
|
||||
assertEquals(HttpStatusCode.OK, syncResponse2.status)
|
||||
val syncJson2 = Json.decodeFromString<SyncResponse>(syncResponse2.bodyAsText())
|
||||
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}""")
|
||||
// 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<SyncResponse>(syncResponse3.bodyAsText())
|
||||
assertEquals(0, syncJson3.remainingSeconds)
|
||||
assertTrue(syncJson3.isBlocked)
|
||||
}
|
||||
assertEquals(HttpStatusCode.OK, syncResponse3.status)
|
||||
val syncJson3 = Json.decodeFromString<SyncResponse>(syncResponse3.bodyAsText())
|
||||
assertEquals(0, syncJson3.remainingSeconds)
|
||||
assertTrue(syncJson3.isBlocked)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue