From 97b07fb4249f5fcf5bef6200b34acf1cba543c32 Mon Sep 17 00:00:00 2001 From: Fabrice Lecomte Date: Mon, 15 Mar 2021 02:01:39 +0100 Subject: [PATCH] Test openapi schema requestBody --- src/test/kotlin/integration/Article routes.kt | 11 +-- src/test/kotlin/integration/Citizen routes.kt | 9 +- .../integration/Comment articles routes.kt | 9 +- .../Comment constitutions routes.kt | 6 +- .../kotlin/integration/Constitution routes.kt | 5 +- src/test/kotlin/integration/Login routes.kt | 15 ++-- src/test/kotlin/integration/Opinion routes.kt | 5 +- .../kotlin/integration/Register routes.kt | 9 +- src/test/kotlin/integration/Vote routes.kt | 13 +-- .../kotlin/integration/Workgroup routes.kt | 13 +-- .../kotlin/integration/steps/then/schema.kt | 85 ++++++++++++------- .../kotlin/integration/steps/when/request.kt | 62 +++++++++++--- 12 files changed, 155 insertions(+), 87 deletions(-) diff --git a/src/test/kotlin/integration/Article routes.kt b/src/test/kotlin/integration/Article routes.kt index d943f64..1faa3ba 100644 --- a/src/test/kotlin/integration/Article routes.kt +++ b/src/test/kotlin/integration/Article routes.kt @@ -6,6 +6,7 @@ import integration.steps.then.`And the response should not be null` import integration.steps.then.`Then the response should be` import integration.steps.`when`.`When I send a GET request` import integration.steps.`when`.`When I send a POST request` +import integration.steps.`when`.`with body` import integration.steps.then.`whish contains` import integration.steps.then.and import integration.steps.given.`Given I have article created by workgroup` @@ -14,7 +15,6 @@ import integration.steps.given.`Given I have articles` import integration.steps.given.`Given I have citizen` import integration.steps.given.`Given I have workgroup` import integration.steps.given.`authenticated as` -import integration.steps.then.`And schema must be valid` import integration.steps.then.`And the response should contain list` import integration.steps.then.`And the response should not contain` import io.ktor.http.HttpStatusCode.Companion.OK @@ -37,7 +37,6 @@ class `Article routes` : BaseTest() { `And the response should contain pattern`("$.result[2].createdBy.name.firstName", "firstName.+") `And the response should not contain`("$.result[3]") `And the response should contain list`("$.result", 3, 3) - `And schema must be valid`() } } } @@ -52,7 +51,6 @@ class `Article routes` : BaseTest() { `And the response should not be null`() `And have property`("$.total") `whish contains` 1 `And have property`("$.result[0]workgroup.name") `whish contains` "Les papy" - `And schema must be valid`() } } } @@ -64,7 +62,6 @@ class `Article routes` : BaseTest() { `When I send a GET request`("/articles/65cda9f3-8991-4420-8d41-1da9da72c9bb") `Then the response should be` OK and { `And the response should not be null`() `And have property`("$.id") `whish contains` "65cda9f3-8991-4420-8d41-1da9da72c9bb" - `And schema must be valid`() } } } @@ -77,7 +74,6 @@ class `Article routes` : BaseTest() { `And the response should not be null`() `And have property`("$.total") `whish contains` 1 `And have property`("$.result[0].id") `whish contains` "13e6091c-8fed-4600-b079-a97a6b7a9800" - `And schema must be valid`() } } } @@ -88,7 +84,7 @@ class `Article routes` : BaseTest() { `Given I have citizen`("John", "Doe") `When I send a POST request`("/articles") { `authenticated as`("John", "Doe") - """ + `with body`(""" { "versionId": "09c418b6-63ba-448b-b38b-502b41cd500e", "title": "title2", @@ -99,11 +95,10 @@ class `Article routes` : BaseTest() { "green" ] } - """ + """) } `Then the response should be` OK and { `And the response should not be null`() `And have property`("$.versionId") `whish contains` "09c418b6-63ba-448b-b38b-502b41cd500e" - `And schema must be valid`() } } } diff --git a/src/test/kotlin/integration/Citizen routes.kt b/src/test/kotlin/integration/Citizen routes.kt index d9ea4d4..9ad6e7e 100644 --- a/src/test/kotlin/integration/Citizen routes.kt +++ b/src/test/kotlin/integration/Citizen routes.kt @@ -5,6 +5,7 @@ import integration.steps.then.`And the response should not be null` import integration.steps.then.`Then the response should be` import integration.steps.`when`.`When I send a GET request` import integration.steps.`when`.`When I send a PUT request` +import integration.steps.`when`.`with body` import integration.steps.then.`whish contains` import integration.steps.then.and import integration.steps.given.`Given I have citizen` @@ -64,12 +65,12 @@ class `Citizen routes` : BaseTest() { `Given I have citizen`("Georges", "Charpak", id = "0c966522-4071-43e5-a3ca-cfff2557f2cf") `When I send a PUT request`("/citizens/0c966522-4071-43e5-a3ca-cfff2557f2cf/password/change") { `authenticated as`("Georges", "Charpak") - """ + `with body`(""" { "old_password": "azerty", "new_password": "qwerty" } - """ + """) } `Then the response should be` Created } } @@ -80,12 +81,12 @@ class `Citizen routes` : BaseTest() { `Given I have citizen`("Louis", "Breguet", id = "6cf2a19d-d15d-4ee5-b2a9-907afd26b525") `When I send a PUT request`("/citizens/6cf2a19d-d15d-4ee5-b2a9-907afd26b525/password/change") { `authenticated as`("Louis", "Breguet") - """ + `with body`(""" { "plup": "azerty", "gloup": "qwerty" } - """ + """) } `Then the response should be` BadRequest } } diff --git a/src/test/kotlin/integration/Comment articles routes.kt b/src/test/kotlin/integration/Comment articles routes.kt index d1d693a..d23082a 100644 --- a/src/test/kotlin/integration/Comment articles routes.kt +++ b/src/test/kotlin/integration/Comment articles routes.kt @@ -7,6 +7,7 @@ import integration.steps.then.`Then the response should be` import integration.steps.`when`.`When I send a GET request` import integration.steps.`when`.`When I send a POST request` import integration.steps.`when`.`When I send a PUT request` +import integration.steps.`when`.`with body` import integration.steps.then.and import integration.steps.given.`Given I have article` import integration.steps.given.`Given I have citizen` @@ -29,11 +30,11 @@ class `Comment articles routes` : BaseTest() { `Given I have article`(id = "aa16c635-28da-46f0-9a89-934eef88c7ca") `When I send a POST request`("/articles/aa16c635-28da-46f0-9a89-934eef88c7ca/comments") { `authenticated as`("Michael", "Faraday") - """ + `with body`(""" { "content": "Hello mister" } - """ + """) } `Then the response should be` Created and { `And the response should not be null`() `And the response should contain`("$.target.id", "aa16c635-28da-46f0-9a89-934eef88c7ca") @@ -98,9 +99,9 @@ class `Comment articles routes` : BaseTest() { `Given I have comment on article`(article = "bb05e4a3-55a1-4088-85e7-8d8c23be29b1", createdBy = Name("Hubert", "Reeves"), id = "fd30d20f-656c-42c6-8955-f61c04537464") `When I send a PUT request`("/comments/fd30d20f-656c-42c6-8955-f61c04537464") { `authenticated as`("Hubert", "Reeves") - """ + `with body`(""" Hello boy - """ + """) } `Then the response should be` OK and { `And the response should not be null`() `And the response should contain`("$.content", "Hello boy") diff --git a/src/test/kotlin/integration/Comment constitutions routes.kt b/src/test/kotlin/integration/Comment constitutions routes.kt index 2b192dc..1ceb37c 100644 --- a/src/test/kotlin/integration/Comment constitutions routes.kt +++ b/src/test/kotlin/integration/Comment constitutions routes.kt @@ -7,6 +7,7 @@ import integration.steps.then.`And the response should not be null` import integration.steps.then.`Then the response should be` import integration.steps.`when`.`When I send a GET request` import integration.steps.`when`.`When I send a POST request` +import integration.steps.`when`.`with body` import integration.steps.then.and import integration.steps.given.`Given I have citizen` import integration.steps.given.`Given I have comment on constitution` @@ -14,6 +15,7 @@ import integration.steps.given.`Given I have constitution` import integration.steps.given.`authenticated as` import io.ktor.http.HttpStatusCode.Companion.Created import io.ktor.http.HttpStatusCode.Companion.OK +import io.ktor.server.testing.setBody import org.junit.jupiter.api.Tag import org.junit.jupiter.api.Tags import org.junit.jupiter.api.Test @@ -29,11 +31,11 @@ class `Comment constitutions routes` : BaseTest() { `Given I have constitution`(id = "1707c287-a472-4a62-89f2-9e85030e915c") `When I send a POST request`("/constitutions/1707c287-a472-4a62-89f2-9e85030e915c/comments") { `authenticated as`("Nicolas", "Copernic") - """ + `with body`(""" { "content": "Hello mister" } - """ + """) } `Then the response should be` Created and { `And the response should not be null`() } diff --git a/src/test/kotlin/integration/Constitution routes.kt b/src/test/kotlin/integration/Constitution routes.kt index 7a1ede9..b127116 100644 --- a/src/test/kotlin/integration/Constitution routes.kt +++ b/src/test/kotlin/integration/Constitution routes.kt @@ -5,6 +5,7 @@ import integration.steps.then.`And the response should not be null` import integration.steps.then.`Then the response should be` import integration.steps.`when`.`When I send a GET request` import integration.steps.`when`.`When I send a POST request` +import integration.steps.`when`.`with body` import integration.steps.then.`whish contains` import integration.steps.then.and import integration.steps.given.`Given I have citizen` @@ -62,7 +63,7 @@ class `Constitution routes` : BaseTest() { `Given I have citizen`("Henri", "Poincaré") `When I send a POST request`("/constitutions") { `authenticated as`("Henri", "Poincaré") - """ + `with body`(""" { "version_id":"15814bb6-8d90-4c6a-a456-c3939a8ec75e", "title":"Hello world!", @@ -74,7 +75,7 @@ class `Constitution routes` : BaseTest() { } ] } - """ + """) } `Then the response should be` OK and { `And the response should not be null`() `And have property`("$.version_id") `whish contains` "15814bb6-8d90-4c6a-a456-c3939a8ec75e" diff --git a/src/test/kotlin/integration/Login routes.kt b/src/test/kotlin/integration/Login routes.kt index 65f837b..6489136 100644 --- a/src/test/kotlin/integration/Login routes.kt +++ b/src/test/kotlin/integration/Login routes.kt @@ -4,10 +4,12 @@ import integration.steps.then.`And the response should not be null` import integration.steps.then.`Then the response should be` import integration.steps.then.`and should contains` import integration.steps.`when`.`When I send a POST request` +import integration.steps.`when`.`with body` import integration.steps.given.`Given I have citizen` import integration.steps.given.`authenticated as` -import io.ktor.http.HttpStatusCode +import integration.steps.then.and import io.ktor.http.HttpStatusCode.Companion.NoContent +import io.ktor.http.HttpStatusCode.Companion.OK import org.junit.jupiter.api.Tag import org.junit.jupiter.api.Tags import org.junit.jupiter.api.Test @@ -21,14 +23,15 @@ class `Login routes` : BaseTest() { withIntegrationApplication { `Given I have citizen`("Niels", "Bohr") `When I send a POST request`("/login") { - """ + `with body`(""" { "username": "niels-bohr", "password": "azerty" } - """ - }.`Then the response should be`(HttpStatusCode.OK) { + """) + } `Then the response should be` OK and { `And the response should not be null`() `and should contains` "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9." + //TODO valid requestBody } } } @@ -39,12 +42,12 @@ class `Login routes` : BaseTest() { `Given I have citizen`("Leonhard", "Euler", "fabrice.lecomte.be@gmail.com", id = "c606110c-ff0e-4d09-a79e-74632d7bf7bd") `When I send a POST request`("/auth/passwordless") { `authenticated as`("Leonhard", "Euler") - """ + `with body`(""" { "url": "https://dc-project.fr/password/reset", "email": "fabrice.lecomte.be@gmail.com" } - """ + """) } `Then the response should be` NoContent } } diff --git a/src/test/kotlin/integration/Opinion routes.kt b/src/test/kotlin/integration/Opinion routes.kt index 29990be..580ecc0 100644 --- a/src/test/kotlin/integration/Opinion routes.kt +++ b/src/test/kotlin/integration/Opinion routes.kt @@ -6,6 +6,7 @@ import integration.steps.then.`And the response should contain` import integration.steps.then.`Then the response should be` import integration.steps.`when`.`When I send a GET request` import integration.steps.`when`.`When I send a PUT request` +import integration.steps.`when`.`with body` import integration.steps.then.and import integration.steps.given.`Given I have an opinion choice` import integration.steps.given.`Given I have article` @@ -54,13 +55,13 @@ class `Opinion routes` : BaseTest() { `Given I have article`(id = "9226c1a3-8091-c3fa-7d0d-c2e98c9bee7b", createdBy = Name("Isaac", "Newton")) `When I send a PUT request`("/articles/9226c1a3-8091-c3fa-7d0d-c2e98c9bee7b/opinions") { `authenticated as`("Isaac", "Newton") - """ + `with body`(""" { "ids": [ "0f4f1721-3136-44f1-9f31-1459f3317b15" ] } - """ + """) } `Then the response should be` Created } } diff --git a/src/test/kotlin/integration/Register routes.kt b/src/test/kotlin/integration/Register routes.kt index 3614615..657386a 100644 --- a/src/test/kotlin/integration/Register routes.kt +++ b/src/test/kotlin/integration/Register routes.kt @@ -2,6 +2,7 @@ package integration import integration.steps.then.`Then the response should be` import integration.steps.`when`.`When I send a POST request` +import integration.steps.`when`.`with body` import io.ktor.http.HttpStatusCode import org.amshove.kluent.`should be null` import org.amshove.kluent.`should contain` @@ -18,7 +19,7 @@ class `Register routes` : BaseTest() { fun `I can register`() { withIntegrationApplication { `When I send a POST request`("/register") { - """ + `with body`(""" { "name": {"first_name":"George", "last_name":"MICHEL"}, "birthday": "2001-01-01", @@ -28,7 +29,7 @@ class `Register routes` : BaseTest() { }, "email": "george-junior@gmail.com" } - """ + """) }.`Then the response should be`(HttpStatusCode.OK) { content .`should not be null`() @@ -41,7 +42,7 @@ class `Register routes` : BaseTest() { fun `I cannot register if no username was sent`() { withIntegrationApplication { `When I send a POST request`("/register") { - """ + `with body`(""" { "name": {"first_name":"George2", "last_name":"MICHEL2"}, "birthday": "2001-01-01", @@ -50,7 +51,7 @@ class `Register routes` : BaseTest() { "password": "" } } - """ + """) }.`Then the response should be`(HttpStatusCode.BadRequest) { content.`should be null`() } diff --git a/src/test/kotlin/integration/Vote routes.kt b/src/test/kotlin/integration/Vote routes.kt index e657e53..b3b2ae2 100644 --- a/src/test/kotlin/integration/Vote routes.kt +++ b/src/test/kotlin/integration/Vote routes.kt @@ -5,6 +5,7 @@ import integration.steps.then.`And the response should contain` import integration.steps.then.`Then the response should be` import integration.steps.`when`.`When I send a GET request` import integration.steps.`when`.`When I send a PUT request` +import integration.steps.`when`.`with body` import integration.steps.then.and import integration.steps.given.`Given I have article` import integration.steps.given.`Given I have citizen` @@ -30,11 +31,11 @@ class `Vote routes` : BaseTest() { `Given I have article`(id = "835c5101-ca39-4038-a4e6-da6ee62ca6d5") `When I send a PUT request`("/articles/835c5101-ca39-4038-a4e6-da6ee62ca6d5/vote") { `authenticated as`("Thalès", "Milet") - """ + `with body`(""" { "note": 1 } - """ + """) } `Then the response should be` Created } } @@ -46,11 +47,11 @@ class `Vote routes` : BaseTest() { `Given I have constitution`(id = "76e79c89-efc1-492d-9e8f-dc9717363a11") `When I send a PUT request`("/constitutions/76e79c89-efc1-492d-9e8f-dc9717363a11/vote") { `authenticated as`("Gregor", "Mendel") - """ + `with body`(""" { "note": 1 } - """ + """) } `Then the response should be` Created } } @@ -101,11 +102,11 @@ class `Vote routes` : BaseTest() { ) `When I send a PUT request`("/comments/e793eccc-456b-4450-a292-46d592229b74/vote") { `authenticated as`("Antoine", "Lavoisier") - """ + `with body`(""" { "note": -1 } - """ + """) } `Then the response should be` Created and { `And the response should contain`("$.down", 1) } diff --git a/src/test/kotlin/integration/Workgroup routes.kt b/src/test/kotlin/integration/Workgroup routes.kt index 80a1a1b..fed5fc3 100644 --- a/src/test/kotlin/integration/Workgroup routes.kt +++ b/src/test/kotlin/integration/Workgroup routes.kt @@ -9,6 +9,7 @@ import integration.steps.`when`.`When I send a DELETE request` import integration.steps.`when`.`When I send a GET request` import integration.steps.`when`.`When I send a POST request` import integration.steps.`when`.`When I send a PUT request` +import integration.steps.`when`.`with body` import integration.steps.then.and import integration.steps.given.`Given I have citizen` import integration.steps.given.`Given I have workgroup` @@ -66,14 +67,14 @@ class `Workgroup routes` : BaseTest() { `Given I have citizen`("Werner", "Heisenberg") `When I send a POST request`("/workgroups") { `authenticated as`("Werner", "Heisenberg") - """ + `with body`(""" { "id":"f496d86d-6654-4068-91ff-90e1dbcc5f38", "name":"Les Bouffons", "description":"La vie est belle", "anonymous":false } - """ + """) } `Then the response should be` Created and { `And the response should contain`("$.id", "f496d86d-6654-4068-91ff-90e1dbcc5f38") `And the response should contain`("$.name", "Les Bouffons") @@ -128,7 +129,7 @@ class `Workgroup routes` : BaseTest() { `Given I have workgroup`("b0ea1922-3bc6-44e2-aa7c-40158998cfbb", createdBy = Name("Blaise", "Pascal")) `When I send a POST request`("/workgroups/b0ea1922-3bc6-44e2-aa7c-40158998cfbb/members") { `authenticated as`("Blaise", "Pascal") - """ + `with body`(""" [ { "citizen": {"id":"6d883fe7-5fc0-4a50-8858-72230673eba4"}, @@ -139,7 +140,7 @@ class `Workgroup routes` : BaseTest() { "roles": ["MASTER"] } ] - """ + """) } `Then the response should be` Created } } @@ -189,7 +190,7 @@ class `Workgroup routes` : BaseTest() { } `When I send a PUT request`("/workgroups/784fe6bc-7635-4ae2-b080-3a4743b998bf/members") { `authenticated as`("Leon", "Foucault") - """ + `with body`(""" [ { "citizen": {"id":"be3b0926-8628-4426-804a-75188a6eb315"}, @@ -200,7 +201,7 @@ class `Workgroup routes` : BaseTest() { "roles": ["MASTER"] } ] - """ + """) } `Then the response should be` OK and { `And the response should contain list`("$", 2, 2) `And the response should contain`("$.[0]citizen.id", "be3b0926-8628-4426-804a-75188a6eb315") diff --git a/src/test/kotlin/integration/steps/then/schema.kt b/src/test/kotlin/integration/steps/then/schema.kt index a2d2e83..94653cd 100644 --- a/src/test/kotlin/integration/steps/then/schema.kt +++ b/src/test/kotlin/integration/steps/then/schema.kt @@ -4,6 +4,7 @@ import com.fasterxml.jackson.databind.JsonNode import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.node.TextNode import fr.dcproject.common.utils.getResource +import io.ktor.http.ContentType import io.ktor.http.Url import io.ktor.request.contentType import io.ktor.request.httpMethod @@ -11,14 +12,27 @@ import io.ktor.request.uri import io.ktor.server.testing.TestApplicationResponse import org.openapi4j.core.model.v3.OAI3 import org.openapi4j.parser.OpenApi3Parser +import org.openapi4j.parser.model.v3.OpenApi3 +import org.openapi4j.parser.model.v3.Operation +import org.openapi4j.parser.model.v3.Schema import org.openapi4j.schema.validator.ValidationContext import org.openapi4j.schema.validator.ValidationData import org.openapi4j.schema.validator.v3.SchemaValidator import java.io.File import kotlin.test.assertTrue -fun TestApplicationResponse.`And schema must be valid`(route: String? = null) { - OpenApi3Parser().parse(File("/openapi2.yaml".getResource().toURI()), true).let { api -> +fun Schema.validate(api: OpenApi3, toValidate: JsonNode) { + val validationContext: ValidationContext = ValidationContext(api.context) + val schemaValidator = SchemaValidator(validationContext, "", this.toNode()) + + val results = ValidationData() + schemaValidator.validate(toValidate, results) + + assertTrue(results.isValid, results.results().toString()) +} + +fun TestApplicationResponse.operation(route: String? = null, callback: Operation.(OpenApi3, String) -> Unit): Operation { + return OpenApi3Parser().parse(File("/openapi2.yaml".getResource().toURI()), true).let { api: OpenApi3 -> val operation = call.request.httpMethod val uri = route ?: "/" + Url(call.request.uri).encodedPath val path = api.paths @@ -28,38 +42,43 @@ fun TestApplicationResponse.`And schema must be valid`(route: String? = null) { api.getPath(path) ?.getOperation(operation.value.toLowerCase()) ?.apply { - val mediaType = call.request.contentType() - val status = call.response.status() - getResponse(status?.value?.toString() ?: error("HttpStatus not found")) - ?.getContentMediaType(mediaType.toString()) - ?.schema?.let { schema -> - val validationContext: ValidationContext = ValidationContext(api.context) - val jsonNode: JsonNode = schema.toNode() - val schemaValidator = SchemaValidator(validationContext, "", jsonNode) - - val results = ValidationData() - val mapper = ObjectMapper() - schemaValidator.validate(mapper.readTree(content), results) - - assertTrue(results.isValid, results.results().toString()) - } - ?: error("""No path found for "$operation $uri" for status ${status.value} with media type "$mediaType".""") - } - ?.apply { - Url(call.request.uri).parameters.forEach { parameter: String, values: List -> - getParametersIn(api.context, "query") - ?.firstOrNull { it.name == parameter }?.schema?.let { schema -> - val validationContext: ValidationContext = ValidationContext(api.context) - val jsonNode: JsonNode = schema.toNode() - val schemaValidator = SchemaValidator(validationContext, "", jsonNode) - val params = ValidationData() - schemaValidator.validate(TextNode(values.first()), params) - - assertTrue(params.isValid, params.results().toString()) - } - ?: error("""No path found for "$operation $uri" for status "$parameter".""") - } + this.callback(api, uri) } ?: error("""No path found for "$operation $uri".""") } } + +fun TestApplicationResponse.`And the schema must be valid`(route: String? = null, contentType: ContentType? = ContentType.Application.Json) { + operation(route) { api, uri -> + /* Validate Response */ + this.apply { + val status = call.response.status() + getResponse(status?.value?.toString() ?: error("HttpStatus not found")) + ?.getContentMediaType(contentType.toString()) + ?.schema + ?.validate(api, ObjectMapper().readTree(content)) + ?: error("""No path found for "$this $uri" for status ${status.value} with media type "$contentType".""") + } + /* Validate Request URL */ + this.apply { + Url(call.request.uri).parameters.forEach { parameter: String, values: List -> + getParametersIn(api.context, "query") + ?.firstOrNull { it.name == parameter }?.schema + ?.validate(api, TextNode(values.first())) + ?: error("""No path found for "$this $uri" for status "$parameter".""") + } + } + } +} + +/** + * Validate request body + */ +fun TestApplicationResponse.`And the schema request body must be valid`(body: String) { + operation { api, uri -> + requestBody + .getContentMediaType(call.request.contentType().toString()) + .schema + .validate(api, ObjectMapper().readTree(body)) + } +} diff --git a/src/test/kotlin/integration/steps/when/request.kt b/src/test/kotlin/integration/steps/when/request.kt index 5eb7e21..a4ab480 100644 --- a/src/test/kotlin/integration/steps/when/request.kt +++ b/src/test/kotlin/integration/steps/when/request.kt @@ -1,5 +1,8 @@ package integration.steps.`when` +import integration.steps.then.`And the schema must be valid` +import integration.steps.then.`And the schema request body must be valid` +import io.ktor.application.ApplicationCall import io.ktor.http.ContentType import io.ktor.http.HttpHeaders import io.ktor.http.HttpMethod @@ -8,7 +11,7 @@ import io.ktor.server.testing.TestApplicationEngine import io.ktor.server.testing.TestApplicationRequest import io.ktor.server.testing.setBody -fun TestApplicationEngine.`When I send a GET request`(uri: String? = null, setup: (TestApplicationRequest.() -> Unit)? = null): TestApplicationCall { +fun TestApplicationEngine.`When I send a GET request`(uri: String? = null, validate: Boolean = true, setup: (TestApplicationRequest.() -> Unit)? = null): TestApplicationCall { return handleRequest(true) { method = HttpMethod.Get if (uri != null) { @@ -16,36 +19,54 @@ fun TestApplicationEngine.`When I send a GET request`(uri: String? = null, setup } addHeader(HttpHeaders.ContentType, ContentType.Application.Json.toString()) setup?.let { it() } + }.apply { + if (validate) { + response.`And the schema must be valid`() + requestBody?.let { body -> + response.`And the schema request body must be valid`(body) + } + } } } -fun TestApplicationEngine.`When I send a POST request`(uri: String? = null, setup: (TestApplicationRequest.() -> String?)? = null): TestApplicationCall { +fun TestApplicationEngine.`When I send a POST request`(uri: String? = null, validate: Boolean = true, setup: (TestApplicationRequest.() -> Unit)? = null): TestApplicationCall { return handleRequest(true) { method = HttpMethod.Post if (uri != null) { this.uri = uri } addHeader(HttpHeaders.ContentType, ContentType.Application.Json.toString()) - setup?.let { it() }?.let { - setBody(it.trimIndent()) + addHeader(HttpHeaders.Accept, ContentType.Application.Json.toString()) + setup?.let { it() } + }.apply { + if (validate) { + response.`And the schema must be valid`() + requestBody?.let { body -> + response.`And the schema request body must be valid`(body) + } } } } -fun TestApplicationEngine.`When I send a PUT request`(uri: String? = null, setup: (TestApplicationRequest.() -> String?)? = null): TestApplicationCall { +fun TestApplicationEngine.`When I send a PUT request`(uri: String? = null, validate: Boolean = true, setup: (TestApplicationRequest.() -> Unit)? = null): TestApplicationCall { return handleRequest(true) { method = HttpMethod.Put if (uri != null) { this.uri = uri } addHeader(HttpHeaders.ContentType, ContentType.Application.Json.toString()) - setup?.let { it() }?.let { - setBody(it.trimIndent()) + setup?.let { it() } + }.apply { + if (validate) { + response.`And the schema must be valid`() + requestBody?.let { body -> + response.`And the schema request body must be valid`(body) + } } } } -fun TestApplicationEngine.`When I send a DELETE request`(uri: String? = null, setup: (TestApplicationRequest.() -> String?)? = null): TestApplicationCall { +fun TestApplicationEngine.`When I send a DELETE request`(uri: String? = null, validate: Boolean = true, setup: (TestApplicationRequest.() -> String?)? = null): TestApplicationCall { return handleRequest(true) { method = HttpMethod.Delete if (uri != null) { @@ -55,9 +76,30 @@ fun TestApplicationEngine.`When I send a DELETE request`(uri: String? = null, se setup?.let { it() }?.let { setBody(it.trimIndent()) } + }.apply { + if (validate) { + response.`And the schema must be valid`() + requestBody?.let { body -> + response.`And the schema request body must be valid`(body) + } + } } } -fun TestApplicationRequest.`with body`(body: String) { - setBody(body.trimIndent()) +private val requestBodies: MutableMap = mutableMapOf() +var TestApplicationCall.requestBody: String? + get() = requestBodies[this] + set(value) { + if (value == null) { + requestBodies.remove(this) + } else { + requestBodies[this] = value + } + } + +infix fun TestApplicationRequest.`with body`(body: String) { + return body.trimIndent().let { + setBody(it) + (call as TestApplicationCall).requestBody = it + } }