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 }