Add Integration test for citizen

This commit is contained in:
2021-02-10 00:58:42 +01:00
parent 55bfbb619d
commit ec0115d613
8 changed files with 268 additions and 76 deletions

View File

@@ -7,22 +7,13 @@ import fr.dcproject.application.Env.TEST
import fr.dcproject.application.module import fr.dcproject.application.module
import fr.postgresjson.connexion.Connection import fr.postgresjson.connexion.Connection
import fr.postgresjson.migration.Migrations import fr.postgresjson.migration.Migrations
import io.ktor.http.ContentType
import io.ktor.http.HttpHeaders
import io.ktor.http.HttpMethod
import io.ktor.http.HttpStatusCode
import io.ktor.locations.KtorExperimentalLocationsAPI import io.ktor.locations.KtorExperimentalLocationsAPI
import io.ktor.server.testing.TestApplicationCall
import io.ktor.server.testing.TestApplicationEngine import io.ktor.server.testing.TestApplicationEngine
import io.ktor.server.testing.TestApplicationRequest
import io.ktor.server.testing.TestApplicationResponse
import io.ktor.server.testing.createTestEnvironment import io.ktor.server.testing.createTestEnvironment
import io.ktor.server.testing.setBody
import io.ktor.util.KtorExperimentalAPI import io.ktor.util.KtorExperimentalAPI
import io.lettuce.core.RedisClient import io.lettuce.core.RedisClient
import io.lettuce.core.api.sync.RedisCommands import io.lettuce.core.api.sync.RedisCommands
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import org.amshove.kluent.`should be`
import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.BeforeAll import org.junit.jupiter.api.BeforeAll
import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.BeforeEach
@@ -50,36 +41,6 @@ abstract class BaseTest : KoinTest {
return engine.test() return engine.test()
} }
public fun TestApplicationEngine.`I send a GET request`(uri: String? = null, setup: (TestApplicationRequest.() -> Unit)? = null): TestApplicationCall {
val setupOveride: TestApplicationRequest.() -> Unit = {
method = HttpMethod.Get
if (uri != null) {
this.uri = uri
}
addHeader(HttpHeaders.ContentType, ContentType.Application.Json.toString())
setup?.let { it() }
}
return handleRequest(true, setupOveride)
}
public fun TestApplicationEngine.`I send a POST request`(uri: String? = null, setup: (TestApplicationRequest.() -> String?)? = null): TestApplicationCall {
val setupOveride: TestApplicationRequest.() -> Unit = {
method = HttpMethod.Post
if (uri != null) {
this.uri = uri
}
addHeader(HttpHeaders.ContentType, ContentType.Application.Json.toString())
setup?.let { it() }?.let {
setBody(it.trimIndent())
}
}
return handleRequest(true, setupOveride)
}
fun TestApplicationRequest.`with body`(body: String) {
setBody(body.trimIndent())
}
@BeforeAll @BeforeAll
fun before() { fun before() {
if (init == false) { if (init == false) {
@@ -114,13 +75,3 @@ abstract class BaseTest : KoinTest {
.sendQuery("rollback to savepoint test_begin;", listOf()) .sendQuery("rollback to savepoint test_begin;", listOf())
} }
} }
fun TestApplicationCall.`Then the response should be`(status: HttpStatusCode? = null, block: TestApplicationResponse.() -> Unit): TestApplicationCall {
if (status != null) {
response.status().`should be`(status)
}
block(response)
return this
}

View File

@@ -0,0 +1,53 @@
package integration.asserts
import com.jayway.jsonpath.JsonPath
import io.ktor.http.HttpStatusCode
import io.ktor.server.testing.TestApplicationCall
import io.ktor.server.testing.TestApplicationResponse
import org.amshove.kluent.`should be equal to`
import org.amshove.kluent.`should be`
import org.amshove.kluent.`should not be null`
import org.amshove.kluent.shouldContain
import kotlin.test.assertEquals
fun TestApplicationCall.`Then the response should be`(status: HttpStatusCode? = null, block: TestApplicationResponse.() -> Unit): TestApplicationCall = this.apply {
if (status != null) {
response.status().`should be`(status)
}
block(response)
}
infix fun TestApplicationCall.`Then the response should be`(status: HttpStatusCode): TestApplicationCall = this.apply {
response.status().`should be`(status)
}
infix fun TestApplicationCall.and(block: TestApplicationResponse.() -> Unit): TestApplicationCall = this.apply {
block(response)
}
infix fun TestApplicationCall.`has property`(path: String): Pair<JsonPath, Any> =
JsonPath.compile(path).let { jsonPath ->
jsonPath.read<Any>(response.content)?.let { result ->
Pair(jsonPath, result)
} ?: throw AssertionError("\"${path}\" element not found on json response")
}
infix fun TestApplicationResponse.`And have property`(path: String): Pair<JsonPath, Any> =
JsonPath.compile(path).let { jsonPath ->
jsonPath.read<Any>(content)?.let { result ->
Pair(jsonPath, result)
} ?: throw AssertionError("\"${path}\" element not found on json response")
}
infix fun Pair<JsonPath, Any>.`whish contains`(expected: Any): Pair<JsonPath, Any> = this.apply {
expected `should be equal to` second
}
fun TestApplicationResponse.`And the response should contain`(path: String, valueExpected: String) {
assertEquals(valueExpected, JsonPath.read<Any>(content, path)?.toString() ?: throw AssertionError("\"$path -> ${valueExpected}\" element not found on json response"))
}
val TestApplicationResponse.`And the response should not be null` get() = content.`should not be null`()
infix fun String.`and should contains`(expected: String) = this
.`should not be null`()
.shouldContain(expected)

View File

@@ -0,0 +1,26 @@
package integration.asserts.given
import com.auth0.jwt.JWT
import fr.dcproject.component.auth.jwt.JwtConfig
import fr.dcproject.component.citizen.Citizen
import fr.dcproject.component.citizen.CitizenRepository
import io.ktor.http.HttpHeaders
import io.ktor.server.testing.TestApplicationRequest
import org.koin.core.context.GlobalContext
fun TestApplicationRequest.`authenticated as`(
firstName: String,
lastName: String,
): Citizen {
val username = "$firstName-$lastName".toLowerCase()
val repo: CitizenRepository by lazy<CitizenRepository> { GlobalContext.get().koin.get() }
val citizen = repo.findByUsername(username) ?: error("Cititzen not exist with username $username")
val jwtAsString: String = JWT.create()
.withIssuer("dc-project.fr")
.withClaim("id", citizen.user.id.toString())
.sign(JwtConfig.algorithm)
addHeader(HttpHeaders.Authorization, "Bearer $jwtAsString")
return citizen
}

View File

@@ -1,38 +1,31 @@
package integration.prerequisite package integration.asserts.given
import com.auth0.jwt.JWT import fr.dcproject.common.utils.toUUID
import fr.dcproject.component.auth.UserForCreate import fr.dcproject.component.auth.UserForCreate
import fr.dcproject.component.auth.jwt.JwtConfig
import fr.dcproject.component.citizen.Citizen import fr.dcproject.component.citizen.Citizen
import fr.dcproject.component.citizen.CitizenForCreate import fr.dcproject.component.citizen.CitizenForCreate
import fr.dcproject.component.citizen.CitizenI import fr.dcproject.component.citizen.CitizenI
import fr.dcproject.component.citizen.CitizenRepository import fr.dcproject.component.citizen.CitizenRepository
import io.ktor.http.HttpHeaders
import io.ktor.server.testing.TestApplicationEngine import io.ktor.server.testing.TestApplicationEngine
import io.ktor.server.testing.TestApplicationRequest
import org.joda.time.DateTime import org.joda.time.DateTime
import org.koin.core.KoinComponent
import org.koin.core.context.GlobalContext import org.koin.core.context.GlobalContext
import org.koin.core.get
import org.koin.test.get
import steps.KtorServerContext
import java.util.UUID import java.util.UUID
fun TestApplicationEngine.`Given I have citizen`( fun TestApplicationEngine.`Given I have citizen`(
firstName: String, firstName: String,
lastName: String, lastName: String,
email: String = ("$firstName-$lastName".toLowerCase()) + "@dc-project.fr", email: String = ("$firstName-$lastName".toLowerCase()) + "@dc-project.fr",
id: UUID = UUID.randomUUID() id: String = UUID.randomUUID().toString()
): Citizen? { ): Citizen? {
val repo: CitizenRepository by lazy<CitizenRepository> { GlobalContext.get().koin.get() } val repo: CitizenRepository by lazy<CitizenRepository> { GlobalContext.get().koin.get() }
val user = UserForCreate( val user = UserForCreate(
id = id, id = id.toUUID(),
username = "$firstName-$lastName".toLowerCase(), username = "$firstName-$lastName".toLowerCase(),
password = "azerty", password = "azerty",
) )
val citizen = CitizenForCreate( val citizen = CitizenForCreate(
id = id, id = id.toUUID(),
name = CitizenI.Name(firstName, lastName), name = CitizenI.Name(firstName, lastName),
email = email, email = email,
birthday = DateTime.now(), birthday = DateTime.now(),

View File

@@ -0,0 +1,53 @@
package integration.asserts.`when`
import io.ktor.http.ContentType
import io.ktor.http.HttpHeaders
import io.ktor.http.HttpMethod
import io.ktor.server.testing.TestApplicationCall
import io.ktor.server.testing.TestApplicationEngine
import io.ktor.server.testing.TestApplicationRequest
import io.ktor.server.testing.setBody
public fun TestApplicationEngine.`When I send a GET request`(uri: String? = null, setup: (TestApplicationRequest.() -> Unit)? = null): TestApplicationCall {
val setupOveride: TestApplicationRequest.() -> Unit = {
method = HttpMethod.Get
if (uri != null) {
this.uri = uri
}
addHeader(HttpHeaders.ContentType, ContentType.Application.Json.toString())
setup?.let { it() }
}
return handleRequest(true, setupOveride)
}
public fun TestApplicationEngine.`When I send a POST request`(uri: String? = null, setup: (TestApplicationRequest.() -> String?)? = null): TestApplicationCall {
val setupOveride: TestApplicationRequest.() -> Unit = {
method = HttpMethod.Post
if (uri != null) {
this.uri = uri
}
addHeader(HttpHeaders.ContentType, ContentType.Application.Json.toString())
setup?.let { it() }?.let {
setBody(it.trimIndent())
}
}
return handleRequest(true, setupOveride)
}
public fun TestApplicationEngine.`When I send a PUT request`(uri: String? = null, setup: (TestApplicationRequest.() -> String?)? = null): TestApplicationCall {
val setupOveride: TestApplicationRequest.() -> Unit = {
method = HttpMethod.Put
if (uri != null) {
this.uri = uri
}
addHeader(HttpHeaders.ContentType, ContentType.Application.Json.toString())
setup?.let { it() }?.let {
setBody(it.trimIndent())
}
}
return handleRequest(true, setupOveride)
}
fun TestApplicationRequest.`with body`(body: String) {
setBody(body.trimIndent())
}

View File

@@ -1,14 +1,17 @@
package integration.auth package integration.auth
import integration.BaseTest import integration.BaseTest
import integration.`Then the response should be` import integration.asserts.`And the response should not be null`
import integration.prerequisite.`Given I have citizen` import integration.asserts.`Then the response should be`
import integration.asserts.`and should contains`
import integration.asserts.`when`.`When I send a POST request`
import integration.asserts.given.`Given I have citizen`
import integration.asserts.given.`authenticated as`
import io.ktor.http.HttpStatusCode import io.ktor.http.HttpStatusCode
import io.ktor.http.HttpStatusCode.Companion.NoContent
import io.ktor.locations.KtorExperimentalLocationsAPI import io.ktor.locations.KtorExperimentalLocationsAPI
import io.ktor.util.KtorExperimentalAPI import io.ktor.util.KtorExperimentalAPI
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import org.amshove.kluent.`should contain`
import org.amshove.kluent.`should not be null`
import org.junit.jupiter.api.Tag import org.junit.jupiter.api.Tag
import org.junit.jupiter.api.Tags import org.junit.jupiter.api.Tags
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
@@ -24,7 +27,7 @@ class LoginTest : BaseTest() {
fun `I can login with username and password`() { fun `I can login with username and password`() {
withIntegrationApplication { withIntegrationApplication {
`Given I have citizen`("Niels", "Bohr") `Given I have citizen`("Niels", "Bohr")
`I send a POST request`("/login") { `When I send a POST request`("/login") {
""" """
{ {
"username": "niels-bohr", "username": "niels-bohr",
@@ -32,10 +35,24 @@ class LoginTest : BaseTest() {
} }
""" """
}.`Then the response should be`(HttpStatusCode.OK) { }.`Then the response should be`(HttpStatusCode.OK) {
content `And the response should not be null` `and should contains` "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9."
.`should not be null`()
.`should contain`("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.")
} }
} }
} }
@Test
fun `I can be connect with Passwordless auth`() {
withIntegrationApplication {
`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")
"""
{
"url": "https://dc-project.fr/password/reset",
"email": "fabrice.lecomte.be@gmail.com"
}
"""
} `Then the response should be` NoContent
}
}
} }

View File

@@ -1,10 +1,10 @@
package integration.auth package integration.auth
import integration.BaseTest import integration.BaseTest
import integration.`Then the response should be` import integration.asserts.`Then the response should be`
import integration.asserts.`when`.`When I send a POST request`
import io.ktor.http.HttpStatusCode import io.ktor.http.HttpStatusCode
import io.ktor.locations.KtorExperimentalLocationsAPI import io.ktor.locations.KtorExperimentalLocationsAPI
import io.ktor.server.testing.setBody
import io.ktor.util.KtorExperimentalAPI import io.ktor.util.KtorExperimentalAPI
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import org.amshove.kluent.`should be null` import org.amshove.kluent.`should be null`
@@ -26,7 +26,7 @@ class RegisterTest : BaseTest() {
@Category(RegisterTest::class) @Category(RegisterTest::class)
fun `I can register`() { fun `I can register`() {
withIntegrationApplication { withIntegrationApplication {
`I send a POST request`("/register") { `When I send a POST request`("/register") {
""" """
{ {
"name": {"first_name":"George", "last_name":"MICHEL"}, "name": {"first_name":"George", "last_name":"MICHEL"},
@@ -49,7 +49,7 @@ class RegisterTest : BaseTest() {
@Test @Test
fun `I cannot register if no username was sent`() { fun `I cannot register if no username was sent`() {
withIntegrationApplication { withIntegrationApplication {
`I send a POST request`("/register") { `When I send a POST request`("/register") {
""" """
{ {
"name": {"first_name":"George2", "last_name":"MICHEL2"}, "name": {"first_name":"George2", "last_name":"MICHEL2"},

View File

@@ -0,0 +1,99 @@
package integration.citizen
import integration.BaseTest
import integration.asserts.`And have property`
import integration.asserts.`And the response should not be null`
import integration.asserts.`Then the response should be`
import integration.asserts.`when`.`When I send a GET request`
import integration.asserts.`when`.`When I send a PUT request`
import integration.asserts.`whish contains`
import integration.asserts.and
import integration.asserts.given.`Given I have citizen`
import integration.asserts.given.`authenticated as`
import io.ktor.http.HttpStatusCode.Companion.BadRequest
import io.ktor.http.HttpStatusCode.Companion.Created
import io.ktor.http.HttpStatusCode.Companion.OK
import io.ktor.locations.KtorExperimentalLocationsAPI
import io.ktor.util.KtorExperimentalAPI
import kotlinx.coroutines.ExperimentalCoroutinesApi
import org.junit.jupiter.api.Tag
import org.junit.jupiter.api.Tags
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
@ExperimentalCoroutinesApi
@KtorExperimentalLocationsAPI
@KtorExperimentalAPI
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@Tags(Tag("integration"), Tag("citizen"))
class CitizenTest : BaseTest() {
@Test
fun `I can get Citizens informations`() {
withIntegrationApplication {
`Given I have citizen`("Jean", "Perrin", id = "5267a5c6-af42-4a02-aa2b-6b71d2e43973")
`When I send a GET request`("/citizens") {
`authenticated as`("Jean", "Perrin")
} `Then the response should be` OK and {
`And the response should not be null`
}
}
}
@Test
fun `I can get specific Citizen informations`() {
withIntegrationApplication {
`Given I have citizen`("Linus", "Pauling", id = "47a05c0f-7329-46c3-a7d0-325db37e9114")
`When I send a GET request`("/citizens/47a05c0f-7329-46c3-a7d0-325db37e9114") {
`authenticated as`("Linus", "Pauling")
} `Then the response should be` OK and {
`And the response should not be null`
`And have property`("$.id") `whish contains` "47a05c0f-7329-46c3-a7d0-325db37e9114"
}
}
}
@Test
fun `I can get my citizen informations when I was connected`() {
withIntegrationApplication {
`Given I have citizen`("Henri", "Becquerel", id = "47356809-c8ef-4649-8b99-1c5cb9886d38")
`When I send a GET request`("/citizens/current") {
`authenticated as`("Henri", "Becquerel")
} `Then the response should be` OK and {
`And the response should not be null`
`And have property`("$.id") `whish contains` "47356809-c8ef-4649-8b99-1c5cb9886d38"
}
}
}
@Test
fun `I can change my password`() {
withIntegrationApplication {
`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")
"""
{
"old_password": "azerty",
"new_password": "qwerty"
}
"""
} `Then the response should be` Created
}
}
@Test
fun `I cannot change my password if request is bad formated`() {
withIntegrationApplication {
`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")
"""
{
"plup": "azerty",
"gloup": "qwerty"
}
"""
} `Then the response should be` BadRequest
}
}
}