commit bb96fd4f635cd9b760363acc7aacb9f7d890cf89
parent 7f5ad7e980ce63eb9652a620d73d991737a320e2
Author: MS <ms@taler.net>
Date: Fri, 21 Apr 2023 21:39:03 +0200
addressing #7738
Diffstat:
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() {