libeufin

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

WsTest.kt (6070B)


      1 /*
      2 * This file is part of LibEuFin.
      3 * Copyright (C) 2024 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 import io.ktor.http.HttpHeaders
     21 import io.ktor.serialization.kotlinx.*
     22 import io.ktor.server.application.*
     23 import io.ktor.server.routing.*
     24 import io.ktor.server.testing.*
     25 import io.ktor.server.websocket.*
     26 import io.ktor.websocket.*
     27 import kotlinx.coroutines.channels.ClosedReceiveChannelException
     28 import kotlinx.serialization.json.Json
     29 import kotlinx.serialization.json.JsonObject
     30 import kotlinx.serialization.json.decodeFromJsonElement
     31 import kotlinx.serialization.json.encodeToJsonElement
     32 import tech.libeufin.ebics.*
     33 import kotlin.test.Test
     34 import kotlin.test.assertEquals
     35 import kotlin.test.assertIs
     36 
     37 class WsTest {
     38     // WSS params example from the spec
     39     val PARAMS_EXAMPLE = """
     40         {
     41             "URL": "https://bankmitwebsocket.de",
     42             "TOKEN": "550e8400-e29b-11d4-a716-446655440000",
     43             "OTT": "N",
     44             "VALIDITY": "2019-03-21T10:35:22Z",
     45             "PARTNERID": "K1234567",
     46             "USERID": "USER4711"
     47         }
     48         """
     49     // Authorization header example from the spec
     50     val AUTH_EXAMPLE = "Basic SzEyMzQ1NjdfVVNFUjQ3MTE6NTUwZTg0MDAtZTI5Yi0xMWQ0LWE3MTYtNDQ2NjU1NDQwMDAw"
     51     // Notifications examples from the spec
     52     val NOTIFICATION_EXAMPLES = sequenceOf(
     53         """
     54         {
     55             "MCLASS": [
     56                 {
     57                     "NAME": "EBICS-HAA",
     58                     "VERS": "1.0",
     59                     "TIMESTAMP": "2019-05-13T12:21:50Z"
     60                 }
     61             ],
     62             "PARTNERID": "K1234567",
     63             "USERID": "USER471",
     64             "BTF": [
     65                 {
     66                     "SERVICE": "REP",
     67                     "SCOPE": "DE",
     68                     "CONTTYPE": "ZIP",
     69                     "MSGNAME": "camt.054"
     70                 }
     71             ],
     72             "ORDERTYPE": [
     73                 "C5N"
     74             ]
     75         }
     76         """, """
     77         {
     78             "MCLASS": [
     79                 {
     80                     "NAME": "EBICS-HAA",
     81                     "VERS": "1.0",
     82                     "TIMESTAMP": "2019-05-13T12:21:53Z"
     83                 }
     84             ],
     85             "PARTNERID": "K1234567",
     86             "USERID": "USER471",
     87             "BTF": [
     88                 {
     89                     "SERVICE": "REP",
     90                     "SCOPE": "DE",
     91                     "CONTTYPE": "ZIP",
     92                     "MSGNAME": "camt.052"
     93                 },
     94                 {
     95                     "SERVICE": "REP",
     96                     "SCOPE": "DE",
     97                     "OPTION": "SCI",
     98                     "CONTTYPE": "ZIP",
     99                     "MSGNAME": "pain.002"
    100                 }
    101             ],
    102             "ORDERTYPE": [
    103                 "C52",
    104                 "CIZ"
    105             ]
    106         }
    107         """, """
    108         {
    109             "MCLASS": [
    110                 {
    111                     "NAME": "INFO",
    112                     "VERS": "1.0",
    113                     "TIMESTAMP": "2019-03-25T12:25:34Z"
    114                 }
    115             ],
    116             "INFO": [
    117                 {
    118                     "LANG": "EN",
    119                     "FREE": " The EBICS-Service is limited on 30.03.2019 from 10:00 a.m. - 11:00a.m. due to maintenance work "
    120                 }
    121             ]
    122         }
    123         """
    124     )
    125 
    126     /** Test JSON serialization roudtrip */
    127     inline fun <reified B> roundtrip(raw: String): B {
    128         val json: JsonObject = Json.decodeFromString(raw)
    129         val decoded: B = Json.decodeFromJsonElement(json)
    130         val encoded = Json.encodeToJsonElement(decoded)
    131         assertEquals(json, encoded)
    132         return decoded
    133     }
    134 
    135     /** Test our serialization implementation works with spec examples */
    136     @Test
    137     fun serialization() {
    138         roundtrip<WssParams>(PARAMS_EXAMPLE)
    139         for (raw in NOTIFICATION_EXAMPLES) {
    140             roundtrip<WssNotification>(raw)
    141         }
    142     }
    143 
    144     /** Test our implementation works with spec examples */
    145     @Test
    146     fun wss() {
    147         val params: WssParams = Json.decodeFromString(PARAMS_EXAMPLE)
    148 
    149         testApplication {
    150             externalServices {
    151                 hosts(params.URL.replace("https://", "wss://")) {
    152                     install(WebSockets) {
    153                         contentConverter = KotlinxWebsocketSerializationConverter(Json)
    154                     }
    155                     routing {
    156                         webSocket("/") {
    157                             assertEquals(AUTH_EXAMPLE, call.request.headers[HttpHeaders.Authorization])
    158                             // Send all examples
    159                             for (example in NOTIFICATION_EXAMPLES) {
    160                                 send(example)
    161                             }
    162                             close(CloseReason(CloseReason.Codes.NORMAL, "Test done"))
    163                         }
    164                     }
    165                 }
    166             }
    167             var count = 0
    168             try {
    169                 params.connect(client) { msg ->
    170                     count++
    171                     // Check message number and type
    172                     assert(count <= 3)
    173                     if (count == 3) {
    174                         assertIs<WssGeneralInfo>(msg)
    175                     } else {
    176                         assertIs<WssNewData>(msg)
    177                     }
    178                 }
    179             } catch (e: ClosedReceiveChannelException) {
    180                 // Expected
    181             }
    182             // Check receive all messages
    183             assertEquals(3, count)
    184         }
    185     }
    186 }