libeufin

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

commit bb96fd4f635cd9b760363acc7aacb9f7d890cf89
parent 7f5ad7e980ce63eb9652a620d73d991737a320e2
Author: MS <ms@taler.net>
Date:   Fri, 21 Apr 2023 21:39:03 +0200

addressing #7738

Diffstat:
Mnexus/src/main/kotlin/tech/libeufin/nexus/server/NexusServer.kt | 29+++++++++++++++++++++++------
Mnexus/src/test/kotlin/NexusApiTest.kt | 47+++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 70 insertions(+), 6 deletions(-)

diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/server/NexusServer.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/server/NexusServer.kt @@ -1036,7 +1036,7 @@ val nexusApp: Application.() -> Unit = { } post("/facades") { - requireSuperuser(call.request) + val user = requireSuperuser(call.request) val body = call.receive<FacadeInfo>() requireValidResourceName(body.name) if (!listOf("taler-wire-gateway", "anastasis").contains(body.type)) @@ -1044,9 +1044,29 @@ val nexusApp: Application.() -> Unit = { HttpStatusCode.NotImplemented, "Facade type '${body.type}' is not implemented" ) - try { + // Check if the facade exists already. + val createNewFacade = transaction { + val maybeFacade = FacadeEntity.findByName(body.name) + // Facade exists, check all the values for idempotence. + if (maybeFacade != null) { + // First get the associated config. + val facadeConfig = getFacadeState(maybeFacade.facadeName) + if (maybeFacade.type != body.type + || maybeFacade.creator.username != user.username + || facadeConfig.bankAccount != body.config.bankAccount + || facadeConfig.bankConnection != body.config.bankConnection + || facadeConfig.reserveTransferLevel != body.config.reserveTransferLevel + || facadeConfig.currency != body.config.currency) { + throw conflict("Facade ${body.name} exists but its state differs from the request.") + } + // Facade exists and has exact same values, inhibit creation. + else return@transaction false + } + // Facade does not exist, trigger creation. + true + } + if (createNewFacade) { transaction { - val user = authenticateRequest(call.request) val newFacade = FacadeEntity.new { facadeName = body.name type = body.type @@ -1060,9 +1080,6 @@ val nexusApp: Application.() -> Unit = { currency = body.config.currency } } - } catch (e: Exception) { - logger.error(e.stackTraceToString()) - throw internalServerError("Could not create facade") } call.respond(HttpStatusCode.OK) return@post diff --git a/nexus/src/test/kotlin/NexusApiTest.kt b/nexus/src/test/kotlin/NexusApiTest.kt @@ -67,6 +67,53 @@ class NexusApiTest { } } } + @Test + fun facadeIdempotence() { + val facadeData = """{ + "name": "foo-facade", + "type": "taler-wire-gateway", + "config": { + "bankAccount": "foo", + "bankConnection": "foo", + "reserveTransferLevel": "report", + "currency": "TESTKUDOS" + } + }""".trimIndent() + withTestDatabase { + prepNexusDb() + testApplication { + application(nexusApp) + client.post("/facades") { + expectSuccess = true + basicAuth("foo", "foo") + contentType(ContentType.Application.Json) + setBody(facadeData) + } + // Changing one detail, and expecting 409 Conflict. + var resp = client.post("/facades") { + expectSuccess = false + basicAuth("foo", "foo") + contentType(ContentType.Application.Json) + setBody(facadeData.replace( + "taler-wire-gateway", + "anastasis" + )) + } + assert(resp.status.value == HttpStatusCode.Conflict.value) + // Changing a value deeper in the request object. + resp = client.post("/facades") { + expectSuccess = false + basicAuth("foo", "foo") + contentType(ContentType.Application.Json) + setBody(facadeData.replace( + "report", + "statement" + )) + } + assert(resp.status.value == HttpStatusCode.Conflict.value) + } + } + } // Testing basic operations on facades. @Test fun facades() {