diff --git a/src/test/kotlin/integration/BaseTest.kt b/src/test/kotlin/integration/BaseTest.kt index 678c63d..017e00d 100644 --- a/src/test/kotlin/integration/BaseTest.kt +++ b/src/test/kotlin/integration/BaseTest.kt @@ -7,22 +7,13 @@ import fr.dcproject.application.Env.TEST import fr.dcproject.application.module import fr.postgresjson.connexion.Connection 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.server.testing.TestApplicationCall 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.setBody import io.ktor.util.KtorExperimentalAPI import io.lettuce.core.RedisClient import io.lettuce.core.api.sync.RedisCommands import kotlinx.coroutines.ExperimentalCoroutinesApi -import org.amshove.kluent.`should be` import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.BeforeAll import org.junit.jupiter.api.BeforeEach @@ -50,36 +41,6 @@ abstract class BaseTest : KoinTest { 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 fun before() { if (init == false) { @@ -114,13 +75,3 @@ abstract class BaseTest : KoinTest { .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 -} \ No newline at end of file diff --git a/src/test/kotlin/integration/asserts/asserts.kt b/src/test/kotlin/integration/asserts/asserts.kt new file mode 100644 index 0000000..cd38ea0 --- /dev/null +++ b/src/test/kotlin/integration/asserts/asserts.kt @@ -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.compile(path).let { jsonPath -> + jsonPath.read(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.compile(path).let { jsonPath -> + jsonPath.read(content)?.let { result -> + Pair(jsonPath, result) + } ?: throw AssertionError("\"${path}\" element not found on json response") + } + +infix fun Pair.`whish contains`(expected: Any): Pair = this.apply { + expected `should be equal to` second +} + +fun TestApplicationResponse.`And the response should contain`(path: String, valueExpected: String) { + assertEquals(valueExpected, JsonPath.read(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) diff --git a/src/test/kotlin/integration/asserts/given/Auth.kt b/src/test/kotlin/integration/asserts/given/Auth.kt new file mode 100644 index 0000000..1ed99dd --- /dev/null +++ b/src/test/kotlin/integration/asserts/given/Auth.kt @@ -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 { 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 +} diff --git a/src/test/kotlin/integration/prerequisite/CitizenPrerequisite.kt b/src/test/kotlin/integration/asserts/given/Citizen.kt similarity index 73% rename from src/test/kotlin/integration/prerequisite/CitizenPrerequisite.kt rename to src/test/kotlin/integration/asserts/given/Citizen.kt index bc30c3e..6ee77f2 100644 --- a/src/test/kotlin/integration/prerequisite/CitizenPrerequisite.kt +++ b/src/test/kotlin/integration/asserts/given/Citizen.kt @@ -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.jwt.JwtConfig import fr.dcproject.component.citizen.Citizen import fr.dcproject.component.citizen.CitizenForCreate import fr.dcproject.component.citizen.CitizenI import fr.dcproject.component.citizen.CitizenRepository -import io.ktor.http.HttpHeaders import io.ktor.server.testing.TestApplicationEngine -import io.ktor.server.testing.TestApplicationRequest import org.joda.time.DateTime -import org.koin.core.KoinComponent import org.koin.core.context.GlobalContext -import org.koin.core.get -import org.koin.test.get -import steps.KtorServerContext import java.util.UUID fun TestApplicationEngine.`Given I have citizen`( firstName: String, lastName: String, email: String = ("$firstName-$lastName".toLowerCase()) + "@dc-project.fr", - id: UUID = UUID.randomUUID() + id: String = UUID.randomUUID().toString() ): Citizen? { val repo: CitizenRepository by lazy { GlobalContext.get().koin.get() } val user = UserForCreate( - id = id, + id = id.toUUID(), username = "$firstName-$lastName".toLowerCase(), password = "azerty", ) val citizen = CitizenForCreate( - id = id, + id = id.toUUID(), name = CitizenI.Name(firstName, lastName), email = email, birthday = DateTime.now(), diff --git a/src/test/kotlin/integration/asserts/when/request.kt b/src/test/kotlin/integration/asserts/when/request.kt new file mode 100644 index 0000000..511ce59 --- /dev/null +++ b/src/test/kotlin/integration/asserts/when/request.kt @@ -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()) +} diff --git a/src/test/kotlin/integration/auth/LoginTest.kt b/src/test/kotlin/integration/auth/LoginTest.kt index d68a9c8..99e99a3 100644 --- a/src/test/kotlin/integration/auth/LoginTest.kt +++ b/src/test/kotlin/integration/auth/LoginTest.kt @@ -1,14 +1,17 @@ package integration.auth import integration.BaseTest -import integration.`Then the response should be` -import integration.prerequisite.`Given I have citizen` +import integration.asserts.`And the response should not be null` +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.Companion.NoContent import io.ktor.locations.KtorExperimentalLocationsAPI import io.ktor.util.KtorExperimentalAPI 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.Tags import org.junit.jupiter.api.Test @@ -24,18 +27,32 @@ class LoginTest : BaseTest() { fun `I can login with username and password`() { withIntegrationApplication { `Given I have citizen`("Niels", "Bohr") - `I send a POST request`("/login") { + `When I send a POST request`("/login") { """ { "username": "niels-bohr", "password": "azerty" } """ - }.`Then the response should be` (HttpStatusCode.OK) { - content - .`should not be null`() - .`should contain`("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.") + }.`Then the response should be`(HttpStatusCode.OK) { + `And the response should not be null` `and should contains` "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 + } + } } diff --git a/src/test/kotlin/integration/auth/RegisterTest.kt b/src/test/kotlin/integration/auth/RegisterTest.kt index 75b3f27..bb72617 100644 --- a/src/test/kotlin/integration/auth/RegisterTest.kt +++ b/src/test/kotlin/integration/auth/RegisterTest.kt @@ -1,10 +1,10 @@ package integration.auth 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.locations.KtorExperimentalLocationsAPI -import io.ktor.server.testing.setBody import io.ktor.util.KtorExperimentalAPI import kotlinx.coroutines.ExperimentalCoroutinesApi import org.amshove.kluent.`should be null` @@ -26,7 +26,7 @@ class RegisterTest : BaseTest() { @Category(RegisterTest::class) fun `I can register`() { withIntegrationApplication { - `I send a POST request`("/register") { + `When I send a POST request`("/register") { """ { "name": {"first_name":"George", "last_name":"MICHEL"}, @@ -38,7 +38,7 @@ class RegisterTest : BaseTest() { "email": "george-junior@gmail.com" } """ - }.`Then the response should be` (HttpStatusCode.OK) { + }.`Then the response should be`(HttpStatusCode.OK) { content .`should not be null`() .`should contain`("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.") @@ -49,7 +49,7 @@ class RegisterTest : BaseTest() { @Test fun `I cannot register if no username was sent`() { withIntegrationApplication { - `I send a POST request`("/register") { + `When I send a POST request`("/register") { """ { "name": {"first_name":"George2", "last_name":"MICHEL2"}, @@ -60,7 +60,7 @@ class RegisterTest : BaseTest() { } } """ - }.`Then the response should be` (HttpStatusCode.BadRequest) { + }.`Then the response should be`(HttpStatusCode.BadRequest) { content.`should be null`() } } diff --git a/src/test/kotlin/integration/citizen/CitizenTest.kt b/src/test/kotlin/integration/citizen/CitizenTest.kt new file mode 100644 index 0000000..a3d7673 --- /dev/null +++ b/src/test/kotlin/integration/citizen/CitizenTest.kt @@ -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 + } + } +}