libeufin

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

commit 0b14b8f09c2009138dd6dbe89e3174ab949afa3c
parent 79ab57d29ef4b9879d89d3f068a929ac231751a7
Author: MS <ms@taler.net>
Date:   Wed, 23 Nov 2022 20:00:20 +0100

Nexus tests.

Implement test server to respond arbitrary
1-segment-long data along EBICS init and transfer
phases.

Diffstat:
Anexus/src/test/kotlin/MakeEnv.kt | 172+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Anexus/src/test/kotlin/SchedulingTest.kt | 138+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Anexus/src/test/resources/logback-test.xml | 28++++++++++++++++++++++++++++
3 files changed, 338 insertions(+), 0 deletions(-)

diff --git a/nexus/src/test/kotlin/MakeEnv.kt b/nexus/src/test/kotlin/MakeEnv.kt @@ -0,0 +1,171 @@ +import org.jetbrains.exposed.sql.Database +import org.jetbrains.exposed.sql.statements.api.ExposedBlob +import org.jetbrains.exposed.sql.transactions.transaction +import tech.libeufin.nexus.* +import tech.libeufin.nexus.dbCreateTables +import tech.libeufin.nexus.dbDropTables +import tech.libeufin.sandbox.* +import tech.libeufin.util.CryptoUtil +import tech.libeufin.util.EbicsInitState +import java.io.File +import tech.libeufin.util.getIban + +data class EbicsKeys( + val auth: CryptoUtil.RsaCrtKeyPair, + val enc: CryptoUtil.RsaCrtKeyPair, + val sig: CryptoUtil.RsaCrtKeyPair +) +const val TEST_DB_FILE = "/tmp/nexus-test.sqlite3" +const val TEST_DB_CONN = "jdbc:sqlite:$TEST_DB_FILE" +val BANK_IBAN = getIban() +val USER_IBAN = getIban() + +val bankKeys = EbicsKeys( + auth = CryptoUtil.generateRsaKeyPair(2048), + enc = CryptoUtil.generateRsaKeyPair(2048), + sig = CryptoUtil.generateRsaKeyPair(2048) +) +val userKeys = EbicsKeys( + auth = CryptoUtil.generateRsaKeyPair(2048), + enc = CryptoUtil.generateRsaKeyPair(2048), + sig = CryptoUtil.generateRsaKeyPair(2048) +) +/** + * Run a block after connecting to the test database. + * Cleans up the DB file afterwards. + */ +fun withTestDatabase(f: () -> Unit) { + val dbfile = TEST_DB_CONN + File(dbfile).also { + if (it.exists()) { + it.delete() + } + } + Database.connect("jdbc:sqlite:$dbfile") + dbDropTables(dbfile) + tech.libeufin.sandbox.dbDropTables(TEST_DB_CONN) + try { + f() + } + finally { + File(dbfile).also { + if (it.exists()) { + it.delete() + } + } + } +} + +fun prepNexusDb() { + dbCreateTables(TEST_DB_CONN) + transaction { + val u = NexusUserEntity.new { + username = "foo" + passwordHash = "foo" + superuser = false + } + val c = NexusBankConnectionEntity.new { + connectionId = "foo" + owner = u + type = "ebics" + } + tech.libeufin.nexus.EbicsSubscriberEntity.new { + ebicsURL = "http://localhost:5000/ebicsweb" + hostID = "eufinSandbox" + partnerID = "foo" + userID = "foo" + systemID = "foo" + signaturePrivateKey = ExposedBlob(userKeys.sig.private.encoded) + encryptionPrivateKey = ExposedBlob(userKeys.enc.private.encoded) + authenticationPrivateKey = ExposedBlob(userKeys.auth.private.encoded) + nexusBankConnection = c + ebicsIniState = EbicsInitState.NOT_SENT + ebicsHiaState = EbicsInitState.NOT_SENT + bankEncryptionPublicKey = ExposedBlob(bankKeys.enc.public.encoded) + bankAuthenticationPublicKey = ExposedBlob(bankKeys.auth.public.encoded) + } + val a = NexusBankAccountEntity.new { + bankAccountName = "mock-bank-account" + iban = USER_IBAN + bankCode = "SANDBOXX" + defaultBankConnection = c + highestSeenBankMessageSerialId = 0 + accountHolder = "foo" + } + } +} + +fun prepSandboxDb() { + tech.libeufin.sandbox.dbCreateTables(TEST_DB_CONN) + transaction { + val demoBank = DemobankConfigEntity.new { + currency = "TESTKUDOS" + bankDebtLimit = 10000 + usersDebtLimit = 1000 + allowRegistrations = true + name = "default" + this.withSignupBonus = false + captchaUrl = "http://example.com/" // unused + } + BankAccountEntity.new { + iban = BANK_IBAN + label = "bank" // used by the wire helper + owner = "bank" // used by the person name finder + // For now, the model assumes always one demobank + this.demoBank = demoBank + } + EbicsHostEntity.new { + this.ebicsVersion = "3.0" + this.hostId = "eufinSandbox" + this.authenticationPrivateKey = ExposedBlob(bankKeys.auth.private.encoded) + this.encryptionPrivateKey = ExposedBlob(bankKeys.enc.private.encoded) + this.signaturePrivateKey = ExposedBlob(bankKeys.sig.private.encoded) + } + val bankAccount = BankAccountEntity.new { + iban = USER_IBAN + /** + * For now, keep same semantics of Pybank: a username + * is AS WELL a bank account label. In other words, it + * identifies a customer AND a bank account. + */ + label = "foo" + owner = "foo" + this.demoBank = demoBank + isPublic = false + } + tech.libeufin.sandbox.EbicsSubscriberEntity.new { + hostId = "eufinSandbox" + partnerId = "foo" + userId = "foo" + systemId = "foo" + signatureKey = EbicsSubscriberPublicKeyEntity.new { + rsaPublicKey = ExposedBlob(userKeys.sig.public.encoded) + state = KeyState.RELEASED + } + encryptionKey = EbicsSubscriberPublicKeyEntity.new { + rsaPublicKey = ExposedBlob(userKeys.enc.public.encoded) + state = KeyState.RELEASED + } + authenticationKey = EbicsSubscriberPublicKeyEntity.new { + rsaPublicKey = ExposedBlob(userKeys.auth.public.encoded) + state = KeyState.RELEASED + } + state = SubscriberState.INITIALIZED + nextOrderID = 1 + this.bankAccount = bankAccount + } + DemobankCustomerEntity.new { + username = "foo" + passwordHash = "foo" + name = "Foo" + } + } +} + +fun withNexusAndSandboxUser(f: () -> Unit) { + withTestDatabase { + prepNexusDb() + prepSandboxDb() + f() + } +} +\ No newline at end of file diff --git a/nexus/src/test/kotlin/SchedulingTest.kt b/nexus/src/test/kotlin/SchedulingTest.kt @@ -0,0 +1,137 @@ +import io.ktor.application.* +import io.ktor.features.* +import io.ktor.http.* +import io.ktor.request.* +import io.ktor.response.* +import io.ktor.routing.* +import io.ktor.server.testing.* +import kotlinx.coroutines.runBlocking +import org.junit.Ignore +import org.junit.Test +import org.w3c.dom.Document +import tech.libeufin.nexus.* +import tech.libeufin.sandbox.* +import tech.libeufin.util.* +import tech.libeufin.util.ebics_h004.EbicsRequest +import tech.libeufin.util.ebics_h004.EbicsResponse +import tech.libeufin.util.ebics_h004.EbicsTypes + + +/** + * Data to make the test server return for EBICS + * phases. Currently only init is supported. + */ +data class EbicsResponses( + val init: String, + val download: String? = null, + val receipt: String? = null +) + +/** + * Minimal server responding always the 'init' field of a EbicsResponses + * object along a download EBICS message. Suitable to set arbitrary data + * in said response. Signs the response assuming the client is the one + * created a MakeEnv.kt. + */ +fun getCustomEbicsServer(r: EbicsResponses, endpoint: String = "/ebicsweb"): Application.() -> Unit { + val ret: Application.() -> Unit = { + install(ContentNegotiation) { + register(ContentType.Text.Xml, XMLEbicsConverter()) + register(ContentType.Text.Plain, XMLEbicsConverter()) + } + routing { + post(endpoint) { + val requestDocument = this.call.receive<Document>() + val req = requestDocument.toObject<EbicsRequest>() + val clientKey = CryptoUtil.loadRsaPublicKey(userKeys.enc.public.encoded) + val msgId = EbicsOrderUtil.generateTransactionId() + val resp: EbicsResponse = if ( + req.header.mutable.transactionPhase == EbicsTypes.TransactionPhaseType.INITIALISATION + ) { + val payload = prepareEbicsPayload(r.init, clientKey) + EbicsResponse.createForDownloadInitializationPhase( + msgId, + 1, + 4096, + payload.second, // for key material + payload.first // actual payload + ) + } else { + // msgId doesn't have to match the one used for the init phase. + EbicsResponse.createForDownloadReceiptPhase(msgId, true) + } + val sigEbics = XMLUtil.signEbicsResponse( + resp, + CryptoUtil.loadRsaPrivateKey(bankKeys.auth.private.encoded) + ) + call.respond(sigEbics) + } + } + } + return ret +} + +@Ignore +class SchedulingTest { + /** + * Instruct the server to return invalid CAMT content. + */ + @Test + fun inject() { + withNexusAndSandboxUser { + val payload = """ + Invalid Camt Document + """.trimIndent() + withTestApplication( + getCustomEbicsServer(EbicsResponses(payload)) + ) { + runBlocking { + runTask( + client, + TaskSchedule( + 0L, + "test-schedule", + "fetch", + "bank-account", + "mock-bank-account", + params = "{\"level\":\"report\",\"rangeType\":\"all\"}" + ) + ) + } + } + } + } + /** + * Create two payments and asks for C52. + */ + @Test + fun ordinary() { + withNexusAndSandboxUser { // DB prep + for (t in 1 .. 2) { + wireTransfer( + "bank", + "foo", + "default", + "1HJX78AH7WAGBDJTCXJ4JKX022DBCHERA051KH7D3EC48X09G4RG", + "TESTKUDOS:5", + "xxx" + ) + } + withTestApplication(sandboxApp) { + runBlocking { + runTask( + client, + TaskSchedule( + 0L, + "test-schedule", + "fetch", + "bank-account", + "mock-bank-account", + params = "{\"level\":\"report\",\"rangeType\":\"all\"}" + ) + ) + } + } + } + } +} +\ No newline at end of file diff --git a/nexus/src/test/resources/logback-test.xml b/nexus/src/test/resources/logback-test.xml @@ -0,0 +1,28 @@ +<!-- configuration scan="true" --> +<configuration> + <appender name="STDERR" class="ch.qos.logback.core.ConsoleAppender"> + <target>System.err</target> + <encoder> + <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern> + </encoder> + </appender> + + <logger name="tech.libeufin.nexus" level="ALL" additivity="false"> + <appender-ref ref="STDERR" /> + </logger> + + <logger name="tech.libeufin.sandbox" level="ALL" additivity="false"> + <appender-ref ref="STDERR" /> + </logger> + + <logger name="io.netty" level="WARN"/> + <logger name="ktor" level="WARN"/> + <logger name="Exposed" level="WARN"/> + <logger name="tech.libeufin.util" level="DEBUG"/> + <logger name="ch.qos" level="WARN"/> + + <root level="WARN"> + <appender-ref ref="STDERR"/> + </root> + +</configuration>