libeufin

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

SubjectTest.kt (6882B)


      1 /*
      2  * This file is part of LibEuFin.
      3  * Copyright (C) 2024-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 import tech.libeufin.common.*
     21 import kotlin.test.*
     22 
     23 class SubjectTest {
     24     fun assertFailsMsg(msg: String, lambda: () -> Unit) {
     25         val failure = assertFails(lambda)
     26         assertEquals(msg, failure.message)
     27     }
     28 
     29     @Test
     30     fun parse() {
     31         val key = "4MZT6RS3RVB3B0E2RDMYW0YRA3Y0VPHYV0CYDE6XBB0YMPFXCEG0";
     32         val other = "00Q979QSMJ29S7BJT3DDAVC5A0DR5Z05B7N0QT1RCBQ8FXJPZ6RG";
     33 
     34         for (ty in sequenceOf(IncomingType.reserve, IncomingType.kyc)) {
     35             val prefix = if (ty == IncomingType.kyc) "KYC" else "";
     36             val standard = "$prefix$key"
     37             val (standardL, standardR) = standard.chunked(standard.length / 2 + 1)
     38             val mixed = "${prefix}4mzt6RS3rvb3b0e2rdmyw0yra3y0vphyv0cyde6xbb0ympfxceg0"
     39             val (mixedL, mixedR) = mixed.chunked(mixed.length / 2 + 1)
     40             val other_standard = "$prefix$other"
     41             val other_mixed = "${prefix}TEGY6d9mh9pgwvwpgs0z0095z854xegfy7jj202yd0esp8p0za60"
     42             val key = if (ty == IncomingType.reserve) {
     43                 IncomingSubject.Reserve(EddsaPublicKey(key))
     44             } else {
     45                 IncomingSubject.Kyc(EddsaPublicKey(key))
     46             }
     47             
     48             // Check succeed if standard or mixed
     49             for (case in sequenceOf(standard, mixed)) {
     50                 for (test in sequenceOf(
     51                     "noise $case noise",
     52                     "$case noise to the right",
     53                     "noise to the left $case",
     54                     "    $case     ",
     55                     "noise\n$case\nnoise",
     56                     "Test+$case"
     57                 )) {
     58                     assertEquals(key, parseIncomingSubject(test))
     59                 }
     60             }
     61 
     62             // Check succeed if standard or mixed and split
     63             for ((L, R) in sequenceOf(standardL to standardR, mixedL to mixedR)) {
     64                 for (case in sequenceOf(
     65                     "left $L$R right",
     66                     "left $L $R right",
     67                     "left $L-$R right",
     68                     "left $L+$R right",
     69                     "left $L\n$R right",
     70                     "left $L%20$R right",
     71                     "left $L-+\n$R right",
     72                     "left $L - $R right",
     73                     "left $L + $R right",
     74                     "left $L \n $R right",
     75                     "left $L - + \n $R right",
     76                 )) {
     77                     assertEquals(key, parseIncomingSubject(case))
     78                 }
     79             }
     80 
     81             // Check concat parts
     82             for (chunkSize in 1 until standard.length) {
     83                 val chunked = standard.chunked(chunkSize).joinToString(" ")
     84                 for (case in sequenceOf(chunked, "left ${chunked} right")) {
     85                     assertEquals(key, parseIncomingSubject(case))
     86                 }
     87             }
     88 
     89             // Check failed when multiple key
     90             for (case in sequenceOf(
     91                 "$standard $other_standard",
     92                 "$mixed $other_mixed",
     93             )) {
     94                 assertFailsMsg("found multiple reserve public key") {
     95                     parseIncomingSubject(case)
     96                 }
     97             }
     98 
     99             // Check accept redundant key
    100             for (case in sequenceOf(
    101                 "$standard $standard $mixed $mixed",   // Accept redundant key
    102                 "$mixedL-$mixedR $standardL-$standardR",
    103                 "$standard $other_mixed",              // Prefer high quality
    104             )) {
    105                 assertEquals(key, parseIncomingSubject(case))
    106             }
    107             
    108             // Check failure if malformed or missing
    109             for (case in sequenceOf(
    110                 "does not contain any reserve",                        // Check fail if none
    111                 standard.substring(1),                    // Check fail if missing char
    112                 "2MZT6RS3RVB3B0E2RDMYW0YRA3Y0VPHYV0CYDE6XBB0YMPFXCEG0" // Check fail if not a valid key
    113             )) {
    114                 assertFailsMsg("missing reserve public key") {
    115                     parseIncomingSubject(case)
    116                 }
    117             }
    118 
    119             if (ty == IncomingType.kyc) {
    120                 // Prefer prefixed over unprefixed
    121                 for (case in sequenceOf(
    122                     "$other $standard", "$other $mixed"
    123                 )) {
    124                     assertEquals(key, parseIncomingSubject(case))
    125                 }
    126             }
    127         }
    128 
    129         // Admin balance adjust
    130         for (subject in sequenceOf(
    131             "ADMIN BALANCE ADJUST",
    132             "ADMIN:BALANCE:ADJUST",
    133             "AdminBalanceAdjust",
    134             "ignore aDmIn:BaLaNCe AdJUsT"
    135         )) {
    136             assertEquals(
    137                 IncomingSubject.AdminBalanceAdjust,
    138                 parseIncomingSubject(subject)
    139             )
    140         }
    141     }
    142 
    143     /** Test parsing logic using real use case */
    144     @Test
    145     fun real() {
    146         // Good reserve cases
    147         for ((subject, key) in sequenceOf(
    148             "Taler TEGY6d9mh9pgwvwpgs0z0095z854xegfy7j j202yd0esp8p0za60" to "TEGY6d9mh9pgwvwpgs0z0095z854xegfy7jj202yd0esp8p0za60",
    149             "00Q979QSMJ29S7BJT3DDAVC5A0DR5Z05B7N 0QT1RCBQ8FXJPZ6RG" to "00Q979QSMJ29S7BJT3DDAVC5A0DR5Z05B7N0QT1RCBQ8FXJPZ6RG",
    150             "Taler NDDCAM9XN4HJZFTBD8V6FNE2FJE8G Y734PJ5AGQMY06C8D4HB3Z0" to "NDDCAM9XN4HJZFTBD8V6FNE2FJE8GY734PJ5AGQMY06C8D4HB3Z0",
    151             "KYCVEEXTBXBEMCS5R64C24GFNQVWBN5R2F9QSQ7PN8QXAP1NG4NG" to "KYCVEEXTBXBEMCS5R64C24GFNQVWBN5R2F9QSQ7PN8QXAP1NG4NG",
    152             "Taler%20NDDCAM9XN4HJZFTBD8V6FNE2FJE8G Y734PJ5AGQMY06C8D4HB3Z0" to "NDDCAM9XN4HJZFTBD8V6FNE2FJE8GY734PJ5AGQMY06C8D4HB3Z0",
    153         )) {
    154             assertEquals(
    155                 IncomingSubject.Reserve(EddsaPublicKey(key)),
    156                 parseIncomingSubject(subject)
    157             )
    158         }
    159         // Good kyc cases
    160         for ((subject, key) in sequenceOf(
    161             "KYC JW398X85FWPKKMS0EYB6TQ1799RMY5DDXTZ FPW4YC3WJ2DWSJT70" to "JW398X85FWPKKMS0EYB6TQ1799RMY5DDXTZFPW4YC3WJ2DWSJT70"
    162         )) {
    163             assertEquals(
    164                 IncomingSubject.Kyc(EddsaPublicKey(key)),
    165                 parseIncomingSubject(subject)
    166             )
    167         }
    168     }
    169 }