libeufin

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

RegistrationTest.kt (35849B)


      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 org.junit.Test
     21 import tech.libeufin.common.*
     22 import tech.libeufin.common.db.*
     23 import tech.libeufin.nexus.*
     24 import tech.libeufin.nexus.cli.*
     25 import tech.libeufin.nexus.db.*
     26 import tech.libeufin.nexus.iso20022.*
     27 import tech.libeufin.ebics.*
     28 import java.nio.file.Files
     29 import java.time.Instant
     30 import java.util.UUID
     31 import kotlin.io.path.*
     32 import kotlin.test.*
     33 
     34 /** End-to-end test for XML file registration */
     35 class RegistrationTest {
     36 
     37     /** Register batches of initiated payments for reconcciliation */
     38     suspend fun Database.batches(batches: Map<String, List<InitiatedPayment>>): List<PaymentBatch> {
     39         val tmp = mutableListOf<PaymentBatch>()
     40         for ((name, txs) in batches) {
     41             for (tx in txs) {
     42                 initiated.create(tx)
     43             }
     44             this.initiated.batch(Instant.now(), name, false)
     45             val (batch) = this.initiated.submittable()
     46             this.initiated.batchSubmissionSuccess(batch.id, Instant.now(), name.replace("BATCH", "ORDER"))
     47             tmp.add(batch)
     48         }
     49         return tmp
     50     }
     51 
     52     /** Register an XML sample into the database */
     53     suspend fun Database.register(
     54         cfg: NexusConfig,
     55         path: String,
     56         doc: OrderDoc
     57     ) {
     58         registerFile(this, cfg, Files.newInputStream(Path(path)), doc)
     59     }
     60 
     61     /** Check database content */
     62     suspend fun Database.check(
     63         status: Map<String, Pair<SubmissionState, Map<String, SubmissionState>>>,
     64         incoming: List<IncomingPayment>,
     65         outgoing: List<OutgoingPayment>,
     66         bounced: List<OutgoingPayment>
     67     ) {
     68         // Check batch status
     69         val batch_status = this.serializable(
     70             """
     71             SELECT message_id, status FROM initiated_outgoing_batches ORDER BY initiated_outgoing_batch_id
     72             """
     73         ) {
     74             all { 
     75                 Pair(
     76                     it.getString("message_id"),
     77                     it.getEnum<SubmissionState>("status")
     78                 )
     79             }
     80         }
     81         assertContentEquals(status.map { Pair(it.key, it.value.first) }, batch_status)
     82 
     83         // Check transactions status
     84         val batch_tx = this.serializable(
     85             """
     86             SELECT message_id, end_to_end_id, initiated_outgoing_transactions.status 
     87             FROM initiated_outgoing_transactions
     88                 JOIN initiated_outgoing_batches USING (initiated_outgoing_batch_id)
     89             ORDER BY initiated_outgoing_batch_id, initiated_outgoing_transaction_id
     90             """
     91         ) {
     92             all { 
     93                 Triple(
     94                     it.getString("message_id"),
     95                     it.getString("end_to_end_id"),
     96                     it.getEnum<SubmissionState>("status")
     97                 )
     98             }.groupBy(
     99                 keySelector = { it.first },
    100                 valueTransform = { Pair(it.second, it.third) }
    101             ).mapValues { it.value.toMap() }
    102         }
    103         assertContentEquals(status.mapValues { it.value.second }.toList(), batch_tx.toList())
    104 
    105         // Check incoming transactions
    106         val incoming_tx = this.serializable(
    107             """
    108             SELECT 
    109                  uetr
    110                 ,tx_id
    111                 ,acct_svcr_ref
    112                 ,(amount).val as amount_val
    113                 ,(amount).frac AS amount_frac
    114                 ,(credit_fee).val AS credit_fee_val
    115                 ,(credit_fee).frac AS credit_fee_frac
    116                 ,subject
    117                 ,execution_time
    118                 ,debit_payto
    119             FROM incoming_transactions
    120             ORDER BY incoming_transaction_id
    121             """
    122         ) {
    123             all { 
    124                 IncomingPayment(
    125                     id = IncomingId(
    126                         it.getObject("uetr") as UUID?,
    127                         it.getString("tx_id"),
    128                         it.getString("acct_svcr_ref"),
    129                     ),
    130                     amount = it.getAmount("amount", this@check.currency),
    131                     creditFee = it.getAmount("credit_fee", this@check.currency).notZeroOrNull(),
    132                     subject = it.getString("subject"),
    133                     executionTime = it.getLong("execution_time").asInstant(),
    134                     debtor = it.getOptIbanPayto("debit_payto"),
    135                 )
    136             }
    137         }
    138         assertContentEquals(incoming, incoming_tx)
    139 
    140         // Check outgoing transactions
    141         val outgoing_tx = this.serializable(
    142             """
    143             SELECT end_to_end_id
    144                 ,acct_svcr_ref
    145                 ,(amount).val as amount_val
    146                 ,(amount).frac AS amount_frac
    147                 ,(debit_fee).val AS debit_fee_val
    148                 ,(debit_fee).frac AS debit_fee_frac
    149                 ,subject
    150                 ,execution_time
    151                 ,credit_payto
    152             FROM outgoing_transactions
    153             ORDER BY outgoing_transaction_id
    154             """
    155         ) {
    156             all {
    157                 OutgoingPayment(
    158                     id = OutgoingId(null, it.getString("end_to_end_id"), it.getString("acct_svcr_ref")),
    159                     amount = it.getAmount("amount", this@check.currency),
    160                     debitFee = it.getAmount("debit_fee", this@check.currency).notZeroOrNull(),
    161                     subject = it.getString("subject"),
    162                     executionTime = it.getLong("execution_time").asInstant(),
    163                     creditor = it.getOptIbanPayto("credit_payto"),
    164                 )
    165             }
    166         }
    167         assertContentEquals(outgoing, outgoing_tx)
    168 
    169         // Check outgoing transactions
    170         val bounced_tx = this.serializable(
    171             """
    172             SELECT end_to_end_id
    173                 ,(amount).val as amount_val
    174                 ,(amount).frac as amount_frac
    175                 ,subject
    176                 ,credit_payto
    177             FROM initiated_outgoing_transactions
    178                 JOIN bounced_transactions USING (initiated_outgoing_transaction_id)
    179             """
    180         ) {
    181             all {
    182                 OutgoingPayment(
    183                     id = OutgoingId(null, null, null),
    184                     amount = it.getAmount("amount", this@check.currency),
    185                     subject = it.getString("subject"),
    186                     executionTime = Instant.EPOCH,
    187                     creditor = it.getOptIbanPayto("credit_payto"),
    188                 )
    189             }
    190         }
    191         assertContentEquals(bounced, bounced_tx)
    192     }
    193 
    194     @Test
    195     fun pain001() = setup { db, cfg -> 
    196         val (batch) = db.batches(mapOf(
    197             "MESSAGE_ID" to listOf(
    198                 genInitPay(
    199                     endToEndId = "TX_FIRST",
    200                     amount = "EUR:42",
    201                     subject = "Test 42",
    202                 ),
    203                 genInitPay(
    204                     endToEndId = "TX_SECOND",
    205                     amount = "EUR:5.11",
    206                     subject = "Test 5.11",
    207                 ),
    208                 genInitPay(
    209                     endToEndId = "TX_THIRD",
    210                     amount = "EUR:0.21",
    211                     subject = "Test 0.21",
    212                 ),
    213             ),
    214         ))
    215         val msg = batchToPain001Msg(cfg.ebics.account, batch).copy(timestamp = dateToInstant("2024-09-09"),)
    216         for (dialect in Dialect.entries) {
    217             println(dialect)
    218             assertEquals(
    219                 Path("sample/platform/${dialect}_pain001.xml").readText().replace("VERSION", VERSION),
    220                 createPain001(msg, dialect, false).asUtf8()
    221             )
    222         }
    223     }
    224 
    225     /** HAC order id test */
    226     @Test
    227     fun hac() = setup { db, cfg ->
    228         db.batches(mapOf(
    229             "BATCH_SUCCESS" to listOf(
    230                 genInitPay("BATCH_SUCCESS_0"),
    231                 genInitPay("BATCH_SUCCESS_1"),
    232             ),
    233             "BATCH_FAILURE" to listOf(
    234                 genInitPay("BATCH_FAILURE_0"),
    235                 genInitPay("BATCH_FAILURE_1"),
    236             )
    237         ))
    238 
    239         // Register HAC files
    240         db.register(cfg, "sample/platform/hac.xml", OrderDoc.acknowledgement)
    241         
    242         // Check state
    243         db.check(
    244             status = mapOf(
    245                 "BATCH_SUCCESS" to Pair(SubmissionState.success, mapOf(
    246                     "BATCH_SUCCESS_0" to SubmissionState.pending,
    247                     "BATCH_SUCCESS_1" to SubmissionState.pending,
    248                 )),
    249                 "BATCH_FAILURE" to Pair(SubmissionState.permanent_failure, mapOf(
    250                     "BATCH_FAILURE_0" to SubmissionState.permanent_failure,
    251                     "BATCH_FAILURE_1" to SubmissionState.permanent_failure,
    252                 ))
    253             ),
    254             incoming = emptyList(),
    255             outgoing = emptyList(),
    256             bounced = emptyList()
    257         )
    258     }
    259 
    260     /** CreditSuisse dialect test */
    261     @Test
    262     fun cs() = setup { db, cfg ->
    263         db.batches(mapOf(
    264             "05BD4C5B4A2649B5B08F6EF6A31F197A" to listOf(
    265                 genInitPay("AQCXNCPWD8PHW5JTN65Y5XTF7R"),
    266                 genInitPay("EE9SX76FC5YSC657EK3GMVZ9TC"),
    267                 genInitPay("V5B3MXPEWES9VQW1JDRD6VAET4"),
    268                 genInitPay("M9NGRCAC1FBX3ENX3XEDEPJ2JW"),
    269             ),
    270         ))
    271 
    272         // Register pain files
    273         db.register(cfg, "sample/platform/pain002_part.xml", OrderDoc.status)
    274 
    275         // Check state
    276         db.check(
    277             status = mapOf(
    278                 "05BD4C5B4A2649B5B08F6EF6A31F197A" to Pair(SubmissionState.pending, mapOf(
    279                     "AQCXNCPWD8PHW5JTN65Y5XTF7R" to SubmissionState.permanent_failure,
    280                     "EE9SX76FC5YSC657EK3GMVZ9TC" to SubmissionState.permanent_failure,
    281                     "V5B3MXPEWES9VQW1JDRD6VAET4" to SubmissionState.permanent_failure,
    282                     "M9NGRCAC1FBX3ENX3XEDEPJ2JW" to SubmissionState.pending,
    283                 )),
    284             ),
    285             incoming = emptyList(),
    286             outgoing = emptyList(),
    287             bounced = emptyList()
    288         )
    289     }
    290 
    291     /** Valiant dialect test */
    292     @Test
    293     fun valiant() = setup("valiant.conf") { db, cfg ->
    294         db.batches(mapOf(
    295             "MJDJO2BDDBL7YSL2P96SXHG3TQZEZQD26L" to listOf(
    296                 genInitPay("4UWWIDGTEIGDU6Z721QE95PYJSIEA48PYE"),
    297             ),
    298             "5HIS3433VVIBAANHW3GX9DR1AXRS43KZ4U" to listOf(
    299                 genInitPay("SKMU2891PAAYBDW22DBWX2W7KTFZ1CDFO8"),
    300                 genInitPay("RC9YD301NZ17YKD6WDWLNOROFHIIN29VJN"),
    301                 genInitPay("GKDGTHLB82X6XVHBJIJ1CK8MEGU9XJ2EL7"),
    302                 genInitPay("PXCH2VVVTXEXBVDWICP23HZ4NV0H2CWW28"),
    303             ),
    304             "X166701F6RV59LP71RVWVIW9SV2AFZYLG4" to listOf(
    305                 genInitPay("R48UBIIB7B4LX0DMVOSI0ZTJWMMG8FMNKX"),
    306             ),
    307             "OLAMDPI6YPMNRZHQ5PQ6JCVUQV2AN5NW6P" to listOf(
    308                 genInitPay("TU2WJ54DR9Z6HT5VE494BNH4EXUSM0DRF7"),
    309             ),
    310             "6OZN5T9W7MK6BIZYE01E62NHGP5JLMUD4X" to listOf(
    311                 genInitPay("02WDIX4J90Z1M1WNFHLNSXY59SHXQTQCMQ"),
    312                 genInitPay("XAP5L7HVWPLCEMECU4GZK6GKUPBL0TD13Y"),
    313                 genInitPay("GM8I8GIETR72LP6CFBGRBUDKNO2CEQBGOE")
    314             ),
    315         ))
    316 
    317         // Register camt files
    318         db.register(cfg, "sample/platform/valiant_camt052.xml", OrderDoc.report)
    319 
    320         // Check state
    321         db.check(
    322             status = mapOf(
    323                 "MJDJO2BDDBL7YSL2P96SXHG3TQZEZQD26L" to Pair(SubmissionState.success, mapOf(
    324                     "4UWWIDGTEIGDU6Z721QE95PYJSIEA48PYE" to SubmissionState.success
    325                 )),
    326                 "5HIS3433VVIBAANHW3GX9DR1AXRS43KZ4U" to Pair(SubmissionState.success, mapOf(
    327                     "SKMU2891PAAYBDW22DBWX2W7KTFZ1CDFO8" to SubmissionState.success,
    328                     "RC9YD301NZ17YKD6WDWLNOROFHIIN29VJN" to SubmissionState.success,
    329                     "GKDGTHLB82X6XVHBJIJ1CK8MEGU9XJ2EL7" to SubmissionState.success,
    330                     "PXCH2VVVTXEXBVDWICP23HZ4NV0H2CWW28" to SubmissionState.success
    331                 )),
    332                 "X166701F6RV59LP71RVWVIW9SV2AFZYLG4" to Pair(SubmissionState.success, mapOf(
    333                     "R48UBIIB7B4LX0DMVOSI0ZTJWMMG8FMNKX" to SubmissionState.late_failure
    334                 )),
    335                 "OLAMDPI6YPMNRZHQ5PQ6JCVUQV2AN5NW6P" to Pair(SubmissionState.success, mapOf(
    336                     "TU2WJ54DR9Z6HT5VE494BNH4EXUSM0DRF7" to SubmissionState.success
    337                 )),
    338                 "6OZN5T9W7MK6BIZYE01E62NHGP5JLMUD4X" to Pair(SubmissionState.success, mapOf(
    339                     "02WDIX4J90Z1M1WNFHLNSXY59SHXQTQCMQ" to SubmissionState.success,
    340                     "XAP5L7HVWPLCEMECU4GZK6GKUPBL0TD13Y" to SubmissionState.late_failure,
    341                     "GM8I8GIETR72LP6CFBGRBUDKNO2CEQBGOE" to SubmissionState.success
    342                 )),
    343             ),
    344             incoming = listOf(
    345                 IncomingPayment(
    346                     id = IncomingId(null, "51030655601.0001", "ZV20251030/514778/1"),
    347                     amount = TalerAmount("CHF:0.85"),
    348                     subject = "fun stuff",
    349                     executionTime = dateToInstant("2025-10-30"),
    350                     debtor = ibanPayto("CH7389144832588726658", "Grothoff Hans"),
    351                 ),
    352                 IncomingPayment(
    353                     id = IncomingId(null, "51030655601.0002", "ZV20251030/514779/1"),
    354                     amount = TalerAmount("CHF:0.95"),
    355                     subject = "Taler PC2MKG0B7CK32K1T7DP08P6E1B7FHB6HY6R Q0PT3VTPBPRPYM1B0",
    356                     executionTime = dateToInstant("2025-10-30"),
    357                     debtor = ibanPayto("CH7389144832588726658", "Grothoff Hans"),
    358                 ),
    359                 IncomingPayment(
    360                     id = IncomingId("7b76d488-05d5-44ab-9d77-31d4165ec158", "00204EQY370", "ZV20251118/685062/1"),
    361                     amount = TalerAmount("CHF:4.55"),
    362                     subject = "TEST",
    363                     executionTime = dateToInstant("2025-11-18"),
    364                     debtor = null
    365                 ),
    366             ),
    367             outgoing = listOf(
    368                 OutgoingPayment(
    369                     id = OutgoingId(null, "4UWWIDGTEIGDU6Z721QE95PYJSIEA48PYE", "ZV20251030/511372/1"),
    370                     amount = TalerAmount("CHF:0.1"),
    371                     creditor = ibanPayto("CH7389144832588726658", "Grothoff Hans"),
    372                     subject = "single 2025-10-30T09:46:04.55293090 9Z",
    373                     executionTime = dateToInstant("2025-10-30")
    374                 ),
    375                 OutgoingPayment(
    376                     id = OutgoingId(null, "SKMU2891PAAYBDW22DBWX2W7KTFZ1CDFO8", "ZV20251030/511373/1"),
    377                     amount = TalerAmount("CHF:0.1"),
    378                     creditor = ibanPayto("CH7389144832588726658", "Grothoff Hans"),
    379                     subject = "multi 0 2025-10-30T09:46:10.3877961 30Z",
    380                     executionTime = dateToInstant("2025-10-30")
    381                 ),
    382                 OutgoingPayment(
    383                     id = OutgoingId(null, "RC9YD301NZ17YKD6WDWLNOROFHIIN29VJN", "ZV20251030/511373/2"),
    384                     amount = TalerAmount("CHF:0.11"),
    385                     creditor = ibanPayto("CH7389144832588726658", "Grothoff Hans"),
    386                     subject = "multi 1 2025-10-30T09:46:10.3877961 30Z",
    387                     executionTime = dateToInstant("2025-10-30")
    388                 ),
    389                 OutgoingPayment(
    390                     id = OutgoingId(null, "GKDGTHLB82X6XVHBJIJ1CK8MEGU9XJ2EL7", "ZV20251030/511373/3"),
    391                     amount = TalerAmount("CHF:0.12"),
    392                     creditor = ibanPayto("CH7389144832588726658", "Grothoff Hans"),
    393                     subject = "multi 2 2025-10-30T09:46:10.3877961 30Z",
    394                     executionTime = dateToInstant("2025-10-30")
    395                 ),
    396                 OutgoingPayment(
    397                     id = OutgoingId(null, "PXCH2VVVTXEXBVDWICP23HZ4NV0H2CWW28", "ZV20251030/511373/4"),
    398                     amount = TalerAmount("CHF:0.13"),
    399                     creditor = ibanPayto("CH7389144832588726658", "Grothoff Hans"),
    400                     subject = "multi 3 2025-10-30T09:46:10.3877961 30Z",
    401                     executionTime = dateToInstant("2025-10-30")
    402                 ),
    403                 OutgoingPayment(
    404                     id = OutgoingId(null, "R48UBIIB7B4LX0DMVOSI0ZTJWMMG8FMNKX", "ZV20251030/524078/1"),
    405                     amount = TalerAmount("CHF:0.21"),
    406                     creditor = ibanPayto("CH6208704048981247126", "John Smith"),
    407                     subject = "bad name 2025-10-30T12:03:24.997478 811Z",
    408                     executionTime = dateToInstant("2025-10-30")
    409                 ),
    410                 OutgoingPayment(
    411                     id = OutgoingId(null, "02WDIX4J90Z1M1WNFHLNSXY59SHXQTQCMQ", "ZV20251030/524079/1"),
    412                     amount = TalerAmount("CHF:0.1"),
    413                     creditor = ibanPayto("CH7389144832588726658", "Grothoff Hans"),
    414                     subject = "single 2025-10-30T12:04:00.37042083 6Z",
    415                     executionTime = dateToInstant("2025-10-30")
    416                 ),
    417                 OutgoingPayment(
    418                     id = OutgoingId(null, "XAP5L7HVWPLCEMECU4GZK6GKUPBL0TD13Y", "ZV20251030/524079/2"),
    419                     amount = TalerAmount("CHF:0.21"),
    420                     creditor = ibanPayto("CH6208704048981247126", "John Smith"),
    421                     subject = "bad name 2025-10-30T12:03:53.042190 686Z",
    422                     executionTime = dateToInstant("2025-10-30")
    423                 ),
    424                 OutgoingPayment(
    425                     id = OutgoingId(null, "TU2WJ54DR9Z6HT5VE494BNH4EXUSM0DRF7", "ZV20251030/524077/1"),
    426                     amount = TalerAmount("CHF:0.23"),
    427                     debitFee = TalerAmount("CHF:5"),
    428                     creditor = ibanPayto("DE48330605920000686018", "Christian Grothoff"),
    429                     subject = "foreign iban 2025-10-30T12:03:44.0972 63765Z",
    430                     executionTime = dateToInstant("2025-10-30")
    431                 ),
    432                 OutgoingPayment(
    433                     id = OutgoingId(null, "GM8I8GIETR72LP6CFBGRBUDKNO2CEQBGOE", "ZV20251030/524080/1"),
    434                     amount = TalerAmount("CHF:0.23"),
    435                     debitFee = TalerAmount("CHF:5"),
    436                     creditor = ibanPayto("DE48330605920000686018", "Christian Grothoff"),
    437                     subject = "foreign iban 2025-10-30T12:03:58.0046 73747Z",
    438                     executionTime = dateToInstant("2025-10-30")
    439                 ),
    440             ),
    441             bounced = listOf(
    442                 OutgoingPayment(
    443                     id = OutgoingId(null, null, null),
    444                     amount = TalerAmount("CHF:0.85"),
    445                     subject = "bounce 51030655601.0001: missing reserve public key",
    446                     executionTime = Instant.EPOCH,
    447                     creditor = ibanPayto("CH7389144832588726658", "Grothoff Hans")
    448                 ),
    449             )
    450         )
    451     }
    452 
    453     /** GLS dialect test */
    454     @Test
    455     fun gls() = setup("gls.conf") { db, cfg ->
    456         db.batches(mapOf(
    457             "COMPAT_SUCCESS" to listOf(
    458                 genInitPay("COMPAT_SUCCESS")
    459             ),
    460             "COMPAT_FAILURE" to listOf(
    461                 genInitPay("COMPAT_FAILURE")
    462             ),
    463             "BATCH_SINGLE_SUCCESS" to listOf(
    464                 genInitPay("FD622SMXKT5QWSAHDY0H8NYG3G"),
    465             ),
    466             // JEYMR3OYZTFM7505OWWENFPAH53LNOWJHS
    467             "BATCH_SINGLE_FAILURE" to listOf(
    468                 genInitPay("DAFC3NEE4T48WVC560T76ABA2C"),
    469             ),
    470             "BATCH_SINGLE_RETURN" to listOf(
    471                 genInitPay("KLJJ28S1LVNDK1R2HCHLN884M7EKM5XGM5"),
    472             ),
    473             "BATCH_MANY_SUCCESS" to listOf(
    474                 genInitPay("IVMIGCUIE7Q7VOF73R8GU3KGRYBZPAYC5V"),
    475                 genInitPay("CDFN7I4FVIZ848DGDQ35DZ2K49H9EWXGAW"),
    476                 genInitPay("35M1268GW5ZFHS5JCB41UKDQNPMD40T849"),
    477                 genInitPay("HPOMV7A4E3P1TK9UZJS1WTM94A9V3X2SR1"),
    478             ),
    479             "BATCH_MANY_PART" to listOf(
    480                 genInitPay("27SK3166EG36SJ7VP7VFYP0MW8"),
    481                 genInitPay("KGTDBASWTJ6JM89WXD3Q5KFQC4"),
    482                 genInitPay("8XK8Z7RAX224FGWK832FD40GYC"),
    483             ),
    484             // ZQOOPJC1DYBP52X119YGO6WMXU6NWIDPJK
    485             "BATCH_MANY_FAILURE" to listOf(
    486                 genInitPay("4XTPKWE4A9V90PRQJCT8Z3MQZ8"),
    487                 genInitPay("3VZZHVYJ6XP2SNPKWF4D4YVHNG"),
    488             )
    489         ))
    490 
    491         // Register camt files
    492         db.register(cfg, "sample/platform/gls_camt052.xml", OrderDoc.report)
    493         db.register(cfg, "sample/platform/gls_camt053.xml", OrderDoc.statement)
    494         // TODO camt054 with missing id before and after
    495 
    496         // Check state
    497         db.check(
    498             status = mapOf(
    499                 "COMPAT_SUCCESS" to Pair(SubmissionState.success, mapOf(
    500                     "COMPAT_SUCCESS" to SubmissionState.success
    501                 )),
    502                 "COMPAT_FAILURE" to Pair(SubmissionState.pending, mapOf(
    503                     "COMPAT_FAILURE" to SubmissionState.permanent_failure
    504                 )),
    505                 "BATCH_SINGLE_SUCCESS" to Pair(SubmissionState.success, mapOf(
    506                     "FD622SMXKT5QWSAHDY0H8NYG3G" to SubmissionState.success
    507                 )),
    508                 "BATCH_SINGLE_FAILURE" to Pair(SubmissionState.pending, mapOf( // TODO success
    509                     "DAFC3NEE4T48WVC560T76ABA2C" to SubmissionState.pending, // TODO failure
    510                 )),
    511                 "BATCH_SINGLE_RETURN" to Pair(SubmissionState.success, mapOf(
    512                     "KLJJ28S1LVNDK1R2HCHLN884M7EKM5XGM5" to SubmissionState.late_failure,
    513                 )),
    514                 "BATCH_MANY_SUCCESS" to Pair(SubmissionState.success, mapOf(
    515                     "IVMIGCUIE7Q7VOF73R8GU3KGRYBZPAYC5V" to SubmissionState.success,
    516                     "CDFN7I4FVIZ848DGDQ35DZ2K49H9EWXGAW" to SubmissionState.success,
    517                     "35M1268GW5ZFHS5JCB41UKDQNPMD40T849" to SubmissionState.success,
    518                     "HPOMV7A4E3P1TK9UZJS1WTM94A9V3X2SR1" to SubmissionState.success,
    519                 )),
    520                 "BATCH_MANY_PART" to Pair(SubmissionState.success, mapOf(
    521                     "27SK3166EG36SJ7VP7VFYP0MW8" to SubmissionState.success,
    522                     "KGTDBASWTJ6JM89WXD3Q5KFQC4" to SubmissionState.permanent_failure,
    523                     "8XK8Z7RAX224FGWK832FD40GYC" to SubmissionState.permanent_failure,
    524                 )),
    525                 "BATCH_MANY_FAILURE" to Pair(SubmissionState.pending, mapOf( // TODO success
    526                     "4XTPKWE4A9V90PRQJCT8Z3MQZ8" to SubmissionState.pending, // TODO failure
    527                     "3VZZHVYJ6XP2SNPKWF4D4YVHNG" to SubmissionState.pending, // TODO failure
    528                 ))
    529             ),
    530             incoming = listOf(
    531                 IncomingPayment(
    532                     id = IncomingId(null, "BYLADEM1WOR-G2910276709458A2", "2024041210041357000"),
    533                     amount = TalerAmount("EUR:3"),
    534                     subject = "Taler FJDQ7W6G7NWX4H9M1MKA12090FRC9K7DA6N0FANDZZFXTR6QHX5G Test.,-",
    535                     executionTime = dateToInstant("2024-04-12"),
    536                     debtor = ibanPayto("DE84500105177118117964", "John Smith")
    537                 ),
    538             ),
    539             outgoing = listOf(
    540                 OutgoingPayment(
    541                     id = OutgoingId(null, "COMPAT_SUCCESS", "2024041801514102000"),
    542                     amount = TalerAmount("EUR:2"),
    543                     subject = "TestABC123",
    544                     executionTime = dateToInstant("2024-04-18"),
    545                     creditor = ibanPayto("DE20500105172419259181", "John Smith")
    546                 ),
    547                 OutgoingPayment(
    548                     id = OutgoingId(null, "FD622SMXKT5QWSAHDY0H8NYG3G", "2024090216552232000"),
    549                     amount = TalerAmount("EUR:1.1"),
    550                     subject = "single 2024-09-02T14:29:52.875253314Z",
    551                     executionTime = dateToInstant("2024-09-02"),
    552                     creditor = ibanPayto("DE89500105173198527518", "Grothoff Hans")
    553                 ),
    554                 OutgoingPayment(
    555                     id = OutgoingId(null, "YF5QBARGQ0MNY0VK59S477VDG4", "2024041810552821000"),
    556                     amount = TalerAmount("EUR:1.1"),
    557                     subject = "Simple tx",
    558                     executionTime = dateToInstant("2024-04-18"),
    559                     creditor = ibanPayto("DE20500105172419259181", "John Smith")
    560                 ),
    561                 OutgoingPayment(
    562                     id = OutgoingId(null, "IVMIGCUIE7Q7VOF73R8GU3KGRYBZPAYC5V", null),
    563                     amount = TalerAmount("EUR:44"),
    564                     subject = "init payment",
    565                     executionTime = dateToInstant("2024-09-20"),
    566                     creditor = ibanPayto("CH4189144589712575493", "Test")
    567                 ),
    568                 OutgoingPayment(
    569                     id = OutgoingId(null, "CDFN7I4FVIZ848DGDQ35DZ2K49H9EWXGAW", null),
    570                     amount = TalerAmount("EUR:44"),
    571                     subject = "init payment",
    572                     executionTime = dateToInstant("2024-09-20"),
    573                     creditor = ibanPayto("CH4189144589712575493", "Test")
    574                 ),
    575                 OutgoingPayment(
    576                     id = OutgoingId(null, "35M1268GW5ZFHS5JCB41UKDQNPMD40T849", null),
    577                     amount = TalerAmount("EUR:44"),
    578                     subject = "init payment",
    579                     executionTime = dateToInstant("2024-09-20"),
    580                     creditor = ibanPayto("CH4189144589712575493", "Test")
    581                 ),
    582                 OutgoingPayment(
    583                     id = OutgoingId(null, "HPOMV7A4E3P1TK9UZJS1WTM94A9V3X2SR1", null),
    584                     amount = TalerAmount("EUR:44"),
    585                     subject = "init payment",
    586                     executionTime = dateToInstant("2024-09-20"),
    587                     creditor = ibanPayto("CH4189144589712575493", "Test")
    588                 ),
    589                 OutgoingPayment(
    590                     id = OutgoingId(null, "KLJJ28S1LVNDK1R2HCHLN884M7EKM5XGM5", "2024092100252498000"),
    591                     amount = TalerAmount("EUR:0.42"),
    592                     subject = "This should fail because bad iban",
    593                     executionTime = dateToInstant("2024-09-23"),
    594                     creditor = ibanPayto("DE18500105173385245163", "John Smith")
    595                 ),
    596                 OutgoingPayment(
    597                     id = OutgoingId(null, "27SK3166EG36SJ7VP7VFYP0MW8", null),
    598                     amount = TalerAmount("EUR:44"),
    599                     subject = "init payment",
    600                     executionTime = dateToInstant("2024-09-04"),
    601                     creditor = ibanPayto("CH4189144589712575493", "Test")
    602                 ),
    603             ),
    604             bounced = emptyList()
    605         )
    606     }
    607 
    608     /** Maerki Baumann dialect test */
    609     @Test
    610     fun maerki_baumann() = setup("maerki_baumann.conf") { db, cfg ->
    611         db.batches(mapOf(
    612             "BATCH_SINGLE_REPORTING" to listOf(
    613                 genInitPay("5IBJZOWESQGPCSOXSNNBBY49ZURI5W7Q4H"),
    614                 genInitPay("XZ15UR0XU52QWI7Q4XB88EDS44PLH7DYXH"),
    615                 genInitPay("A09R35EW0359SZ51464E7TC37A0P2CBK04"),
    616                 genInitPay("UYXZ78LE9KAIMBY6UNXFYT1K8KNY8VLZLT"),
    617             ),
    618         ))
    619 
    620         // Register camt files
    621         db.register(cfg, "sample/platform/maerki_baumann_camt053.xml", OrderDoc.statement)
    622 
    623         // Check state
    624         db.check(
    625             status = mapOf(
    626                 "BATCH_SINGLE_REPORTING" to Pair(SubmissionState.success, mapOf(
    627                     "5IBJZOWESQGPCSOXSNNBBY49ZURI5W7Q4H" to SubmissionState.success,
    628                     "XZ15UR0XU52QWI7Q4XB88EDS44PLH7DYXH" to SubmissionState.success,
    629                     "A09R35EW0359SZ51464E7TC37A0P2CBK04" to SubmissionState.success,
    630                     "UYXZ78LE9KAIMBY6UNXFYT1K8KNY8VLZLT" to SubmissionState.success,
    631                 )),
    632             ),
    633             incoming = listOf(
    634                 IncomingPayment(
    635                     id = IncomingId("adbe4a5a-6cea-4263-b259-8ab964561a32", "41103099704.0002", "ZV20241104/765446/1"),
    636                     amount = TalerAmount("CHF:1"),
    637                     creditFee = TalerAmount("CHF:0.2"),
    638                     subject = "SFHP6H24C16A5J05Q3FJW2XN1PB3EK70ZPY 5SJ30ADGY68FWN68G",
    639                     executionTime = dateToInstant("2024-11-04"),
    640                     debtor = ibanPayto("CH7389144832588726658", "Mr Test")
    641                 ),
    642                 IncomingPayment(
    643                     id = IncomingId("7371795e-62fa-42dd-93b7-da89cc120faa", "41103099704.0003", "ZV20241104/765447/1"),
    644                     amount = TalerAmount("CHF:1"),
    645                     creditFee = TalerAmount("CHF:0.2"),
    646                     subject = "Random subject",
    647                     executionTime = dateToInstant("2024-11-04"),
    648                     debtor = ibanPayto("CH7389144832588726658", "Mr Test")
    649                 ),
    650                 IncomingPayment(
    651                     id = IncomingId(null, "50523424675.0001", "ZV20250523/851716/1"),
    652                     amount = TalerAmount("CHF:0.5"),
    653                     creditFee = TalerAmount("CHF:0.2"),
    654                     subject = null,
    655                     executionTime = dateToInstant("2025-05-23"),
    656                     debtor = ibanPayto("CH7389144832588726658", "Grothoff Hans")
    657                 ),
    658                 IncomingPayment(
    659                     id = IncomingId("f203fbb4-6e13-4c78-9b2a-d852fea6374a", "41202060702.0001", "ZV20241202/778108/1"),
    660                     amount = TalerAmount("CHF:0.05"),
    661                     creditFee = TalerAmount("CHF:0.2"),
    662                     subject = "mini",
    663                     executionTime = dateToInstant("2024-12-02"),
    664                     debtor = ibanPayto("CH7389144832588726658", "Grothoff Hans")
    665                 ),
    666                 IncomingPayment(
    667                     id = IncomingId("81b0d8c6-a677-4577-b75e-a639dcc03681", "41120636093.0001", "ZV20241121/773118/1"),
    668                     amount = TalerAmount("CHF:0.1"),
    669                     creditFee = TalerAmount("CHF:0.2"),
    670                     subject = "small transfer test",
    671                     executionTime = dateToInstant("2024-11-21"),
    672                     debtor = ibanPayto("CH7389144832588726658", "Grothoff Hans")
    673                 ),
    674                 IncomingPayment(
    675                     id = IncomingId(null, null, "ZV20250114/796191/1"),
    676                     amount = TalerAmount("CHF:3003"),
    677                     subject = "Fix bad payment by MB.",
    678                     executionTime = dateToInstant("2025-01-27"),
    679                     debtor = null
    680                 ),
    681                 IncomingPayment(
    682                     id = IncomingId(null, "F000787951230001", "ZV20250526/852733/1"),
    683                     amount = TalerAmount("CHF:1.38"),
    684                     creditFee = TalerAmount("CHF:0.2"),
    685                     subject = "Taler XT3D9MADR4V85JBWX47SMJFDQD2FDZDHHPH8R25YDG1KNVTSEH6G",
    686                     executionTime = dateToInstant("2025-05-26"),
    687                     debtor = ibanPayto("DE20500105172419259181", "Mr German")
    688                 ),
    689             ),
    690             outgoing = listOf(
    691                 OutgoingPayment(
    692                     id = OutgoingId(null, "5IBJZOWESQGPCSOXSNNBBY49ZURI5W7Q4H", "ZV20241121/773541/1"),
    693                     amount = TalerAmount("CHF:0.1"),
    694                     subject = "multi 0 2024-11-21T15:21:59.8859234 63Z",
    695                     executionTime = dateToInstant("2024-11-27"),
    696                     creditor = ibanPayto("CH7389144832588726658", "Grothoff Hans")
    697                 ),
    698                 OutgoingPayment(
    699                     id = OutgoingId(null, "XZ15UR0XU52QWI7Q4XB88EDS44PLH7DYXH", "ZV20241121/773541/4"),    
    700                     amount = TalerAmount("CHF:0.13"),
    701                     subject = "multi 3 2024-11-21T15:21:59.8859234 63Z",
    702                     executionTime = dateToInstant("2024-11-27"),
    703                     creditor = ibanPayto("CH7389144832588726658", "Grothoff Hans")
    704                 ),
    705                 OutgoingPayment(
    706                     id = OutgoingId(null, "A09R35EW0359SZ51464E7TC37A0P2CBK04", "ZV20241121/773541/3"),
    707                     amount = TalerAmount("CHF:0.12"),
    708                     subject = "multi 2 2024-11-21T15:21:59.8859234 63Z",
    709                     executionTime = dateToInstant("2024-11-27"),
    710                     creditor = ibanPayto("CH7389144832588726658", "Grothoff Hans")
    711                 ),
    712                 OutgoingPayment(
    713                     id = OutgoingId(null, "UYXZ78LE9KAIMBY6UNXFYT1K8KNY8VLZLT", "ZV20241121/773541/2"),
    714                     amount = TalerAmount("CHF:0.11"),
    715                     subject = "multi 1 2024-11-21T15:21:59.8859234 63Z",
    716                     executionTime = dateToInstant("2024-11-27"),
    717                     creditor = ibanPayto("CH7389144832588726658", "Grothoff Hans")
    718                 ),
    719                 OutgoingPayment(
    720                     id = OutgoingId(null, null, "GB20241220/205792/1"),
    721                     amount = TalerAmount("CHF:3000"),
    722                     subject = null,
    723                     executionTime = dateToInstant("2024-12-20"),
    724                     creditor = null
    725                 ),
    726             ),
    727             bounced = listOf(
    728                 OutgoingPayment(
    729                     id = OutgoingId(null, null, null),
    730                     amount = TalerAmount("CHF:1"),
    731                     subject = "bounce 7371795e-62fa-42dd-93b7-da89cc120faa: missing reserve public key",
    732                     executionTime = Instant.EPOCH,
    733                     creditor = ibanPayto("CH7389144832588726658", "Mr Test")
    734                 ),
    735                 OutgoingPayment(
    736                     id = OutgoingId(null, null, null),
    737                     amount = TalerAmount("CHF:0.5"),
    738                     subject = "bounce 50523424675.0001: missing subject",
    739                     executionTime = Instant.EPOCH,
    740                     creditor = ibanPayto("CH7389144832588726658", "Grothoff Hans")
    741                 ),
    742                 OutgoingPayment(
    743                     id = OutgoingId(null, null, null),
    744                     amount = TalerAmount("CHF:0.05"),
    745                     subject = "bounce f203fbb4-6e13-4c78-9b2a-d852fea6374a: missing reserve public key",
    746                     executionTime = Instant.EPOCH,
    747                     creditor = ibanPayto("CH7389144832588726658", "Grothoff Hans")
    748                 ),
    749                 OutgoingPayment(
    750                     id = OutgoingId(null, null, null),
    751                     amount = TalerAmount("CHF:0.1"),
    752                     subject = "bounce 81b0d8c6-a677-4577-b75e-a639dcc03681: missing reserve public key",
    753                     executionTime = Instant.EPOCH,
    754                     creditor = ibanPayto("CH7389144832588726658", "Grothoff Hans")
    755                 ),
    756                 OutgoingPayment(
    757                     id = OutgoingId(null, null, null),
    758                     amount = TalerAmount("CHF:1.38"),
    759                     subject = "bounce F000787951230001: restricted account",
    760                     executionTime = Instant.EPOCH,
    761                     creditor = ibanPayto("DE20500105172419259181", "Mr German")
    762                 )
    763             )
    764         )
    765     }
    766 }