diff options
author | MS <ms@taler.net> | 2023-03-31 14:08:04 +0200 |
---|---|---|
committer | MS <ms@taler.net> | 2023-03-31 14:08:04 +0200 |
commit | 84a5889dd8b49510f1b76fa68211070667d4d177 (patch) | |
tree | 2bb9a485be9ce13395f540ff27a487af3e67d832 | |
parent | 9fa7dbec7ada36d55893a80db8bd1eb71e72d10a (diff) | |
download | libeufin-84a5889dd8b49510f1b76fa68211070667d4d177.tar.gz libeufin-84a5889dd8b49510f1b76fa68211070667d4d177.tar.bz2 libeufin-84a5889dd8b49510f1b76fa68211070667d4d177.zip |
tests
-rwxr-xr-x | cli/tests/launch_services.sh | 13 | ||||
-rw-r--r-- | nexus/src/test/kotlin/DownloadAndSubmit.kt | 8 | ||||
-rw-r--r-- | nexus/src/test/kotlin/MakeEnv.kt | 140 | ||||
-rw-r--r-- | nexus/src/test/kotlin/NexusApiTest.kt | 4 | ||||
-rw-r--r-- | nexus/src/test/kotlin/SandboxCircuitApiTest.kt | 26 | ||||
-rw-r--r-- | nexus/src/test/kotlin/TalerTest.kt | 106 | ||||
-rw-r--r-- | nexus/src/test/kotlin/XLibeufinBankTest.kt | 111 |
7 files changed, 346 insertions, 62 deletions
diff --git a/cli/tests/launch_services.sh b/cli/tests/launch_services.sh index b3fcc4eb..2bee7df7 100755 --- a/cli/tests/launch_services.sh +++ b/cli/tests/launch_services.sh @@ -4,8 +4,8 @@ # EBICS pair, in order to try CLI commands. set -eu -WITH_TASKS=1 -# WITH_TASKS=0 +# WITH_TASKS=1 +WITH_TASKS=0 function exit_cleanup() { echo "Running exit-cleanup" @@ -25,13 +25,13 @@ curl --version &> /dev/null || (echo "'curl' command not found"; exit 77) SQLITE_FILE_PATH=/tmp/libeufin-cli-test.sqlite3 getDbConn () { if test withPostgres == "${1:-}"; then - echo "jdbc:postgresql://localhost:5432/taler?user=$(whoami)" + echo "jdbc:postgresql://localhost:5432/libeufincheck?user=$(whoami)" return fi echo "jdbc:sqlite:${SQLITE_FILE_PATH}" } -DB_CONN=`getDbConn` +DB_CONN=`getDbConn withPostgres` export LIBEUFIN_SANDBOX_DB_CONNECTION=$DB_CONN export LIBEUFIN_NEXUS_DB_CONNECTION=$DB_CONN @@ -139,6 +139,9 @@ if test 1 = $WITH_TASKS; then www-nexus || true echo OK else - echo NOT creating backound tasks! + echo NOT creating background tasks! fi +echo "Requesting Taler history with 90 seconds timeout..." +curl -u test-user:x "http://localhost:5001/facades/test-facade/taler-wire-gateway/history/incoming?delta=5&long_poll_ms=90000" + read -p "Press Enter to terminate..." diff --git a/nexus/src/test/kotlin/DownloadAndSubmit.kt b/nexus/src/test/kotlin/DownloadAndSubmit.kt index 0ac5b0c7..622ff928 100644 --- a/nexus/src/test/kotlin/DownloadAndSubmit.kt +++ b/nexus/src/test/kotlin/DownloadAndSubmit.kt @@ -114,9 +114,9 @@ class DownloadAndSubmit { client, fetchSpec = FetchSpecAllJson( level = FetchLevel.REPORT, - "foo" + bankConnection = "foo" ), - "foo" + accountId = "foo" ) } transaction { @@ -223,7 +223,7 @@ class DownloadAndSubmit { } /** - * Submit one payment instruction with a invalid Pain.001 + * Submit one payment instruction with an invalid Pain.001 * document, and check that it was marked as invalid. Hence, * the error is expected only by the first submission, since * the second won't pick the invalid payment. @@ -238,7 +238,7 @@ class DownloadAndSubmit { addPaymentInitiation( Pain001Data( creditorIban = getIban(), - creditorBic = "not-a-BIC", + creditorBic = "not-a-BIC", // this value causes the expected error. creditorName = "Tester", subject = "test payment", sum = "1", diff --git a/nexus/src/test/kotlin/MakeEnv.kt b/nexus/src/test/kotlin/MakeEnv.kt index 9f8f5249..596b2c95 100644 --- a/nexus/src/test/kotlin/MakeEnv.kt +++ b/nexus/src/test/kotlin/MakeEnv.kt @@ -3,19 +3,16 @@ import org.jetbrains.exposed.sql.Database import org.jetbrains.exposed.sql.statements.api.ExposedBlob import org.jetbrains.exposed.sql.transactions.TransactionManager import org.jetbrains.exposed.sql.transactions.transaction -import org.jetbrains.exposed.sql.transactions.transactionManager import tech.libeufin.nexus.* import tech.libeufin.nexus.dbCreateTables import tech.libeufin.nexus.dbDropTables import tech.libeufin.nexus.iso20022.* +import tech.libeufin.nexus.server.BankConnectionType import tech.libeufin.nexus.server.CurrencyAmount import tech.libeufin.nexus.server.FetchLevel import tech.libeufin.nexus.server.FetchSpecAllJson import tech.libeufin.sandbox.* -import tech.libeufin.util.CryptoUtil -import tech.libeufin.util.EbicsInitState -import java.io.File -import tech.libeufin.util.getIban +import tech.libeufin.util.* data class EbicsKeys( val auth: CryptoUtil.RsaCrtKeyPair, @@ -40,6 +37,15 @@ val userKeys = EbicsKeys( sig = CryptoUtil.generateRsaKeyPair(2048) ) +fun assertWithPrint(cond: Boolean, msg: String) { + try { + assert(cond) + } catch (e: AssertionError) { + System.err.println(msg) + throw e + } +} + // New versions of JUnit provide this! inline fun <reified ExceptionType> assertException( block: () -> Unit, @@ -85,11 +91,28 @@ fun prepNexusDb() { passwordHash = CryptoUtil.hashpw("foo") superuser = true } + val b = NexusUserEntity.new { + username = "bar" + passwordHash = CryptoUtil.hashpw("bar") + superuser = true + } val c = NexusBankConnectionEntity.new { + connectionId = "bar" + owner = b + type = "x-libeufin-bank" + } + val d = NexusBankConnectionEntity.new { connectionId = "foo" - owner = u + owner = b type = "ebics" } + XLibeufinBankUserEntity.new { + username = "bar" + password = "bar" + // Only addressing mild cases where ONE slash ends the base URL. + baseUrl = "http://localhost/demobanks/default/access-api" + nexusBankConnection = c + } tech.libeufin.nexus.EbicsSubscriberEntity.new { // ebicsURL = "http://localhost:5000/ebicsweb" ebicsURL = "http://localhost/ebicsweb" @@ -100,7 +123,7 @@ fun prepNexusDb() { signaturePrivateKey = ExposedBlob(userKeys.sig.private.encoded) encryptionPrivateKey = ExposedBlob(userKeys.enc.private.encoded) authenticationPrivateKey = ExposedBlob(userKeys.auth.private.encoded) - nexusBankConnection = c + nexusBankConnection = d ebicsIniState = EbicsInitState.NOT_SENT ebicsHiaState = EbicsInitState.NOT_SENT bankEncryptionPublicKey = ExposedBlob(bankKeys.enc.public.encoded) @@ -110,7 +133,7 @@ fun prepNexusDb() { bankAccountName = "foo" iban = FOO_USER_IBAN bankCode = "SANDBOXX" - defaultBankConnection = c + defaultBankConnection = d highestSeenBankMessageSerialId = 0 accountHolder = "foo" } @@ -140,7 +163,7 @@ fun prepNexusDb() { } // Giving 'foo' a Taler facade. val f = FacadeEntity.new { - facadeName = "taler" + facadeName = "foo-facade" type = "taler-wire-gateway" creator = u } @@ -152,6 +175,20 @@ fun prepNexusDb() { facade = f highestSeenMessageSerialId = 0 } + // Giving 'bar' a Taler facade + val g = FacadeEntity.new { + facadeName = "bar-facade" + type = "taler-wire-gateway" + creator = b + } + FacadeStateEntity.new { + bankAccount = "bar" + bankConnection = "bar" // uses x-libeufin-bank connection. + currency = "TESTKUDOS" + reserveTransferLevel = "report" + facade = g + highestSeenMessageSerialId = 0 + } } } @@ -287,35 +324,82 @@ fun withSandboxTestDatabase(f: () -> Unit) { } } -fun newNexusBankTransaction(currency: String, value: String, subject: String) { +fun newNexusBankTransaction( + currency: String, + value: String, + subject: String, + creditorAcct: String = "foo", + connType: BankConnectionType = BankConnectionType.EBICS +) { + val jDetails: String = when(connType) { + BankConnectionType.EBICS -> { + jacksonObjectMapper( + ).writerWithDefaultPrettyPrinter( + ).writeValueAsString( + genNexusIncomingCamt( + amount = CurrencyAmount(currency,value), + subject = subject + ) + ) + } + /** + * Note: x-libeufin-bank ALSO stores the transactions in the + * CaMt representation, hence this branch should be removed. + */ + BankConnectionType.X_LIBEUFIN_BANK -> { + jacksonObjectMapper( + ).writerWithDefaultPrettyPrinter( + ).writeValueAsString(genNexusIncomingCamt( + amount = CurrencyAmount(currency, value), + subject = subject + )) + } + else -> throw Exception("Unsupported connection type: ${connType.typeName}") + } transaction { NexusBankTransactionEntity.new { - bankAccount = NexusBankAccountEntity.findByName("foo")!! + bankAccount = NexusBankAccountEntity.findByName(creditorAcct)!! accountTransactionId = "mock" creditDebitIndicator = "CRDT" this.currency = currency this.amount = value status = EntryStatus.BOOK - transactionJson = jacksonObjectMapper( - ).writerWithDefaultPrettyPrinter( - ).writeValueAsString( - genNexusIncomingPayment( - amount = CurrencyAmount(currency,value), - subject = subject - ) - ) + transactionJson = jDetails } - /*TalerIncomingPaymentEntity.new { - payment = inc - reservePublicKey = "mock" - timestampMs = 0L - debtorPaytoUri = "mock" - }*/ } } - -fun genNexusIncomingPayment( +/** + * This function generates the Nexus JSON model of one transaction + * as if it got downloaded via one x-libeufin-bank connection. The + * non given values are either resorted from other sources by Nexus, + * or actually not useful so far. + */ +private fun genNexusIncomingXLibeufinBank( + amount: CurrencyAmount, + subject: String +): XLibeufinBankTransaction = + XLibeufinBankTransaction( + creditorIban = "NOTUSED", + creditorBic = null, + creditorName = "Not Used", + debtorIban = "NOTUSED", + debtorBic = null, + debtorName = "Not Used", + amount = amount.value, + currency = amount.currency, + subject = subject, + date = "0", + uid = "not-used", + direction = XLibeufinBankDirection.CREDIT + ) +/** + * This function generates the Nexus JSON model of one transaction + * as if it got downloaded via one Ebics connection. The non given + * values are either resorted from other sources by Nexus, or actually + * not useful so far. + */ +private fun genNexusIncomingCamt( amount: CurrencyAmount, subject: String, ): CamtBankAccountEntry = @@ -382,4 +466,4 @@ fun genNexusIncomingPayment( ) ) ) - )
\ No newline at end of file + ) diff --git a/nexus/src/test/kotlin/NexusApiTest.kt b/nexus/src/test/kotlin/NexusApiTest.kt index 30763005..e4fcc6d0 100644 --- a/nexus/src/test/kotlin/NexusApiTest.kt +++ b/nexus/src/test/kotlin/NexusApiTest.kt @@ -4,13 +4,13 @@ import io.ktor.http.* import io.ktor.server.testing.* import org.junit.Test import tech.libeufin.nexus.server.nexusApp +import tech.libeufin.sandbox.sandboxApp /** * This class tests the API offered by Nexus, * documented here: https://docs.taler.net/libeufin/api-nexus.html */ class NexusApiTest { - // Testing basic operations on facades. @Test fun facades() { @@ -19,7 +19,7 @@ class NexusApiTest { prepNexusDb() testApplication { application(nexusApp) - client.delete("/facades/taler") { + client.delete("/facades/foo-facade") { basicAuth("foo", "foo") expectSuccess = true } diff --git a/nexus/src/test/kotlin/SandboxCircuitApiTest.kt b/nexus/src/test/kotlin/SandboxCircuitApiTest.kt index 8979fef9..d9ff3d51 100644 --- a/nexus/src/test/kotlin/SandboxCircuitApiTest.kt +++ b/nexus/src/test/kotlin/SandboxCircuitApiTest.kt @@ -38,18 +38,40 @@ class SandboxCircuitApiTest { prepSandboxDb() testApplication { application(sandboxApp) - val R = client.get( + var R = client.get( "/demobanks/default/circuit-api/cashouts/estimates?amount_debit=TESTKUDOS:2" ) { expectSuccess = true basicAuth("foo", "foo") } val mapper = ObjectMapper() - val respJson = mapper.readTree(R.bodyAsText()) + var respJson = mapper.readTree(R.bodyAsText()) val creditAmount = respJson.get("amount_credit").asText() // sell ratio and fee are the following constants: 0.95 and 0. // expected credit amount = 2 * 0.95 - 0 = 1.90 assert("CHF:1.90" == creditAmount || "CHF:1.9" == creditAmount) + R = client.get( + "/demobanks/default/circuit-api/cashouts/estimates?amount_credit=CHF:1.9" + ) { + expectSuccess = true + basicAuth("foo", "foo") + } + respJson = mapper.readTree(R.bodyAsText()) + val debitAmount = respJson.get("amount_debit").asText() + assertWithPrint( + "TESTKUDOS:2" == debitAmount || "TESTKUDOS:2.0" == debitAmount, + "'debit_amount' was $debitAmount for a 'credit_amount' of CHF:1.9" + ) + R = client.get( + "/demobanks/default/circuit-api/cashouts/estimates?amount_credit=CHF:1&amount_debit=TESTKUDOS=1" + ) { + expectSuccess = false + basicAuth("foo", "foo") + } + assertWithPrint( + R.status.value == HttpStatusCode.BadRequest.value, + "Expected status code was 400, but got '${R.status.value}' instead." + ) } } } diff --git a/nexus/src/test/kotlin/TalerTest.kt b/nexus/src/test/kotlin/TalerTest.kt index c433284a..f877203b 100644 --- a/nexus/src/test/kotlin/TalerTest.kt +++ b/nexus/src/test/kotlin/TalerTest.kt @@ -30,7 +30,7 @@ class TalerTest { withNexusAndSandboxUser { testApplication { application(nexusApp) - client.post("/facades/taler/taler-wire-gateway/transfer") { + client.post("/facades/foo-facade/taler-wire-gateway/transfer") { contentType(ContentType.Application.Json) basicAuth("foo", "foo") // exchange's credentials expectSuccess = true @@ -73,7 +73,7 @@ class TalerTest { */ testApplication { application(nexusApp) - val r = client.get("/facades/taler/taler-wire-gateway/history/outgoing?delta=5") { + val r = client.get("/facades/foo-facade/taler-wire-gateway/history/outgoing?delta=5") { expectSuccess = true contentType(ContentType.Application.Json) basicAuth("foo", "foo") @@ -85,38 +85,102 @@ class TalerTest { } } - // Checking that a correct wire transfer (with Taler-compatible subject) - // is responded by the Taler facade. + // Tests that incoming Taler txs arrive via EBICS. @Test - fun historyIncomingTest() { + fun historyIncomingTestEbics() { + historyIncomingTest( + testedAccount = "foo", + connType = BankConnectionType.EBICS + ) + } + + // Tests that incoming Taler txs arrive via x-libeufin-bank. + @Test + fun historyIncomingTestXLibeufinBank() { + historyIncomingTest( + testedAccount = "bar", + connType = BankConnectionType.X_LIBEUFIN_BANK + ) + } + + // Tests that even if one call is long-polling, other calls + @Test + fun servingTest() { + withTestDatabase { + prepNexusDb() + testApplication { + application(nexusApp) + // This call blocks for 90 seconds + val currentTime = System.currentTimeMillis() + runBlocking { + launch { + val r = client.get("/facades/foo-facade/taler-wire-gateway/history/incoming?delta=5&start=0&long_poll_ms=5000") { + expectSuccess = true + contentType(ContentType.Application.Json) + basicAuth("foo", "foo") // user & pw always equal. + } + assert(r.status.value == HttpStatusCode.NoContent.value) + } + val R = client.get("/") { + expectSuccess = true + } + val latestTime = System.currentTimeMillis() + assert(R.status.value == HttpStatusCode.OK.value + && (latestTime - currentTime) < 2000 + ) + } + } + } + } + + // Downloads Taler txs via the default connection of 'testedAccount'. + // This allows to test the Taler logic on different connection types. + fun historyIncomingTest(testedAccount: String, connType: BankConnectionType) { val reservePub = "GX5H5RME193FDRCM1HZKERXXQ2K21KH7788CKQM8X6MYKYRBP8F0" withNexusAndSandboxUser { testApplication { application(nexusApp) runBlocking { + /** + * This block issues the request by long-polling and + * lets the execution proceed where the actions to unblock + * the polling are taken. + */ launch { - val r = client.get("/facades/taler/taler-wire-gateway/history/incoming?delta=5&start=0&long_poll_ms=3000") { - expectSuccess = false + val r = client.get("/facades/${testedAccount}-facade/taler-wire-gateway/history/incoming?delta=5&start=0&long_poll_ms=30000") { + expectSuccess = true contentType(ContentType.Application.Json) - basicAuth("foo", "foo") + basicAuth(testedAccount, testedAccount) // user & pw always equal. } - println("maybe response body: ${r.bodyAsText()}") - assert(r.status.value == HttpStatusCode.OK.value) + assertWithPrint( + r.status.value == HttpStatusCode.OK.value, + "Long-polling history had status: ${r.status.value} and" + + " body: ${r.bodyAsText()}" + ) val j = mapper.readTree(r.readBytes()) val reservePubFromTwg = j.get("incoming_transactions").get(0).get("reserve_pub").asText() assert(reservePubFromTwg == reservePub) } - newNexusBankTransaction( - "KUDOS", - "10", - reservePub - ) - ingestFacadeTransactions( - "foo", // bank account local to Nexus. - "taler-wire-gateway", - ::talerFilter, - ::maybeTalerRefunds - ) + launch { + delay(500) + /** + * FIXME: this test never gets the server to wait notifications from the DBMS. + * Somehow, the wire transfer arrives always before the blocking await on the DBMS. + */ + newNexusBankTransaction( + currency = "KUDOS", + value = "10", + subject = reservePub, + creditorAcct = testedAccount, + connType = connType + ) + ingestFacadeTransactions( + bankAccountId = testedAccount, // bank account local to Nexus. + facadeType = NexusFacadeType.TALER, + incomingFilterCb = ::talerFilter, + refundCb = ::maybeTalerRefunds + ) + } } } } diff --git a/nexus/src/test/kotlin/XLibeufinBankTest.kt b/nexus/src/test/kotlin/XLibeufinBankTest.kt new file mode 100644 index 00000000..9af9133c --- /dev/null +++ b/nexus/src/test/kotlin/XLibeufinBankTest.kt @@ -0,0 +1,111 @@ +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper +import io.ktor.server.testing.* +import org.jetbrains.exposed.sql.transactions.transaction +import org.junit.Test +import tech.libeufin.nexus.BankConnectionProtocol +import tech.libeufin.nexus.NexusBankTransactionEntity +import tech.libeufin.nexus.NexusBankTransactionsTable +import tech.libeufin.nexus.bankaccount.ingestBankMessagesIntoAccount +import tech.libeufin.nexus.getNexusUser +import tech.libeufin.nexus.iso20022.CamtBankAccountEntry +import tech.libeufin.nexus.server.* +import tech.libeufin.nexus.xlibeufinbank.XlibeufinBankConnectionProtocol +import tech.libeufin.sandbox.sandboxApp +import tech.libeufin.sandbox.wireTransfer +import tech.libeufin.util.XLibeufinBankTransaction +import java.net.URL + +// Testing the x-libeufin-bank communication + +class XLibeufinBankTest { + private val mapper = jacksonObjectMapper() + @Test + fun urlParse() { + val u = URL("http://localhost") + println(u.authority) + } + + /** + * This test tries to submit a transaction to Sandbox + * via the x-libeufin-bank connection and later - after + * having downloaded its transactions - tries to reconcile + * it as sent. + */ + @Test + fun submitTransaction() { + + } + + /** + * Testing that Nexus downloads one transaction from + * Sandbox via the x-libeufin-bank protocol supplier + * and stores it in the Nexus internal transactions + * table. + * + * NOTE: the test should be extended by checking that + * downloading twice the transaction doesn't lead to asset + * duplication locally in Nexus. + */ + @Test + fun fetchTransaction() { + withTestDatabase { + prepSandboxDb() + prepNexusDb() + testApplication { + // Creating the Sandbox transaction that's expected to be ingested. + wireTransfer( + debitAccount = "bar", + creditAccount = "foo", + demobank = "default", + subject = "x-libeufin-bank test transaction", + amount = "TESTKUDOS:333" + ) + val fooUser = getNexusUser("foo") + // Creating the x-libeufin-bank connection to interact with Sandbox. + val conn = XlibeufinBankConnectionProtocol() + val jDetails = """{ + "username": "foo", + "password": "foo", + "baseUrl": "http://localhost/demobanks/default/access-api" + }""".trimIndent() + conn.createConnection( + connId = "x", + user = fooUser, + data = mapper.readTree(jDetails) + ) + // Starting _Sandbox_ to check how it reacts to Nexus request. + application(sandboxApp) + /** + * Doing two rounds of download: the first is expected to + * record the payment as new, and the second is expected to + * ignore it because it has already it in the database. + */ + repeat(2) { + // Invoke transaction fetcher offered by the x-libeufin-bank connection. + conn.fetchTransactions( + fetchSpec = FetchSpecAllJson( + FetchLevel.STATEMENT, + null + ), + accountId = "foo", + bankConnectionId = "x", + client = client + ) + } + // The messages are in the database now, invoke the + // ingestion routine to parse them into the Nexus internal + // format. + ingestBankMessagesIntoAccount("x", "foo") + // Asserting that the payment made it to the database in the Nexus format. + transaction { + val maybeTx = NexusBankTransactionEntity.all() + // This assertion checks that the payment is not doubled in the database: + assert(maybeTx.count() == 1L) + val tx = maybeTx.first().parseDetailsIntoObject<CamtBankAccountEntry>() + assert(tx.getSingletonSubject() == "x-libeufin-bank test transaction") + } + } + } + } +}
\ No newline at end of file |