Added support for referencing the request

This commit is contained in:
Tim Ysewyn
2019-08-10 17:00:27 +02:00
parent ff22880d1e
commit cb1a836c17
7 changed files with 361 additions and 16 deletions

View File

@@ -23,6 +23,7 @@ import java.nio.file.Files
import com.example.loan.model.Client
import com.example.loan.model.LoanApplication
import com.example.loan.model.LoanApplicationStatus
import com.fasterxml.jackson.annotation.JsonProperty
import com.jayway.jsonpath.JsonPath
import com.toomuchcoding.jsonassert.JsonAssertion.assertThatJson
import io.restassured.RestAssured
@@ -145,4 +146,23 @@ class LoanApplicationServiceTests {
assertThat(exchange.body).isEqualTo(Files.readAllBytes(response.toPath()))
}
@Test
fun shouldSuccessfullyReturnRequestDataInResponse() {
// when:
val exchange: ResponseEntity<FraudCheckResponse> = RestTemplate().exchange(
RequestEntity.put(URI.create("http://localhost:6565/frauds/name"))
.header("Content-Type", "application/json")
.body(FraudCheckRequest("Tim")),
FraudCheckResponse::class.java)
// then:
assertThat(exchange.statusCodeValue).isEqualTo(200)
assertThat(exchange.headers["Content-Type"]?.get(0))
.isEqualTo("application/json")
// and:
assertThat(exchange.body?.message).isEqualTo("Don't worry Tim you're not a fraud")
}
}
data class FraudCheckRequest(@JsonProperty("name") val name: String)
data class FraudCheckResponse(@JsonProperty("result") val message: String)

View File

@@ -17,8 +17,6 @@
package fraudname
import org.springframework.cloud.contract.spec.ContractDsl.Companion.contract
import org.springframework.cloud.contract.spec.internal.HttpMethods
import org.springframework.cloud.contract.spec.internal.HttpStatus
contract {
// highest priority
@@ -33,10 +31,9 @@ contract {
}
response {
status = OK
// TODO
// body = body("result" to "Sorry ${fromRequest().body("$.name")} but you're a fraud")
// headers {
// header(contentType(), fromRequest().header(contentType()))
// }
body = body("result" to "Sorry ${fromRequest().body("$.name")} but you're a fraud")
headers {
contentType = fromRequest().header(CONTENT_TYPE)
}
}
}

View File

