Refactoring of cucumber implementation

This commit is contained in:
2019-08-23 13:04:20 +02:00
parent 46885ac599
commit 9b6f3aab88
14 changed files with 259 additions and 189 deletions

View File

@@ -1,25 +1,21 @@
import cucumber.api.CucumberOptions
import cucumber.api.Scenario
import cucumber.api.java8.En
import cucumber.api.junit.Cucumber
import feature.Context
import feature.KtorServerContext
import fr.dcproject.config
import fr.dcproject.module
import fr.dcproject.utils.LoggerDelegate
import fr.postgresjson.connexion.Connection
import fr.postgresjson.connexion.Requester
import fr.postgresjson.migration.Migrations
import io.cucumber.core.api.Scenario
import io.cucumber.java8.En
import io.cucumber.junit.Cucumber
import io.cucumber.junit.CucumberOptions
import io.ktor.locations.KtorExperimentalLocationsAPI
import io.ktor.server.testing.TestApplicationEngine
import io.ktor.server.testing.createTestEnvironment
import io.ktor.server.testing.withTestApplication
import io.ktor.util.KtorExperimentalAPI
import org.junit.runner.RunWith
import org.koin.test.KoinTest
import org.koin.test.get
import org.slf4j.Logger
import java.util.concurrent.TimeUnit
import feature.Context.Companion.current as contextCurrent
var unitialized: Boolean = false
@@ -29,6 +25,11 @@ var unitialized: Boolean = false
@CucumberOptions(plugin = ["pretty"])
class RunCucumberTest: En, KoinTest {
private val logger: Logger? by LoggerDelegate()
val ktorContext = KtorServerContext {
module()
}
init {
Before(-2) { _: Scenario ->
if (!unitialized) {
@@ -48,11 +49,11 @@ class RunCucumberTest: En, KoinTest {
config.database = "test"
config.username = "test"
config.password = "test"
contextCurrent = Context(TestApplicationEngine(createTestEnvironment()) {}, scenario)
ktorContext.start()
}
After { _: Scenario ->
contextCurrent.engine.stop(0L, 0L, TimeUnit.MILLISECONDS)
ktorContext.stop()
}
}

View File

@@ -1,44 +0,0 @@
package feature
import cucumber.api.Scenario
import fr.dcproject.module
import io.ktor.application.Application
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.util.KtorExperimentalAPI
@KtorExperimentalLocationsAPI
@KtorExperimentalAPI
class Context(
val engine: TestApplicationEngine,
val scenario: Scenario
) {
companion object {
lateinit var current: Context
}
init {
engine.start()
val moduleFunction: Application.() -> Unit = { module() }
val test: TestApplicationEngine.() -> Unit = {
moduleFunction(application)
}
engine.test()
}
var call: TestApplicationCall? = null
private val requestContextConfigurations: MutableList<TestApplicationRequest.() -> Unit> = mutableListOf()
fun setupRequest(testApplicationRequest: TestApplicationRequest) {
requestContextConfigurations.forEach {
it(testApplicationRequest)
}
}
fun setupNextRequests(requestContextConfiguration: TestApplicationRequest.() -> Unit) = requestContextConfigurations.add(requestContextConfiguration)
}
fun TestApplicationRequest.applyConfigurations() {
Context.current.setupRequest(this)
}

View File

@@ -0,0 +1,56 @@
package feature
import com.auth0.jwt.JWT
import com.auth0.jwt.algorithms.Algorithm
import fr.dcproject.JwtConfig
import fr.dcproject.entity.Citizen
import fr.dcproject.entity.User
import fr.postgresjson.connexion.Requester
import io.cucumber.datatable.DataTable
import io.cucumber.java8.En
import io.ktor.http.HttpHeaders
import org.joda.time.DateTime
import org.koin.test.KoinTest
import org.koin.test.get
import org.koin.test.inject
import java.util.*
import kotlin.random.Random
import fr.dcproject.repository.User as UserRepository
class KtorServerAuthSteps: En, KoinTest {
private val requester: Requester by inject()
init {
When("I have citizen:") { body: DataTable ->
val user = User(username = "jaque_${Random.nextInt(0, 10000)}", plainPassword = "azerty")
requester
.getFunction("insert_user")
.selectOne(user)
val data = body.asMap<String, String>(String::class.java, String::class.java)
val citizen = Citizen(
id = UUID.fromString(data["id"]),
name = Citizen.Name(data["firstName"], data["lastName"]),
birthday = DateTime.now(),
user = user
)
requester
.getFunction("upsert_citizen")
.selectOne(citizen)
}
Given("I am authenticated as an user") {
val id = UUID.randomUUID()
val jwtAsString: String = JWT.create()
.withIssuer("dc-project.fr")
.withClaim("id", id.toString())
.sign(Algorithm.HMAC512(JwtConfig.secret))
val user = User(id = id, username = "user", plainPassword = "azerty")
get<UserRepository>().insert(user)
KtorServerContext.defaultServer.addPreRequestSetup {
addHeader(HttpHeaders.Authorization, "Bearer $jwtAsString")
}
}
}
}

View File

@@ -0,0 +1,54 @@
package feature
import io.ktor.application.Application
import io.ktor.server.testing.TestApplicationCall
import io.ktor.server.testing.TestApplicationEngine
import io.ktor.server.testing.TestApplicationRequest
import io.ktor.server.testing.createTestEnvironment
import java.util.concurrent.TimeUnit
import kotlin.test.fail
class KtorServerContext(useByDefault: Boolean = true, val module: Application.() -> Unit) {
init { if (useByDefault) setDefault() }
companion object {
lateinit var defaultServer: KtorServerContext
}
private val engine = TestApplicationEngine(createTestEnvironment())
private data class RequestSetup(val setup: TestApplicationRequest.() -> Unit, val keepSetup: Boolean = true)
private val preRequestSetup = mutableListOf<RequestSetup>()
var call: TestApplicationCall? = null
fun addPreRequestSetup(keepSetup: Boolean = true, hook: TestApplicationRequest.() -> Unit) {
preRequestSetup.add(RequestSetup(hook, keepSetup))
}
fun handleRequest(setup: TestApplicationRequest.() -> Unit) =
try {
call = engine.handleRequest {
preRequestSetup.forEach { it.setup(this) }
setup(this)
}
} catch (e: Throwable) {
fail("Request fail, $e")
} finally {
preRequestSetup.removeAll { !it.keepSetup }
}
fun setDefault() {
defaultServer = this
}
fun start() {
engine.start()
module(engine.application)
}
fun stop() {
engine.stop(0L, 0L, TimeUnit.MILLISECONDS)
}
}

View File

@@ -0,0 +1,64 @@
package feature
import com.google.gson.JsonParser
import io.cucumber.datatable.DataTable
import io.cucumber.java8.En
import io.ktor.http.ContentType
import io.ktor.http.HttpHeaders
import io.ktor.http.HttpMethod
import io.ktor.http.HttpStatusCode
import io.ktor.server.testing.setBody
import io.ktor.util.KtorExperimentalAPI
import kotlinx.serialization.ImplicitReflectionSerializer
import org.junit.jupiter.api.Assertions
import org.opentest4j.AssertionFailedError
import kotlin.test.assertEquals
import kotlin.test.assertTrue
@ImplicitReflectionSerializer
@KtorExperimentalAPI
class KtorServerRequestSteps : En {
init {
Given("Next request as headers:") { dataTable: DataTable ->
KtorServerContext.defaultServer.addPreRequestSetup(false) {
dataTable.asMap<String, String>(String::class.java, String::class.java).forEach { key, value ->
this.addHeader(key, value)
}
}
}
Given("I send a {word} request to {string} with body:") { method: String, uri: String, body: String ->
KtorServerContext.defaultServer.handleRequest {
this.method = HttpMethod.parse(method)
this.uri = uri
this.addHeader(HttpHeaders.ContentType, ContentType.Application.Json.toString())
setBody(body)
}
}
Given("I send a {word} request to {string}") { method: String, uri: String ->
KtorServerContext.defaultServer.handleRequest {
this.method = HttpMethod.parse(method.toUpperCase())
this.uri = uri
}
}
Then("the response status code should be {int}") { statusCode: Int ->
assertEquals(HttpStatusCode.fromValue(statusCode), KtorServerContext.defaultServer.call?.response?.status())
}
Then("the response should contain object:") { expected: DataTable ->
val call = KtorServerContext.defaultServer.call ?: throw AssertionFailedError("No call")
val response = JsonParser().parse(call.response.content).getAsJsonObject()
expected.asMap<String, String>(String::class.java, String::class.java).forEach { (key, valueExpected) ->
assertTrue(response.has(key))
Assertions.assertEquals(valueExpected, response.get(key).asString)
}
}
Then("print last response") {
print(KtorServerContext.defaultServer.call?.response?.content)
}
}
}

View File

@@ -0,0 +1,55 @@
package feature
import io.cucumber.datatable.DataTable
import io.cucumber.java8.En
import kotlinx.serialization.ImplicitReflectionSerializer
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonArray
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.parse
import kotlin.test.assertEquals
import kotlin.test.fail
@ImplicitReflectionSerializer
class KtorServerRestSteps : En {
init {
Then("the JSON should contain:") { dataTable: DataTable ->
dataTable.asMap<String, String>(String::class.java, String::class.java).forEach { (key, value) ->
val jsonPrimitive = findJsonElement(key) as? JsonPrimitive ?: fail("\"$key\" element isn't json primitive")
assertEquals(jsonPrimitive.content, value)
}
}
Then("the JSON element {word} should have {int} item(s)") { node: String, count: Int ->
val jsonArray = findJsonElement(node) as? JsonArray ?: fail("\"$node\" element isn't json array")
assertEquals(count, jsonArray.size)
}
Then("the JSON should have {int} item(s)") { count: Int ->
val jsonArray = responseJsonElement as? JsonArray ?: fail("The json response isn't array")
assertEquals(count, jsonArray.size)
}
}
private fun findJsonElement(node: String): JsonElement {
var jsonElement: JsonElement = responseJsonElement
val elements = node.split(".")
elements.forEach {
val asArrayIndex = """\d+""".toRegex().find(it)
jsonElement = if (asArrayIndex != null) {
val index = asArrayIndex.groups.first()!!
jsonElement.jsonArray.get(index.value.toInt())
} else {
jsonElement.jsonObject.get(it) ?: throw AssertionError("\"$node\" element not found on json response")
}
}
return jsonElement
}
private val responseJsonElement: JsonElement
get() = Json.parse(KtorServerContext.defaultServer.call?.response?.content ?: fail("The response isn't valid JSON"))
}

View File

@@ -1,116 +0,0 @@
package feature
import com.google.gson.Gson
import com.google.gson.JsonParser
import cucumber.api.java8.En
import fr.dcproject.entity.Citizen
import fr.dcproject.entity.User
import fr.postgresjson.connexion.Requester
import fr.postgresjson.migration.Migrations
import io.cucumber.datatable.DataTable
import io.ktor.http.ContentType
import io.ktor.http.HttpHeaders
import io.ktor.http.HttpMethod
import io.ktor.http.HttpStatusCode
import io.ktor.server.testing.TestApplicationCall
import io.ktor.server.testing.TestApplicationEngine
import io.ktor.server.testing.setBody
import org.joda.time.DateTime
import org.junit.jupiter.api.Assertions.assertEquals
import org.koin.test.KoinTest
import org.koin.test.inject
import org.opentest4j.AssertionFailedError
import java.util.*
import kotlin.random.Random
import kotlin.test.assertTrue
import kotlin.test.asserter
import feature.Context.Companion.current as currentContext
class Request: En, KoinTest {
private val migrations: Migrations by inject()
private val requester: Requester by inject()
init {
When("I have citizen:") { body: DataTable ->
val user = User(username = "jaque_${Random.nextInt(0, 10000)}", plainPassword = "azerty")
val test: TestApplicationEngine.() -> Unit = {
requester
.getFunction("insert_user")
.selectOne(user)
val data = body.asMap<String, String>(String::class.java, String::class.java)
val citizen = Citizen(
id = UUID.fromString(data["id"]),
name = Citizen.Name(data["firstName"], data["lastName"]),
birthday = DateTime.now(),
user = user
)
requester
.getFunction("upsert_citizen")
.selectOne(citizen)
}
currentContext.engine.test()
}
When("I send a {string} request to {string} with body:") { method: String, uri: String, body: String ->
val test: TestApplicationEngine.() -> Unit = {
currentContext.call = handleRequest {
applyConfigurations()
addHeader(HttpHeaders.ContentType, ContentType.Application.Json.toString())
this.method = HttpMethod.parse(method)
this.uri = uri
setBody(body)
}
}
currentContext.engine.test()
}
When("I send a {string} request to {string}") { method: String, uri: String ->
val test: TestApplicationEngine.() -> Unit = {
currentContext.call = handleRequest {
applyConfigurations()
addHeader(HttpHeaders.ContentType, ContentType.Application.Json.toString())
this.method = HttpMethod.parse(method.toUpperCase())
this.uri = uri
}
}
currentContext.engine.test()
}
Then("the response status code should be {int}") { statusCode: Int ->
val call: TestApplicationCall = currentContext.call ?: throw AssertionFailedError("No call", statusCode, null)
with(call) {
assertEquals(HttpStatusCode.fromValue(statusCode), response.status(), response.content)
}
}
And("the response should contain:") { expected: DataTable ->
val call: TestApplicationCall = currentContext.call ?: throw AssertionFailedError("No call")
val p = call.response
val response = Gson().fromJson<List<Map<String, String>>>(p.content, List::class.java)
expected.asMap<String, String>(String::class.java, String::class.java).forEach { (key, value) ->
response.forEach {
if (it.containsKey(key)) {
assertEquals(it[key], value)
return@And
}
}
asserter.fail("The response not contain $key field")
}
}
And("the response should contain object:") { expected: DataTable ->
val call: TestApplicationCall = currentContext.call ?: throw AssertionFailedError("No call")
val p = call.response
val response = JsonParser().parse(p.content).getAsJsonObject()
expected.asMap<String, String>(String::class.java, String::class.java).forEach { (key, valueExpected) ->
assertTrue(response.has(key))
assertEquals(valueExpected, response.get(key).asString)
}
}
}
}