libeufin

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

commit b33d4324adecad7d3ffbcefda12e7e2791ab1151
parent 5e1baab1cc6509e2115941586ccd3b34996bb072
Author: MS <ms@taler.net>
Date:   Mon,  5 Dec 2022 20:35:36 +0100

Some legacy API tests.

Diffstat:
Mnexus/build.gradle | 13+++++++++++--
Mnexus/src/test/kotlin/DownloadAndSubmit.kt | 4++--
Anexus/src/test/kotlin/SandboxLegacyApiTest.kt | 305++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dnexus/src/test/kotlin/SelfContainedDBTest.kt | 73-------------------------------------------------------------------------
4 files changed, 318 insertions(+), 77 deletions(-)

diff --git a/nexus/build.gradle b/nexus/build.gradle @@ -89,6 +89,7 @@ dependencies { // Ktor, an HTTP client and server library implementation "io.ktor:ktor-server-core:$ktor_version" implementation "io.ktor:ktor-client-apache:$ktor_version" + implementation "io.ktor:ktor-client-auth:$ktor_version" implementation "io.ktor:ktor-server-netty:$ktor_version" implementation "io.ktor:ktor-server-test-host:$ktor_version" implementation "io.ktor:ktor-auth:$ktor_version" @@ -101,18 +102,26 @@ dependencies { implementation 'com.cronutils:cron-utils:9.1.5' // Unit testing - testImplementation 'junit:junit:4.13.2' + // testImplementation 'junit:junit:4.13.2' + // From https://docs.gradle.org/current/userguide/java_testing.html#sec:java_testing_basics: + testImplementation 'org.junit.jupiter:junit-jupiter:5.7.1' testImplementation 'org.jetbrains.kotlin:kotlin-test:1.5.21' testImplementation 'org.jetbrains.kotlin:kotlin-test-junit:1.5.21' } +test { + useJUnit() + failFast = true + testLogging.showStandardStreams = false + environment.put("LIBEUFIN_SANDBOX_ADMIN_PASSWORD", "foo") +} + application { mainClassName = "tech.libeufin.nexus.MainKt" applicationName = "libeufin-nexus" applicationDefaultJvmArgs = ['-Djava.net.preferIPv6Addresses=true'] } - jar { manifest { attributes "Main-Class": "tech.libeufin.nexus.MainKt" diff --git a/nexus/src/test/kotlin/DownloadAndSubmit.kt b/nexus/src/test/kotlin/DownloadAndSubmit.kt @@ -187,8 +187,8 @@ class DownloadAndSubmit { val painMessage = createPain001document( NexusPaymentInitiationData( debtorIban = bar!!.iban, - debtorBic = bar!!.bankCode, - debtorName = bar!!.accountHolder, + debtorBic = bar.bankCode, + debtorName = bar.accountHolder, currency = "TESTKUDOS", amount = "1", creditorIban = getIban(), diff --git a/nexus/src/test/kotlin/SandboxLegacyApiTest.kt b/nexus/src/test/kotlin/SandboxLegacyApiTest.kt @@ -0,0 +1,304 @@ +import com.fasterxml.jackson.databind.JsonNode +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.databind.util.ByteBufferBackedInputStream +import io.ktor.client.features.* +import io.ktor.client.request.* +import io.ktor.client.statement.HttpResponse +import io.ktor.http.* +import io.ktor.server.testing.* +import io.ktor.util.* +import io.ktor.utils.io.* +import io.netty.handler.codec.http.HttpResponseStatus +import kotlinx.coroutines.runBlocking +import org.junit.Ignore +import org.junit.Test +import tech.libeufin.sandbox.sandboxApp +import tech.libeufin.util.buildBasicAuthLine +import tech.libeufin.util.getIban +import java.io.ByteArrayOutputStream +import java.io.InputStream +import java.nio.ByteBuffer + +/** + * Mostly checking legacy API's access control. + */ +class SandboxLegacyApiTest { + fun dbHelper (f: () -> Unit) { + withTestDatabase { + prepSandboxDb() + f() + } + } + val mapper = ObjectMapper() + + + // EBICS Subscribers API. + @Test + fun adminEbiscSubscribers() { + dbHelper { + withTestApplication(sandboxApp) { + runBlocking { + /** + * Create a EBICS subscriber. That conflicts because + * MakeEnv.kt created it already, but tests access control + * and conflict detection. + */ + var body = mapper.writeValueAsString(object { + val hostID = "foo" + val userID = "foo" + val systemID = "foo" + val partnerID = "foo" + }) + var r: HttpResponse = client.post("/admin/ebics/subscribers") { + expectSuccess = false + headers { + append( + HttpHeaders.Authorization, + buildBasicAuthLine("admin", "foo") + ) + append( + HttpHeaders.ContentType, + ContentType.Application.Json + ) + } + this.body = body + } + assert(r.status.value == HttpStatusCode.Conflict.value) + /** + * Check that EBICS subscriber indeed exists. + */ + r = client.get("/admin/ebics/subscribers") { + headers { + append( + HttpHeaders.Authorization, + buildBasicAuthLine("admin", "foo") + ) + } + } + assert(r.status.value == HttpStatusCode.OK.value) + val buf = ByteArrayOutputStream() + r.content.read { buf.write(it.array()) } + val respObj = mapper.readTree(buf.toString()) + assert("foo" == respObj.get("subscribers").get(0).get("userID").asText()) + /** + * Try same operations as above, with wrong admin credentials + */ + r = client.get("/admin/ebics/subscribers") { + expectSuccess = false + headers { + append( + HttpHeaders.Authorization, + buildBasicAuthLine("admin", "wrong") + ) + } + } + assert(r.status.value == HttpStatusCode.Unauthorized.value) + r = client.post("/admin/ebics/subscribers") { + expectSuccess = false + headers { + append( + HttpHeaders.Authorization, + buildBasicAuthLine("admin", "wrong") + ) + } + } + assert(r.status.value == HttpStatusCode.Unauthorized.value) + // Good credentials, but unauthorized user. + r = client.get("/admin/ebics/subscribers") { + expectSuccess = false + headers { + append( + HttpHeaders.Authorization, + buildBasicAuthLine("foo", "foo") + ) + } + } + assert(r.status.value == HttpStatusCode.Unauthorized.value) + r = client.post("/admin/ebics/subscribers") { + expectSuccess = false + headers { + append( + HttpHeaders.Authorization, + buildBasicAuthLine("foo", "foo") + ) + } + } + assert(r.status.value == HttpStatusCode.Unauthorized.value) + /** + * Give a bank account to the existing subscriber. Bank account + * is (implicitly / hard-coded) hosted at default demobank. + */ + // Create new subscriber. No need to have the related customer. + body = mapper.writeValueAsString(object { + val hostID = "eufinSandbox" + val userID = "baz" + val partnerID = "baz" + val systemID = "foo" + }) + client.post<HttpResponse>("/admin/ebics/subscribers") { + expectSuccess = true + headers { + append( + HttpHeaders.Authorization, + buildBasicAuthLine("admin", "foo") + ) + append( + HttpHeaders.ContentType, + ContentType.Application.Json + ) + } + this.body = body + } + // Associate new bank account to it. + body = mapper.writeValueAsString(object { + val subscriber = object { + val userID = "baz" + val partnerID = "baz" + val systemID = "baz" + val hostID = "eufinSandbox" + } + val iban = getIban() + val bic = "SANDBOXX" + val name = "Now Have Account" + val label = "baz" + val owner = "baz" + }) + client.post<HttpResponse>("/admin/ebics/bank-accounts") { + expectSuccess = true + headers { + append( + HttpHeaders.Authorization, + buildBasicAuthLine("admin", "foo") + ) + append( + HttpHeaders.ContentType, + ContentType.Application.Json + ) + } + this.body = body + } + r = client.get("/admin/ebics/subscribers") { + headers { + append( + HttpHeaders.Authorization, + buildBasicAuthLine("admin", "foo") + ) + } + } + assert(r.status.value == HttpStatusCode.OK.value) + val buf_ = ByteArrayOutputStream() + r.content.read { buf_.write(it.array()) } + val respObj_ = mapper.readTree(buf_.toString()) + val bankAccountLabel = respObj_.get("subscribers").get(1).get("demobankAccountLabel").asText() + assert("baz" == bankAccountLabel) + // Same operation, wrong/unauth credentials. + r = client.post("/admin/ebics/bank-accounts") { + expectSuccess = false + headers { + append( + HttpHeaders.Authorization, + buildBasicAuthLine("admin", "wrong") + ) + } + } + assert(r.status.value == HttpStatusCode.Unauthorized.value) + r = client.post("/admin/ebics/bank-accounts") { + expectSuccess = false + headers { + append( + HttpHeaders.Authorization, + buildBasicAuthLine("foo", "foo") + ) + } + } + assert(r.status.value == HttpStatusCode.Unauthorized.value) + } + } + } + } + + // EBICS Hosts API. + @Ignore + fun adminEbicsCreateHost() { + dbHelper { + withTestApplication(sandboxApp) { + runBlocking { + val body = mapper.writeValueAsString( + object { + val hostID = "www" + var ebicsVersion = "www" + } + ) + // Valid request, good credentials. + var r = client.post<HttpResponse>("/admin/ebics/hosts") { + this.body = body + this.headers { + append( + HttpHeaders.Authorization, + buildBasicAuthLine("admin", "foo") + ) + append( + HttpHeaders.ContentType, + ContentType.Application.Json + ) + } + } + assert(r.status.value == HttpResponseStatus.OK.code()) + r = client.get("/admin/ebics/hosts") { + expectSuccess = false + + } + assert(r.status.value == HttpResponseStatus.UNAUTHORIZED.code()) + r = client.get("/admin/ebics/hosts") { + this.headers { + append( + HttpHeaders.Authorization, + buildBasicAuthLine("admin", "foo") + ) + } + } + assert(r.status.value == HttpResponseStatus.OK.code()) + // Invalid, with good credentials. + r = client.post("/admin/ebics/hosts") { + expectSuccess = false + this.body = "invalid" + this.headers { + append( + io.ktor.http.HttpHeaders.Authorization, + buildBasicAuthLine("admin", "foo") + ) + append( + io.ktor.http.HttpHeaders.ContentType, + ContentType.Application.Json + ) + } + } + assert(r.status.value == HttpResponseStatus.BAD_REQUEST.code()) + // Unauth: admin with wrong password. + r = client.post("/admin/ebics/hosts") { + expectSuccess = false + this.headers { + append( + io.ktor.http.HttpHeaders.Authorization, + buildBasicAuthLine("admin", "bar") + ) + } + } + assert(r.status.value == HttpResponseStatus.UNAUTHORIZED.code()) + // Auth & forbidden resource. + r = client.post("/admin/ebics/hosts") { + expectSuccess = false + this.headers { + append( + io.ktor.http.HttpHeaders.Authorization, + // Exist, but no rights over the EBICS host. + buildBasicAuthLine("foo", "foo") + ) + } + } + assert(r.status.value == HttpResponseStatus.UNAUTHORIZED.code()) + } + } + } + } +} +\ No newline at end of file diff --git a/nexus/src/test/kotlin/SelfContainedDBTest.kt b/nexus/src/test/kotlin/SelfContainedDBTest.kt @@ -1,72 +0,0 @@ -import org.jetbrains.exposed.dao.Entity -import org.jetbrains.exposed.dao.EntityClass -import org.jetbrains.exposed.dao.IntEntity -import org.jetbrains.exposed.dao.IntEntityClass -import org.jetbrains.exposed.dao.id.EntityID -import org.jetbrains.exposed.dao.id.IdTable -import org.jetbrains.exposed.dao.id.IntIdTable -import org.jetbrains.exposed.sql.Database -import org.jetbrains.exposed.sql.SchemaUtils -import org.jetbrains.exposed.sql.StdOutSqlLogger -import org.jetbrains.exposed.sql.addLogger -import org.jetbrains.exposed.sql.transactions.transaction -import org.junit.Test -import java.io.File - -object ContainedTableWithIntId : IntIdTable() { - val column = text("column") -} -class ContainedEntityWithIntId(id: EntityID<Int>) : IntEntity(id) { - companion object : IntEntityClass<ContainedEntityWithIntId>(ContainedTableWithIntId) - var column by ContainedTableWithIntId.column -} - -object ContainedTableWithStringId : IdTable<String>() { - override val id = varchar("id", 10).entityId() - override val primaryKey = PrimaryKey(id, name = "id") - val column = text("column") - -} -class ContainedEntityWithStringId(id: EntityID<String>) : Entity<String>(id) { - companion object : EntityClass<String, ContainedEntityWithStringId>(ContainedTableWithStringId) - var column by ContainedTableWithStringId.column -} - -object ContainingTable : IdTable<String>() { - override val id = varchar("id", 10).entityId() - override val primaryKey = PrimaryKey(id, name = "id") - val referenceStringId = reference("referenceStringId", ContainedTableWithStringId) - val referenceIntId = reference("referenceIntId", ContainedTableWithIntId) -} -class ContainingEntity(id: EntityID<String>) : Entity<String>(id) { - companion object : EntityClass<String, ContainingEntity>(ContainingTable) - var referenceStringId by ContainedEntityWithStringId referencedOn ContainingTable.referenceStringId - var referenceIntId by ContainedEntityWithIntId referencedOn ContainingTable.referenceIntId -} - -class DBTest { - @Test - fun facadeConfigTest() { - withTestDatabase { - transaction { - addLogger(StdOutSqlLogger) - SchemaUtils.create( - ContainingTable, - ContainedTableWithIntId, - ContainedTableWithStringId - ) - val entityWithIntId = ContainedEntityWithIntId.new { - column = "value" - } - entityWithIntId.flush() - val entityWithStringId = ContainedEntityWithStringId.new("contained-id") { - column = "another value" - } - ContainingEntity.new("containing-id") { - referenceIntId = entityWithIntId - referenceStringId = entityWithStringId - } - } - } - } -} -\ No newline at end of file