@@ -17,8 +17,6 @@
package fraudname
import org.springframework.cloud.contract.spec.ContractDsl.Companion.contract
import org.springframework.cloud.contract.spec.internal.HttpMethods
import org.springframework.cloud.contract.spec.internal.HttpStatus
contract {
request {
@@ -31,10 +29,9 @@ contract {
}
response {
status = OK
// TODO
// body("result" to "Don't worry ${fromRequest().body("$.name")} you're not a fraud")
// headers {
// header(contentType(), fromRequest().header(contentType()))
// }
body = body("result" to "Don't worry ${fromRequest().body("$.name")} you're not a fraud")
headers {
contentType = fromRequest().header(CONTENT_TYPE)
}
}
}

View File

@@ -0,0 +1,173 @@
/*
* Copyright 2013-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.contract.spec.internal
import org.springframework.cloud.contract.spec.ContractTemplate
/**
* @author Tim Ysewyn
*/
class FromRequestDsl {
private val template: ContractTemplate = HandlebarsContractTemplate()
/**
* @return URL path and query.
*/
fun url() = FromRequestDslProperty(template.escapedUrl())
/**
* First value of a query parameter e.g. request.query.search.
* @param key key for the query param
* @return dsl property
*/
fun query(key: String) = FromRequestDslProperty(template.escapedQuery(key))
/**
* nth value of a query parameter (zero indexed) e.g. request.query.search.[5].
* @param key key for the query param
* @param index index of the query param
* @return dsl property
*/
fun query(key: String, index: Int) = FromRequestDslProperty(template.escapedQuery(key, index))
/**
* URL path.
* @return dsl property
*/
fun path() = FromRequestDslProperty(template.escapedPath())
/**
* nth value of a URL path (zero indexed) e.g. {{{ request.path.[2] }}}* @param index.
* @param index path index
* @return dsl property
*/
fun path(index: Int) = FromRequestDslProperty(template.escapedPath(index))
/**
* First value of a request header e.g. request.headers.X-Request-Id.
* @param key header key
* @return dsl property
*/
fun header(key: String) = FromRequestDslProperty(template.escapedHeader(key))
/**
* nth value of a request header (zero indexed) e.g. request.headers.X-Request-Id.
* @param key header key
* @param index header index
* @return dsl property
*/
fun header(key: String, index: Int) = FromRequestDslProperty(template.escapedHeader(key, index))
/**
* Retruns the tempalte for retrieving the first value of a cookie with certain key.
* @param key cookie key
* @return dsl property
*/
fun cookie(key: String) = FromRequestDslProperty(template.escapedCookie(key))
/**
* Request body text (avoid for non-text bodies).
* @return dsl property
*/
fun body() = FromRequestDslProperty(template.escapedBody())
/**
* Request body text for the given JsonPath.
* @param jsonPath json path body
* @return dsl property
*/
fun body(jsonPath: String) = FromRequestDslProperty(template.escapedBody(jsonPath))
/**
* Unescaped URL path and query.
* @return dsl property
*/
fun rawUrl() = FromRequestDslProperty(template.url())
/**
* Unescaped First value of a query parameter e.g. request.query.search.
* @param key query key
* @return dsl property
*/
fun rawQuery(key: String) = FromRequestDslProperty(template.query(key))
/**
* Unescaped nth value of a query parameter (zero indexed) e.g.
* request.query.search.[5].
* @param key query key
* @param index query index
* @return dsl property
*/
fun rawQuery(key: String, index: Int) = FromRequestDslProperty(template.query(key, index))
/**
* Unescaped URL path.
* @return dsl property
*/
fun rawPath() = FromRequestDslProperty(template.path())
/**
* Unescaped nth value of a URL path (zero indexed) e.g. {{{ request.path.[2]. }}}*
* @param index path index
* @return dsl property
*/
fun rawPath(index: Int) = FromRequestDslProperty(template.path(index))
/**
* Unescaped First value of a request header e.g. request.headers.X-Request-Id.
* @param key header key
* @return dsl property
*/
fun rawHeader(key: String) = FromRequestDslProperty(template.header(key))
/**
* Unescaped nth value of a request header (zero indexed) e.g.
* request.headers.X-Request-Id.
* @param key header key
* @param index header index
* @return dsl property
*/
fun rawHeader(key: String, index: Int) = FromRequestDslProperty(template.header(key, index))
/**
* Unescaped Returns the template for retrieving the first value of a cookie with
* certain key.
* @param key cookie key
* @return dsl property
*/
fun rawCookie(key: String) = FromRequestDslProperty(template.cookie(key))
/**
* Unescaped Request body text (avoid for non-text bodies).
* @return dsl property
*/
fun rawBody() = FromRequestDslProperty(template.body())
/**
* Unescaped Request body text for the given JsonPath.
* @param jsonPath json path body
* @return dsl property
*/
fun rawBody(jsonPath: String) = FromRequestDslProperty(template.body(jsonPath))
}
class FromRequestDslProperty(private val content: String) : DslProperty<String>(content) {
// Overridden to support String interpolation
override fun toString(): String = content
}

View File

@@ -275,6 +275,132 @@ open class HeadersDsl: CommonDsl() {
/* HELPER VARIABLES */
/* HTTP HEADERS */
val ACCEPT = HttpHeaders.ACCEPT
val ACCEPT_CHARSET = HttpHeaders.ACCEPT_CHARSET
val ACCEPT_ENCODING = HttpHeaders.ACCEPT_ENCODING
val ACCEPT_LANGUAGE = HttpHeaders.ACCEPT_LANGUAGE
val ACCEPT_RANGES = HttpHeaders.ACCEPT_RANGES
val ACCESS_CONTROL_ALLOW_CREDENTIALS = HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS
val ACCESS_CONTROL_ALLOW_HEADERS = HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS
val ACCESS_CONTROL_ALLOW_METHODS = HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS
val ACCESS_CONTROL_ALLOW_ORIGIN = HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN
val ACCESS_CONTROL_EXPOSE_HEADERS = HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS
val ACCESS_CONTROL_MAX_AGE = HttpHeaders.ACCESS_CONTROL_MAX_AGE
val ACCESS_CONTROL_REQUEST_HEADERS = HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS
val ACCESS_CONTROL_REQUEST_METHOD = HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD
val AGE = HttpHeaders.AGE
val ALLOW = HttpHeaders.ALLOW
val AUTHORIZATION = HttpHeaders.AUTHORIZATION
val CACHE_CONTROL = HttpHeaders.CACHE_CONTROL
val CONNECTION = HttpHeaders.CONNECTION
val CONTENT_ENCODING = HttpHeaders.CONTENT_ENCODING
val CONTENT_DISPOSITION = HttpHeaders.CONTENT_DISPOSITION
val CONTENT_LANGUAGE = HttpHeaders.CONTENT_LANGUAGE
val CONTENT_LENGTH = HttpHeaders.CONTENT_LENGTH
val CONTENT_LOCATION = HttpHeaders.CONTENT_LOCATION
val CONTENT_RANGE = HttpHeaders.CONTENT_RANGE
val CONTENT_TYPE = HttpHeaders.CONTENT_TYPE
val COOKIE = HttpHeaders.COOKIE
val DATE = HttpHeaders.DATE
val ETAG = HttpHeaders.ETAG
val EXPECT = HttpHeaders.EXPECT
val EXPIRES = HttpHeaders.EXPIRES
val FROM = HttpHeaders.FROM
val HOST = HttpHeaders.HOST
val IF_MATCH = HttpHeaders.IF_MATCH
val IF_MODIFIED_SINCE = HttpHeaders.IF_MODIFIED_SINCE
val IF_NONE_MATCH = HttpHeaders.IF_NONE_MATCH
val IF_RANGE = HttpHeaders.IF_RANGE
val IF_UNMODIFIED_SINCE = HttpHeaders.IF_UNMODIFIED_SINCE
val LAST_MODIFIED = HttpHeaders.LAST_MODIFIED
val LINK = HttpHeaders.LINK
val LOCATION = HttpHeaders.LOCATION
val MAX_FORWARDS = HttpHeaders.MAX_FORWARDS
val ORIGIN = HttpHeaders.ORIGIN
val PRAGMA = HttpHeaders.PRAGMA
val PROXY_AUTHENTICATE = HttpHeaders.PROXY_AUTHENTICATE
val PROXY_AUTHORIZATION = HttpHeaders.PROXY_AUTHORIZATION
val RANGE = HttpHeaders.RANGE
val REFERER = HttpHeaders.REFERER
val RETRY_AFTER = HttpHeaders.RETRY_AFTER
val SERVER = HttpHeaders.SERVER
val SET_COOKIE = HttpHeaders.SET_COOKIE
val SET_COOKIE_2 = HttpHeaders.SET_COOKIE_2
val TE = HttpHeaders.TE
val TRAILER = HttpHeaders.TRAILER
val TRANSFER_ENCODING = HttpHeaders.TRANSFER_ENCODING
val UPGRADE = HttpHeaders.UPGRADE
val USER_AGENT = HttpHeaders.USER_AGENT
val VARY = HttpHeaders.VARY
val VIA = HttpHeaders.VIA
val WARNING = HttpHeaders.WARNING
val WWW_AUTHENTICATE = HttpHeaders.WWW_AUTHENTICATE
/* MESSAGING HEADERS */
val MESSAGING_CONTENT_TYPE = MessagingHeaders.MESSAGING_CONTENT_TYPE
/* MEDIA TYPES */
val ALL_VALUE = MediaTypes.ALL_VALUE

View File

@@ -229,8 +229,7 @@ class ResponseDsl : CommonDsl(), RegexCreatingProperty<ServerDslProperty> {
fun `$`(server: ServerDslProperty, client: ClientDslProperty) = delegate.value(client, server)
// TODO, needs to be reworked - no lazy string interpolation like in Groovy
// fun fromRequest() = FromRequest()
fun fromRequest() = FromRequestDsl()
override fun anyAlphaUnicode() = delegate.anyAlphaUnicode()

View File

@@ -881,4 +881,37 @@ then:
}
}
@Test
fun `should support fromRequest`() {
val contract = contract {
request {
method = GET
url = url("/path")
body = body("id" to mapOf("value" to "132"))
headers {
accept = APPLICATION_JSON
}
}
response {
status = OK
body = body("value is ${fromRequest().body("$.value")}")
headers {
contentType = fromRequest().header(ACCEPT)
}
}
}
assertDoesNotThrow {
Contract.assertContract(contract)
}.also {
val response = contract.response
assertThat(response.body.clientValue).isEqualTo("value is {{{jsonPath request.body '$.value'}}}")
val headers = response.headers.entries
assertThat(headers).hasSize(1)
assertThat(headers.elementAt(0).name).isEqualTo("Content-Type")
assertThat(headers.elementAt(0).clientValue).isEqualTo("{{{request.headers.Accept.[0]}}}")
assertThat(headers.elementAt(0).serverValue).isEqualTo("{{{request.headers.Accept.[0]}}}")
}
}
}