Improve tests #90

Merged
flecomte merged 3 commits from improve-test into master 2021-04-03 00:39:17 +02:00
21 changed files with 52 additions and 32 deletions

View File

@@ -0,0 +1,4 @@
package fr.dcproject.common.utils
fun String.isInt(): Boolean = this.toIntOrNull() != null
fun String.isBool(): Boolean = this == "true" || this == "false"

View File

@@ -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")
}

View File

@@ -22,7 +22,7 @@ import org.koin.test.get
@KtorExperimentalLocationsAPI @KtorExperimentalLocationsAPI
@KtorExperimentalAPI @KtorExperimentalAPI
@TestInstance(TestInstance.Lifecycle.PER_CLASS) @TestInstance(TestInstance.Lifecycle.PER_CLASS)
@Tags(Tag("functional")) @Tags(Tag("functional"), Tag("mail"))
class MailerTest : KoinTest, AutoCloseKoinTest() { class MailerTest : KoinTest, AutoCloseKoinTest() {
@InternalCoroutinesApi @InternalCoroutinesApi
@ExperimentalCoroutinesApi @ExperimentalCoroutinesApi

View File

@@ -33,7 +33,7 @@ import org.junit.jupiter.api.TestInstance
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
@TestInstance(TestInstance.Lifecycle.PER_METHOD) @TestInstance(TestInstance.Lifecycle.PER_METHOD)
@Tags(Tag("functional")) @Tags(Tag("functional"), Tag("notification"))
class NotificationConsumerTest { class NotificationConsumerTest {
companion object { companion object {
@BeforeAll @BeforeAll

View File

@@ -24,7 +24,7 @@ import org.junit.jupiter.api.Tags
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals
@Tags(Tag("functional")) @Tags(Tag("functional"), Tag("notification"))
internal class NotificationsPushTest { internal class NotificationsPushTest {
companion object { companion object {
@BeforeAll @BeforeAll

View File

@@ -8,7 +8,7 @@ import org.junit.jupiter.api.TestInstance
import kotlin.test.assertEquals import kotlin.test.assertEquals
@TestInstance(TestInstance.Lifecycle.PER_CLASS) @TestInstance(TestInstance.Lifecycle.PER_CLASS)
@Tags(Tag("functional")) @Tags(Tag("functional"), Tag("utils"))
class ResourcesKtTest { class ResourcesKtTest {
@Test @Test
fun readResource() { fun readResource() {

View File

@@ -25,7 +25,7 @@ import java.util.UUID
@KtorExperimentalAPI @KtorExperimentalAPI
@ExperimentalCoroutinesApi @ExperimentalCoroutinesApi
@TestInstance(PER_CLASS) @TestInstance(PER_CLASS)
@Tags(Tag("functional")) @Tags(Tag("functional"), Tag("view"))
class ViewTest { class ViewTest {
@Test @Test
fun `test View Article`() { fun `test View Article`() {

View File

@@ -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[1].createdBy.name.firstName", "firstName.+")
`And the response should contain pattern`("$.result[2].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 not contain`("$.result[3]")
`And the response should contain list`("$.result", 3, 3) `And the response should contain list`("$.result", 3)
} }
} }
} }

View File

@@ -57,7 +57,7 @@ class `Comment constitutions routes` : BaseTest() {
`And the response should contain`("$.limit", 50) `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].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`("$.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)
} }
} }
} }

View File

@@ -21,7 +21,7 @@ import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance import org.junit.jupiter.api.TestInstance
@TestInstance(TestInstance.Lifecycle.PER_CLASS) @TestInstance(TestInstance.Lifecycle.PER_CLASS)
@Tags(Tag("integration"), Tag("article"), Tag("opinion")) @Tags(Tag("integration"), Tag("opinion"))
class `Opinion routes` : BaseTest() { class `Opinion routes` : BaseTest() {
@Test @Test
fun `I can get all opinion choices`() { fun `I can get all opinion choices`() {
@@ -48,6 +48,7 @@ class `Opinion routes` : BaseTest() {
} }
@Test @Test
@Tag("article")
fun `I can create opinion on article`() { fun `I can create opinion on article`() {
withIntegrationApplication { withIntegrationApplication {
`Given I have citizen`("Isaac", "Newton", id = "2f414045-95d9-42ca-a3a9-8cdde52ad253") `Given I have citizen`("Isaac", "Newton", id = "2f414045-95d9-42ca-a3a9-8cdde52ad253")
@@ -89,6 +90,7 @@ class `Opinion routes` : BaseTest() {
} }
@Test @Test
@Tag("article")
fun `I can receive opinion aggregation with article`() { fun `I can receive opinion aggregation with article`() {
withIntegrationApplication { withIntegrationApplication {
`Given I have an opinion choice`("Opinion6") `Given I have an opinion choice`("Opinion6")
@@ -120,6 +122,7 @@ class `Opinion routes` : BaseTest() {
} }
@Test @Test
@Tag("article")
fun `I can get all my opinion of one article`() { fun `I can get all my opinion of one article`() {
withIntegrationApplication { withIntegrationApplication {
`Given I have citizen`("Albert", "Einstein", id = "c1542096-3431-432d-8e35-9dc071d4c818") `Given I have citizen`("Albert", "Einstein", id = "c1542096-3431-432d-8e35-9dc071d4c818")
@@ -134,7 +137,7 @@ class `Opinion routes` : BaseTest() {
`authenticated as`("Albert", "Einstein") `authenticated as`("Albert", "Einstein")
} `Then the response should be` OK and { } `Then the response should be` OK and {
`And the response should contain`("$.result[0].name", "Opinion9") `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)
} }
} }
} }

View File

@@ -119,7 +119,7 @@ class `Workgroup routes` : BaseTest() {
`And the response should contain`("$.description", "Une petite souris") `And the response should contain`("$.description", "Une petite souris")
`And have property`("$.members") `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.[1]citizen.id", "94f92424-c257-4582-907c-98564a8c4ac9")
`And the response should contain`("$.members.[2]citizen.id", "87909ba3-2069-431c-9924-219fd8411cf2") `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 { } `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`("$.[0]citizen.id", "94f92424-c257-4582-907c-98564a8c4ac9")
`And the response should contain`("$.[1]citizen.id", "1baf48bb-02bc-4d8f-ac86-33335354f5e7") `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 { } `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`("$.[0]citizen.id", "be3b0926-8628-4426-804a-75188a6eb315")
`And the response should contain`("$.[1]citizen.id", "b49e20c1-8393-45d6-a6a0-3fa5c71cbdc1") `And the response should contain`("$.[1]citizen.id", "b49e20c1-8393-45d6-a6a0-3fa5c71cbdc1")
} }

View File

@@ -1,7 +1,6 @@
package integration.steps.then package integration.steps.then
import assert.assertGreaterThan import assert.assertContain
import assert.assertLessThan
import com.jayway.jsonpath.JsonPath import com.jayway.jsonpath.JsonPath
import com.jayway.jsonpath.PathNotFoundException import com.jayway.jsonpath.PathNotFoundException
import io.ktor.http.HttpStatusCode 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<JSONArray?>(content, path).also { JsonPath.read<JSONArray?>(content, path).also {
assertNotNull(it) assertNotNull(it)
if (min != null) { range assertContain it.size
it.size assertGreaterThan min
}
if (max != null) {
it.size assertLessThan max
}
} }
} }

View File

@@ -2,8 +2,12 @@ package integration.steps.then
import com.fasterxml.jackson.databind.JsonNode import com.fasterxml.jackson.databind.JsonNode
import com.fasterxml.jackson.databind.ObjectMapper 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 com.fasterxml.jackson.databind.node.TextNode
import fr.dcproject.common.utils.getResource 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.ContentType
import io.ktor.http.Url import io.ktor.http.Url
import io.ktor.request.contentType import io.ktor.request.contentType
@@ -55,15 +59,15 @@ fun TestApplicationResponse.`And the schema response body must be valid`(content
/* Validate Response */ /* Validate Response */
this.apply { this.apply {
val status = call.response.status() val status = call.response.status()
val httpMethod = call.request.httpMethod.value.toUpperCase()
val responseContent: JsonNode = if (content != null) val responseContent: JsonNode = if (content != null)
ObjectMapper().readTree(content) ObjectMapper().readTree(content)
else TextNode("") 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 val schema = response.getContentMediaType(contentType.toString())?.schema
if (content != null) { if (content != null) {
val httpMethod = call.request.httpMethod.value
schema?.validate(api, responseContent) schema?.validate(api, responseContent)
?: fail("""No Status "${status.value}" found with media type "$contentType" for "$httpMethod $uri".""") ?: 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 -> operation { api, uri ->
/* Validate Request URL */ /* Validate Request URL */
this.apply { this.apply {
val methodName = call.request.httpMethod.value.toUpperCase()
Url(call.request.uri).parameters.forEach { parameter: String, values: List<String> -> Url(call.request.uri).parameters.forEach { parameter: String, values: List<String> ->
val schema = getParametersIn(api.context, "query") val schema = getParametersIn(api.context, "query")
?.firstOrNull { it.name == parameter }?.schema ?.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") { if (schema.type == "array") {
schema.validate(api, ObjectMapper().valueToTree(values)) 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 { } else {
schema.validate(api, TextNode(values.first())) schema.validate(api, TextNode(values.first()))
} }

View File

@@ -27,7 +27,7 @@ import fr.dcproject.component.article.database.ArticleRepository as ArticleRepo
@TestInstance(TestInstance.Lifecycle.PER_CLASS) @TestInstance(TestInstance.Lifecycle.PER_CLASS)
@Execution(CONCURRENT) @Execution(CONCURRENT)
@Tags(Tag("security"), Tag("unit")) @Tags(Tag("security"), Tag("unit"), Tag("article"))
internal class `Article Access Control` { internal class `Article Access Control` {
private val tesla = CitizenCreator( private val tesla = CitizenCreator(
id = UUID.fromString("e6efc288-4283-4729-a268-6debb18de1a0"), id = UUID.fromString("e6efc288-4283-4729-a268-6debb18de1a0"),

View File

@@ -18,7 +18,7 @@ import org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT
@TestInstance(TestInstance.Lifecycle.PER_CLASS) @TestInstance(TestInstance.Lifecycle.PER_CLASS)
@Execution(CONCURRENT) @Execution(CONCURRENT)
@Tags(Tag("security"), Tag("unit")) @Tags(Tag("security"), Tag("unit"), Tag("citizen"))
internal class `Citizen Access Control` { internal class `Citizen Access Control` {
private val tesla = CitizenCart( private val tesla = CitizenCart(
user = User( user = User(

View File

@@ -25,7 +25,7 @@ import java.util.UUID
@TestInstance(TestInstance.Lifecycle.PER_CLASS) @TestInstance(TestInstance.Lifecycle.PER_CLASS)
@Execution(CONCURRENT) @Execution(CONCURRENT)
@Tags(Tag("security"), Tag("unit")) @Tags(Tag("security"), Tag("unit"), Tag("comment"))
internal class `Comment Access Control` { internal class `Comment Access Control` {
private val tesla = Citizen( private val tesla = Citizen(
user = User( user = User(

View File

@@ -23,7 +23,7 @@ import java.util.UUID
@TestInstance(TestInstance.Lifecycle.PER_CLASS) @TestInstance(TestInstance.Lifecycle.PER_CLASS)
@Execution(CONCURRENT) @Execution(CONCURRENT)
@Tags(Tag("security"), Tag("unit")) @Tags(Tag("security"), Tag("unit"), Tag("follow"))
internal class `Follow Access Control` { internal class `Follow Access Control` {
private val tesla = CitizenCreator( private val tesla = CitizenCreator(
user = UserCreator( user = UserCreator(

View File

@@ -21,7 +21,7 @@ import java.util.UUID
@TestInstance(TestInstance.Lifecycle.PER_CLASS) @TestInstance(TestInstance.Lifecycle.PER_CLASS)
@Execution(CONCURRENT) @Execution(CONCURRENT)
@Tags(Tag("security"), Tag("unit")) @Tags(Tag("security"), Tag("unit"), Tag("opinion"))
internal class `Opinion Access Control` { internal class `Opinion Access Control` {
private val tesla = CitizenCreator( private val tesla = CitizenCreator(
user = UserCreator( user = UserCreator(

View File

@@ -15,7 +15,7 @@ import java.util.UUID
@TestInstance(TestInstance.Lifecycle.PER_CLASS) @TestInstance(TestInstance.Lifecycle.PER_CLASS)
@Execution(CONCURRENT) @Execution(CONCURRENT)
@Tags(Tag("security"), Tag("unit")) @Tags(Tag("security"), Tag("unit"), Tag("opinion"))
internal class `OpinionChoice Access Control` { internal class `OpinionChoice Access Control` {
private val tesla = CitizenRef( private val tesla = CitizenRef(
id = UUID.fromString("e6efc288-4283-4729-a268-6debb18de1a0"), id = UUID.fromString("e6efc288-4283-4729-a268-6debb18de1a0"),

View File

@@ -24,7 +24,7 @@ import java.util.UUID
@TestInstance(TestInstance.Lifecycle.PER_CLASS) @TestInstance(TestInstance.Lifecycle.PER_CLASS)
@Execution(CONCURRENT) @Execution(CONCURRENT)
@Tags(Tag("security"), Tag("unit")) @Tags(Tag("security"), Tag("unit"), Tag("vote"))
internal class `Vote Access Control` { internal class `Vote Access Control` {
private val tesla = Citizen( private val tesla = Citizen(
id = UUID.fromString("a1e35c99-9d33-4fb4-9201-58d7071243bb"), id = UUID.fromString("a1e35c99-9d33-4fb4-9201-58d7071243bb"),

View File

@@ -20,7 +20,7 @@ import fr.dcproject.component.workgroup.database.WorkgroupForView as WorkgroupEn
@TestInstance(TestInstance.Lifecycle.PER_CLASS) @TestInstance(TestInstance.Lifecycle.PER_CLASS)
@Execution(CONCURRENT) @Execution(CONCURRENT)
@Tags(Tag("security"), Tag("unit")) @Tags(Tag("security"), Tag("unit"), Tag("workgroup"))
internal class `Workgroup Access Control` { internal class `Workgroup Access Control` {
private val tesla = CitizenCreator( private val tesla = CitizenCreator(
user = UserCreator( user = UserCreator(