libeufin

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

client.kt (4594B)


      1 /*
      2  * This file is part of LibEuFin.
      3  * Copyright (C) 2023-2025 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 kotlin.test.assertEquals
     29 
     30 /* ----- Json DSL ----- */
     31 
     32 inline fun obj(from: JsonObject = JsonObject(emptyMap()), builderAction: JsonBuilder.() -> Unit): JsonObject {
     33     val builder = JsonBuilder(from)
     34     builder.apply(builderAction)
     35     return JsonObject(builder.content)
     36 }
     37 
     38 class JsonBuilder(from: JsonObject) {
     39     val content: MutableMap<String, JsonElement> = from.toMutableMap()
     40 
     41     inline infix fun <reified T> String.to(v: T) {
     42         val json = Json.encodeToJsonElement(kotlinx.serialization.serializer<T>(), v)
     43         content[this] = json
     44     }
     45 }
     46 
     47 /* ----- Json body helper ----- */
     48 
     49 inline fun <reified B> HttpRequestBuilder.json(b: B) {
     50     val json = Json.encodeToString(kotlinx.serialization.serializer<B>(), b)
     51     contentType(ContentType.Application.Json)
     52     setBody(json)
     53 }
     54 
     55 inline fun HttpRequestBuilder.json(
     56     from: JsonObject = JsonObject(emptyMap()),
     57     builderAction: JsonBuilder.() -> Unit
     58 ) {
     59     json(obj(from, builderAction))
     60 }
     61 
     62 suspend inline fun <reified B> HttpResponse.json(): B =
     63     Json.decodeFromString(kotlinx.serialization.serializer<B>(), bodyAsText())
     64 
     65 suspend inline fun <reified B> HttpResponse.assertOkJson(lambda: (B) -> Unit = {}): B {
     66     assertOk()
     67     val body = json<B>()
     68     lambda(body)
     69     return body
     70 }
     71 
     72 suspend inline fun <reified B> HttpResponse.assertAcceptedJson(lambda: (B) -> Unit = {}): B {
     73     assertAccepted()
     74     val body = json<B>()
     75     lambda(body)
     76     return body
     77 }
     78 
     79 /* ----- Assert ----- */
     80 
     81 suspend fun HttpResponse.isStatus(status: HttpStatusCode, err: TalerErrorCode?): Boolean {
     82     if (status != this.status) return false
     83     if (err != null) {
     84         val body = json<TalerError>()
     85         return err.code == body.code
     86     }
     87     return true
     88 }
     89 
     90 suspend fun HttpResponse.assertStatus(status: HttpStatusCode, err: TalerErrorCode?): HttpResponse {
     91     assertEquals(status, this.status, if (err != null) "$err" else err)
     92     if (err != null) {
     93         val body = json<TalerError>()
     94         assertEquals(err.code, body.code)
     95     }
     96     return this
     97 }
     98 suspend fun HttpResponse.assertOk(): HttpResponse
     99     = assertStatus(HttpStatusCode.OK, null)
    100 suspend fun HttpResponse.assertNoContent(err: TalerErrorCode? = null): HttpResponse 
    101     = assertStatus(HttpStatusCode.NoContent, err)
    102 suspend fun HttpResponse.assertAccepted(): HttpResponse 
    103     = assertStatus(HttpStatusCode.Accepted, null)
    104 suspend fun HttpResponse.assertNotFound(err: TalerErrorCode): HttpResponse 
    105     = assertStatus(HttpStatusCode.NotFound, err)
    106 suspend fun HttpResponse.assertUnauthorized(err: TalerErrorCode = TalerErrorCode.GENERIC_UNAUTHORIZED): HttpResponse 
    107     = assertStatus(HttpStatusCode.Unauthorized, err)
    108 suspend fun HttpResponse.assertConflict(err: TalerErrorCode): HttpResponse 
    109     = assertStatus(HttpStatusCode.Conflict, err)
    110 suspend fun HttpResponse.assertBadRequest(err: TalerErrorCode = TalerErrorCode.GENERIC_JSON_INVALID): HttpResponse 
    111     = assertStatus(HttpStatusCode.BadRequest, err)
    112 suspend fun HttpResponse.assertForbidden(err: TalerErrorCode = TalerErrorCode.GENERIC_FORBIDDEN): HttpResponse 
    113     = assertStatus(HttpStatusCode.Forbidden, err)
    114 suspend fun HttpResponse.assertNotImplemented(err: TalerErrorCode = TalerErrorCode.END): HttpResponse 
    115     = assertStatus(HttpStatusCode.NotImplemented, err)
    116 suspend fun HttpResponse.assertTooManyRequests(err: TalerErrorCode): HttpResponse 
    117     = assertStatus(HttpStatusCode.TooManyRequests, err)
    118 suspend fun HttpResponse.assertPayloadTooLarge(
    119     err: TalerErrorCode = TalerErrorCode.GENERIC_UPLOAD_EXCEEDS_LIMIT
    120 ): HttpResponse = assertStatus(HttpStatusCode.PayloadTooLarge, err)