From dd4c2dadab6490dd69e21f5f3bc24729bc032e8d Mon Sep 17 00:00:00 2001 From: Fabrice Lecomte Date: Fri, 2 Apr 2021 22:41:04 +0200 Subject: [PATCH 1/3] Fix parameters schema validation --- .../kotlin/fr/dcproject/common/utils/Numeric.kt | 4 ++++ src/test/kotlin/integration/steps/then/schema.kt | 15 ++++++++++++--- 2 files changed, 16 insertions(+), 3 deletions(-) create mode 100644 src/main/kotlin/fr/dcproject/common/utils/Numeric.kt diff --git a/src/main/kotlin/fr/dcproject/common/utils/Numeric.kt b/src/main/kotlin/fr/dcproject/common/utils/Numeric.kt new file mode 100644 index 0000000..9fa2138 --- /dev/null +++ b/src/main/kotlin/fr/dcproject/common/utils/Numeric.kt @@ -0,0 +1,4 @@ +package fr.dcproject.common.utils + +fun String.isInt(): Boolean = this.toIntOrNull() != null +fun String.isBool(): Boolean = this == "true" || this == "false" diff --git a/src/test/kotlin/integration/steps/then/schema.kt b/src/test/kotlin/integration/steps/then/schema.kt index 0b1ceb2..fe04cf0 100644 --- a/src/test/kotlin/integration/steps/then/schema.kt +++ b/src/test/kotlin/integration/steps/then/schema.kt @@ -2,8 +2,12 @@ package integration.steps.then import com.fasterxml.jackson.databind.JsonNode import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.databind.node.BooleanNode +import com.fasterxml.jackson.databind.node.IntNode import com.fasterxml.jackson.databind.node.TextNode import fr.dcproject.common.utils.getResource +import fr.dcproject.common.utils.isBool +import fr.dcproject.common.utils.isInt import io.ktor.http.ContentType import io.ktor.http.Url import io.ktor.request.contentType @@ -55,15 +59,15 @@ fun TestApplicationResponse.`And the schema response body must be valid`(content /* Validate Response */ this.apply { val status = call.response.status() + val httpMethod = call.request.httpMethod.value.toUpperCase() val responseContent: JsonNode = if (content != null) ObjectMapper().readTree(content) else TextNode("") - val response = getResponse(status?.value?.toString() ?: error("HttpStatus not found")) ?: fail("""No Status "${status.value}" found for "$this $uri".""") + val response = getResponse(status?.value?.toString() ?: error("HttpStatus not found")) ?: fail("""No Status "${status.value}" found for "$httpMethod $uri".""") val schema = response.getContentMediaType(contentType.toString())?.schema if (content != null) { - val httpMethod = call.request.httpMethod.value schema?.validate(api, responseContent) ?: fail("""No Status "${status.value}" found with media type "$contentType" for "$httpMethod $uri".""") } @@ -75,13 +79,18 @@ fun TestApplicationResponse.`And the schema parameters must be valid`() { operation { api, uri -> /* Validate Request URL */ this.apply { + val methodName = call.request.httpMethod.value.toUpperCase() Url(call.request.uri).parameters.forEach { parameter: String, values: List -> val schema = getParametersIn(api.context, "query") ?.firstOrNull { it.name == parameter }?.schema - ?: error("""No parameter found ($parameter) for "$this $uri".""") + ?: error("""No parameter found ($parameter) for "$methodName $uri".""") if (schema.type == "array") { schema.validate(api, ObjectMapper().valueToTree(values)) + } else if (schema.type == "integer" && values.first().isInt()) { + schema.validate(api, IntNode(values.first().toInt())) + } else if (schema.type == "boolean" && values.first().isBool()) { + schema.validate(api, BooleanNode.valueOf(values.first().toBoolean())) } else { schema.validate(api, TextNode(values.first())) } -- 2.49.1 From a48cd52652f50e31a7083f4659121ef22fef839a Mon Sep 17 00:00:00 2001 From: Fabrice Lecomte Date: Sat, 3 Apr 2021 00:10:01 +0200 Subject: [PATCH 2/3] Add Tags on tests --- src/test/kotlin/functional/MailerTest.kt | 2 +- src/test/kotlin/functional/NotificationConsumerTest.kt | 2 +- src/test/kotlin/functional/NotificationsPushTest.kt | 2 +- src/test/kotlin/functional/ResourcesKtTest.kt | 2 +- src/test/kotlin/functional/ViewTest.kt | 2 +- src/test/kotlin/integration/Opinion routes.kt | 5 ++++- src/test/kotlin/unit/security/Article Access Control.kt | 2 +- src/test/kotlin/unit/security/Citizen Access Control.kt | 2 +- src/test/kotlin/unit/security/Comment Access Control.kt | 2 +- src/test/kotlin/unit/security/Follow Access Control.kt | 2 +- src/test/kotlin/unit/security/Opinion Access Control.kt | 2 +- .../kotlin/unit/security/OpinionChoice Access Control.kt | 2 +- src/test/kotlin/unit/security/Vote Access Control.kt | 2 +- src/test/kotlin/unit/security/Workgroup Access Control.kt | 2 +- 14 files changed, 17 insertions(+), 14 deletions(-) diff --git a/src/test/kotlin/functional/MailerTest.kt b/src/test/kotlin/functional/MailerTest.kt index f6bbb6a..b63d6fc 100644 --- a/src/test/kotlin/functional/MailerTest.kt +++ b/src/test/kotlin/functional/MailerTest.kt @@ -22,7 +22,7 @@ import org.koin.test.get @KtorExperimentalLocationsAPI @KtorExperimentalAPI @TestInstance(TestInstance.Lifecycle.PER_CLASS) -@Tags(Tag("functional")) +@Tags(Tag("functional"), Tag("mail")) class MailerTest : KoinTest, AutoCloseKoinTest() { @InternalCoroutinesApi @ExperimentalCoroutinesApi diff --git a/src/test/kotlin/functional/NotificationConsumerTest.kt b/src/test/kotlin/functional/NotificationConsumerTest.kt index 85302ab..4d35704 100644 --- a/src/test/kotlin/functional/NotificationConsumerTest.kt +++ b/src/test/kotlin/functional/NotificationConsumerTest.kt @@ -33,7 +33,7 @@ import org.junit.jupiter.api.TestInstance import org.slf4j.LoggerFactory @TestInstance(TestInstance.Lifecycle.PER_METHOD) -@Tags(Tag("functional")) +@Tags(Tag("functional"), Tag("notification")) class NotificationConsumerTest { companion object { @BeforeAll diff --git a/src/test/kotlin/functional/NotificationsPushTest.kt b/src/test/kotlin/functional/NotificationsPushTest.kt index 9c266d7..cedbaf6 100644 --- a/src/test/kotlin/functional/NotificationsPushTest.kt +++ b/src/test/kotlin/functional/NotificationsPushTest.kt @@ -24,7 +24,7 @@ import org.junit.jupiter.api.Tags import org.junit.jupiter.api.Test import kotlin.test.assertEquals -@Tags(Tag("functional")) +@Tags(Tag("functional"), Tag("notification")) internal class NotificationsPushTest { companion object { @BeforeAll diff --git a/src/test/kotlin/functional/ResourcesKtTest.kt b/src/test/kotlin/functional/ResourcesKtTest.kt index 34f3ff4..9f79a1b 100644 --- a/src/test/kotlin/functional/ResourcesKtTest.kt +++ b/src/test/kotlin/functional/ResourcesKtTest.kt @@ -8,7 +8,7 @@ import org.junit.jupiter.api.TestInstance import kotlin.test.assertEquals @TestInstance(TestInstance.Lifecycle.PER_CLASS) -@Tags(Tag("functional")) +@Tags(Tag("functional"), Tag("utils")) class ResourcesKtTest { @Test fun readResource() { diff --git a/src/test/kotlin/functional/ViewTest.kt b/src/test/kotlin/functional/ViewTest.kt index 2d0de49..d8a74d7 100644 --- a/src/test/kotlin/functional/ViewTest.kt +++ b/src/test/kotlin/functional/ViewTest.kt @@ -25,7 +25,7 @@ import java.util.UUID @KtorExperimentalAPI @ExperimentalCoroutinesApi @TestInstance(PER_CLASS) -@Tags(Tag("functional")) +@Tags(Tag("functional"), Tag("view")) class ViewTest { @Test fun `test View Article`() { diff --git a/src/test/kotlin/integration/Opinion routes.kt b/src/test/kotlin/integration/Opinion routes.kt index 6abc93c..ec8c7dd 100644 --- a/src/test/kotlin/integration/Opinion routes.kt +++ b/src/test/kotlin/integration/Opinion routes.kt @@ -21,7 +21,7 @@ import org.junit.jupiter.api.Test import org.junit.jupiter.api.TestInstance @TestInstance(TestInstance.Lifecycle.PER_CLASS) -@Tags(Tag("integration"), Tag("article"), Tag("opinion")) +@Tags(Tag("integration"), Tag("opinion")) class `Opinion routes` : BaseTest() { @Test fun `I can get all opinion choices`() { @@ -48,6 +48,7 @@ class `Opinion routes` : BaseTest() { } @Test + @Tag("article") fun `I can create opinion on article`() { withIntegrationApplication { `Given I have citizen`("Isaac", "Newton", id = "2f414045-95d9-42ca-a3a9-8cdde52ad253") @@ -89,6 +90,7 @@ class `Opinion routes` : BaseTest() { } @Test + @Tag("article") fun `I can receive opinion aggregation with article`() { withIntegrationApplication { `Given I have an opinion choice`("Opinion6") @@ -120,6 +122,7 @@ class `Opinion routes` : BaseTest() { } @Test + @Tag("article") fun `I can get all my opinion of one article`() { withIntegrationApplication { `Given I have citizen`("Albert", "Einstein", id = "c1542096-3431-432d-8e35-9dc071d4c818") diff --git a/src/test/kotlin/unit/security/Article Access Control.kt b/src/test/kotlin/unit/security/Article Access Control.kt index c5474d1..bddb1d0 100644 --- a/src/test/kotlin/unit/security/Article Access Control.kt +++ b/src/test/kotlin/unit/security/Article Access Control.kt @@ -27,7 +27,7 @@ import fr.dcproject.component.article.database.ArticleRepository as ArticleRepo @TestInstance(TestInstance.Lifecycle.PER_CLASS) @Execution(CONCURRENT) -@Tags(Tag("security"), Tag("unit")) +@Tags(Tag("security"), Tag("unit"), Tag("article")) internal class `Article Access Control` { private val tesla = CitizenCreator( id = UUID.fromString("e6efc288-4283-4729-a268-6debb18de1a0"), diff --git a/src/test/kotlin/unit/security/Citizen Access Control.kt b/src/test/kotlin/unit/security/Citizen Access Control.kt index f735b6f..153356c 100644 --- a/src/test/kotlin/unit/security/Citizen Access Control.kt +++ b/src/test/kotlin/unit/security/Citizen Access Control.kt @@ -18,7 +18,7 @@ import org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT @TestInstance(TestInstance.Lifecycle.PER_CLASS) @Execution(CONCURRENT) -@Tags(Tag("security"), Tag("unit")) +@Tags(Tag("security"), Tag("unit"), Tag("citizen")) internal class `Citizen Access Control` { private val tesla = CitizenCart( user = User( diff --git a/src/test/kotlin/unit/security/Comment Access Control.kt b/src/test/kotlin/unit/security/Comment Access Control.kt index 89c2034..e2d9260 100644 --- a/src/test/kotlin/unit/security/Comment Access Control.kt +++ b/src/test/kotlin/unit/security/Comment Access Control.kt @@ -25,7 +25,7 @@ import java.util.UUID @TestInstance(TestInstance.Lifecycle.PER_CLASS) @Execution(CONCURRENT) -@Tags(Tag("security"), Tag("unit")) +@Tags(Tag("security"), Tag("unit"), Tag("comment")) internal class `Comment Access Control` { private val tesla = Citizen( user = User( diff --git a/src/test/kotlin/unit/security/Follow Access Control.kt b/src/test/kotlin/unit/security/Follow Access Control.kt index d57152a..c093a7e 100644 --- a/src/test/kotlin/unit/security/Follow Access Control.kt +++ b/src/test/kotlin/unit/security/Follow Access Control.kt @@ -23,7 +23,7 @@ import java.util.UUID @TestInstance(TestInstance.Lifecycle.PER_CLASS) @Execution(CONCURRENT) -@Tags(Tag("security"), Tag("unit")) +@Tags(Tag("security"), Tag("unit"), Tag("follow")) internal class `Follow Access Control` { private val tesla = CitizenCreator( user = UserCreator( diff --git a/src/test/kotlin/unit/security/Opinion Access Control.kt b/src/test/kotlin/unit/security/Opinion Access Control.kt index 8bdd07a..802cdaa 100644 --- a/src/test/kotlin/unit/security/Opinion Access Control.kt +++ b/src/test/kotlin/unit/security/Opinion Access Control.kt @@ -21,7 +21,7 @@ import java.util.UUID @TestInstance(TestInstance.Lifecycle.PER_CLASS) @Execution(CONCURRENT) -@Tags(Tag("security"), Tag("unit")) +@Tags(Tag("security"), Tag("unit"), Tag("opinion")) internal class `Opinion Access Control` { private val tesla = CitizenCreator( user = UserCreator( diff --git a/src/test/kotlin/unit/security/OpinionChoice Access Control.kt b/src/test/kotlin/unit/security/OpinionChoice Access Control.kt index 5c1b78a..223557b 100644 --- a/src/test/kotlin/unit/security/OpinionChoice Access Control.kt +++ b/src/test/kotlin/unit/security/OpinionChoice Access Control.kt @@ -15,7 +15,7 @@ import java.util.UUID @TestInstance(TestInstance.Lifecycle.PER_CLASS) @Execution(CONCURRENT) -@Tags(Tag("security"), Tag("unit")) +@Tags(Tag("security"), Tag("unit"), Tag("opinion")) internal class `OpinionChoice Access Control` { private val tesla = CitizenRef( id = UUID.fromString("e6efc288-4283-4729-a268-6debb18de1a0"), diff --git a/src/test/kotlin/unit/security/Vote Access Control.kt b/src/test/kotlin/unit/security/Vote Access Control.kt index 51dfa6c..e10f69a 100644 --- a/src/test/kotlin/unit/security/Vote Access Control.kt +++ b/src/test/kotlin/unit/security/Vote Access Control.kt @@ -24,7 +24,7 @@ import java.util.UUID @TestInstance(TestInstance.Lifecycle.PER_CLASS) @Execution(CONCURRENT) -@Tags(Tag("security"), Tag("unit")) +@Tags(Tag("security"), Tag("unit"), Tag("vote")) internal class `Vote Access Control` { private val tesla = Citizen( id = UUID.fromString("a1e35c99-9d33-4fb4-9201-58d7071243bb"), diff --git a/src/test/kotlin/unit/security/Workgroup Access Control.kt b/src/test/kotlin/unit/security/Workgroup Access Control.kt index aedc0ae..a240429 100644 --- a/src/test/kotlin/unit/security/Workgroup Access Control.kt +++ b/src/test/kotlin/unit/security/Workgroup Access Control.kt @@ -20,7 +20,7 @@ import fr.dcproject.component.workgroup.database.WorkgroupForView as WorkgroupEn @TestInstance(TestInstance.Lifecycle.PER_CLASS) @Execution(CONCURRENT) -@Tags(Tag("security"), Tag("unit")) +@Tags(Tag("security"), Tag("unit"), Tag("workgroup")) internal class `Workgroup Access Control` { private val tesla = CitizenCreator( user = UserCreator( -- 2.49.1 From 2bb90ced032d7c0ae19bb7b034517431de2a0b01 Mon Sep 17 00:00:00 2001 From: Fabrice Lecomte Date: Sat, 3 Apr 2021 00:17:29 +0200 Subject: [PATCH 3/3] Refactor 'the response should contain list' --- src/test/kotlin/assert/Range.kt | 7 +++++++ src/test/kotlin/integration/Article routes.kt | 2 +- .../integration/Comment constitutions routes.kt | 2 +- src/test/kotlin/integration/Opinion routes.kt | 2 +- src/test/kotlin/integration/Workgroup routes.kt | 6 +++--- src/test/kotlin/integration/steps/then/request.kt | 15 ++++++--------- 6 files changed, 19 insertions(+), 15 deletions(-) create mode 100644 src/test/kotlin/assert/Range.kt diff --git a/src/test/kotlin/assert/Range.kt b/src/test/kotlin/assert/Range.kt new file mode 100644 index 0000000..a388716 --- /dev/null +++ b/src/test/kotlin/assert/Range.kt @@ -0,0 +1,7 @@ +package assert + +import kotlin.test.assertTrue + +infix fun IntProgression.assertContain(expected: Int) { + assertTrue(this.contains(expected), "Expected $this less than $expected") +} diff --git a/src/test/kotlin/integration/Article routes.kt b/src/test/kotlin/integration/Article routes.kt index 661e4f9..0ed6685 100644 --- a/src/test/kotlin/integration/Article routes.kt +++ b/src/test/kotlin/integration/Article routes.kt @@ -38,7 +38,7 @@ class `Article routes` : BaseTest() { `And the response should contain pattern`("$.result[1].createdBy.name.firstName", "firstName.+") `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 the response should contain list`("$.result", 3) } } } diff --git a/src/test/kotlin/integration/Comment constitutions routes.kt b/src/test/kotlin/integration/Comment constitutions routes.kt index f887ce1..a999082 100644 --- a/src/test/kotlin/integration/Comment constitutions routes.kt +++ b/src/test/kotlin/integration/Comment constitutions routes.kt @@ -57,7 +57,7 @@ class `Comment constitutions routes` : BaseTest() { `And the response should contain`("$.limit", 50) `And the response should contain`("$.result[0].createdBy.id", "46e0bda9-ca6a-4c65-a58b-7e7267a0bbc5") `And the response should contain`("$.result[0].target.id", "34ddd50a-da00-4a90-a869-08baa2a121be") - `And the response should contain list`("$.result[*]", 1, 1) + `And the response should contain list`("$.result[*]", 1) } } } diff --git a/src/test/kotlin/integration/Opinion routes.kt b/src/test/kotlin/integration/Opinion routes.kt index ec8c7dd..4613372 100644 --- a/src/test/kotlin/integration/Opinion routes.kt +++ b/src/test/kotlin/integration/Opinion routes.kt @@ -137,7 +137,7 @@ class `Opinion routes` : BaseTest() { `authenticated as`("Albert", "Einstein") } `Then the response should be` OK and { `And the response should contain`("$.result[0].name", "Opinion9") - `And the response should contain list`("$.result[*]", 1, 1) + `And the response should contain list`("$.result[*]", 1) } } } diff --git a/src/test/kotlin/integration/Workgroup routes.kt b/src/test/kotlin/integration/Workgroup routes.kt index 0df0a7f..32b69d7 100644 --- a/src/test/kotlin/integration/Workgroup routes.kt +++ b/src/test/kotlin/integration/Workgroup routes.kt @@ -119,7 +119,7 @@ class `Workgroup routes` : BaseTest() { `And the response should contain`("$.description", "Une petite souris") `And have property`("$.members") - `And the response should contain list`("$.members", 3, 3) + `And the response should contain list`("$.members", 3) `And the response should contain`("$.members.[1]citizen.id", "94f92424-c257-4582-907c-98564a8c4ac9") `And the response should contain`("$.members.[2]citizen.id", "87909ba3-2069-431c-9924-219fd8411cf2") } @@ -215,7 +215,7 @@ class `Workgroup routes` : BaseTest() { ] """ } `Then the response should be` OK and { - `And the response should contain list`("$", 2, 2) + `And the response should contain list`("$", 2) `And the response should contain`("$.[0]citizen.id", "94f92424-c257-4582-907c-98564a8c4ac9") `And the response should contain`("$.[1]citizen.id", "1baf48bb-02bc-4d8f-ac86-33335354f5e7") } @@ -252,7 +252,7 @@ class `Workgroup routes` : BaseTest() { """ ) } `Then the response should be` OK and { - `And the response should contain list`("$", 2, 2) + `And the response should contain list`("$", 2) `And the response should contain`("$.[0]citizen.id", "be3b0926-8628-4426-804a-75188a6eb315") `And the response should contain`("$.[1]citizen.id", "b49e20c1-8393-45d6-a6a0-3fa5c71cbdc1") } diff --git a/src/test/kotlin/integration/steps/then/request.kt b/src/test/kotlin/integration/steps/then/request.kt index c4ed68a..cc5156d 100644 --- a/src/test/kotlin/integration/steps/then/request.kt +++ b/src/test/kotlin/integration/steps/then/request.kt @@ -1,7 +1,6 @@ package integration.steps.then -import assert.assertGreaterThan -import assert.assertLessThan +import assert.assertContain import com.jayway.jsonpath.JsonPath import com.jayway.jsonpath.PathNotFoundException import io.ktor.http.HttpStatusCode @@ -85,15 +84,13 @@ fun TestApplicationResponse.`And the response should contain pattern`(path: Stri } } -fun TestApplicationResponse.`And the response should contain list`(path: String, min: Int? = null, max: Int? = null) { +fun TestApplicationResponse.`And the response should contain list`(path: String, exactCount: Int) = + `And the response should contain list`(path, IntRange(exactCount, exactCount)) + +fun TestApplicationResponse.`And the response should contain list`(path: String, range: IntRange) { JsonPath.read(content, path).also { assertNotNull(it) - if (min != null) { - it.size assertGreaterThan min - } - if (max != null) { - it.size assertLessThan max - } + range assertContain it.size } } -- 2.49.1