libeufin

Integration and sandbox testing for FinTech APIs and data formats
Log | Files | Refs | Submodules | README | LICENSE

client.kt (4920B)


      1 /*
      2  * This file is part of LibEuFin.
      3  * Copyright (C) 2023-2025, 2026 Taler Systems S.A.
      4 
      5  * LibEuFin is free software; you can redistribute it and/or modify
      6  * it under the terms of the GNU Affero General Public License as
      7  * published by the Free Software Foundation; either version 3, or
      8  * (at your option) any later version.
      9 
     10  * LibEuFin is distributed in the hope that it will be useful, but
     11  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
     12  * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General
     13  * Public License for more details.
     14 
     15  * You should have received a copy of the GNU Affero General Public
     16  * License along with LibEuFin; see the file COPYING.  If not, see
     17  * <http://www.gnu.org/licenses/>
     18  */
     19 
     20 package tech.libeufin.common
     21 
     22 import io.ktor.client.request.*
     23 import io.ktor.client.statement.*
     24 import io.ktor.http.*
     25 import kotlinx.serialization.json.Json
     26 import kotlinx.serialization.json.JsonElement
     27 import kotlinx.serialization.json.JsonObject
     28 import kotlinx.serialization.json.encodeToJsonElement
     29 import kotlinx.serialization.json.jsonObject
     30 import kotlin.test.assertEquals
     31 
     32 /* ----- Json DSL ----- */
     33 
     34 inline fun obj(from: JsonObject = JsonObject(emptyMap()), builderAction: JsonBuilder.() -> Unit): JsonObject {
     35     val builder = JsonBuilder(from)
     36     builder.apply(builderAction)
     37     return JsonObject(builder.content)
     38 }
     39 
     40 class JsonBuilder(from: JsonObject) {
     41     val content: MutableMap<String, JsonElement> = from.toMutableMap()
     42 
     43     inline infix fun <reified T> String.to(v: T) {
     44         val json = Json.encodeToJsonElement(kotlinx.serialization.serializer<T>(), v)
     45         content[this] = json
     46     }
     47 }
     48 
     49 /* ----- Json body helper ----- */
     50 
     51 inline fun <reified B> HttpRequestBuilder.json(b: B) {
     52     val json = Json.encodeToString(kotlinx.serialization.serializer<B>(), b)
     53     contentType(ContentType.Application.Json)
     54     setBody(json)
     55 }
     56 
     57 inline fun <reified B> HttpRequestBuilder.json(
     58     from: B,
     59     builderAction: JsonBuilder.() -> Unit
     60 ) {
     61     json(obj(Json.encodeToJsonElement(kotlinx.serialization.serializer<B>(), from).jsonObject, builderAction))
     62 }
     63 
     64 inline fun HttpRequestBuilder.json(
     65     from: JsonObject = JsonObject(emptyMap()),
     66     builderAction: JsonBuilder.() -> Unit
     67 ) {
     68     json(obj(from, builderAction))
     69 }
     70 
     71 suspend inline fun <reified B> HttpResponse.json(): B =
     72     Json.decodeFromString(kotlinx.serialization.serializer<B>(), bodyAsText())
     73 
     74 suspend inline fun <reified B> HttpResponse.assertOkJson(lambda: (B) -> Unit = {}): B {
     75     assertOk()
     76     val body = json<B>()
     77     lambda(body)
     78     return body
     79 }
     80 
     81 suspend inline fun <reified B> HttpResponse.assertAcceptedJson(lambda: (B) -> Unit = {}): B {
     82     assertAccepted()
     83     val body = json<B>()
     84     lambda(body)
     85     return body
     86 }
     87 
     88 /* ----- Assert ----- */
     89 
     90 suspend fun HttpResponse.isStatus(status: HttpStatusCode, err: TalerErrorCode?): Boolean {
     91     if (status != this.status) return false
     92     if (err != null) {
     93         val body = json<TalerError>()
     94         return err.code == body.code
     95     }
     96     return true
     97 }
     98 
     99 suspend fun HttpResponse.assertStatus(status: HttpStatusCode, err: TalerErrorCode?): HttpResponse {
    100     assertEquals(status, this.status, if (err != null) "$err" else err)
    101     if (err != null) {
    102         val body = json<TalerError>()
    103         assertEquals(err.code, body.code)
    104     }
    105     return this
    106 }
    107 suspend fun HttpResponse.assertOk(): HttpResponse
    108     = assertStatus(HttpStatusCode.OK, null)
    109 suspend fun HttpResponse.assertNoContent(err: TalerErrorCode? = null): HttpResponse 
    110     = assertStatus(HttpStatusCode.NoContent, err)
    111 suspend fun HttpResponse.assertAccepted(): HttpResponse 
    112     = assertStatus(HttpStatusCode.Accepted, null)
    113 suspend fun HttpResponse.assertNotFound(err: TalerErrorCode): HttpResponse 
    114     = assertStatus(HttpStatusCode.NotFound, err)
    115 suspend fun HttpResponse.assertUnauthorized(err: TalerErrorCode = TalerErrorCode.GENERIC_UNAUTHORIZED): HttpResponse 
    116     = assertStatus(HttpStatusCode.Unauthorized, err)
    117 suspend fun HttpResponse.assertConflict(err: TalerErrorCode): HttpResponse 
    118     = assertStatus(HttpStatusCode.Conflict, err)
    119 suspend fun HttpResponse.assertBadRequest(err: TalerErrorCode = TalerErrorCode.GENERIC_JSON_INVALID): HttpResponse 
    120     = assertStatus(HttpStatusCode.BadRequest, err)
    121 suspend fun HttpResponse.assertForbidden(err: TalerErrorCode = TalerErrorCode.GENERIC_FORBIDDEN): HttpResponse 
    122     = assertStatus(HttpStatusCode.Forbidden, err)
    123 suspend fun HttpResponse.assertNotImplemented(err: TalerErrorCode = TalerErrorCode.END): HttpResponse 
    124     = assertStatus(HttpStatusCode.NotImplemented, err)
    125 suspend fun HttpResponse.assertTooManyRequests(err: TalerErrorCode): HttpResponse 
    126     = assertStatus(HttpStatusCode.TooManyRequests, err)
    127 suspend fun HttpResponse.assertPayloadTooLarge(
    128     err: TalerErrorCode = TalerErrorCode.GENERIC_UPLOAD_EXCEEDS_LIMIT
    129 ): HttpResponse = assertStatus(HttpStatusCode.PayloadTooLarge, err)