Additional functionality

This commit is contained in:
Tim Ysewyn
2019-08-07 23:05:02 +02:00
parent ab1c921c28
commit 2ebe11e2a5
11 changed files with 195 additions and 136 deletions

View File

@@ -16,6 +16,9 @@
package org.springframework.cloud.contract.spec.internal
import java.io.File
import java.net.URISyntaxException
import java.nio.charset.Charset
import java.util.regex.Pattern
/**
@@ -85,6 +88,43 @@ open class CommonDsl {
fun execute(commandToExecute: String) = ExecutionProperty(commandToExecute)
/**
* Read file contents as String.
* @param relativePath of the file to read
* @return String file contents
*/
fun file(relativePath: String) = file(relativePath, Charset.defaultCharset())
/**
* Read file contents as String with the given Charset.
* @param relativePath of the file to read
* @param charset to use for converting the bytes to String
* @return String file contents
*/
fun file(relativePath: String, charset: Charset) = Body(FromFileProperty(fileLocation(relativePath), String::class.java, charset))
/**
* Read file contents as bytes[].
* @param relativePath of the file to read
* @return String file contents
*/
fun fileAsBytes(relativePath: String) = Body(FromFileProperty(fileLocation(relativePath), ByteArray::class.java))
/**
* Read file contents as array of bytes.
* @param relativePath of the file to read
* @return file contents as an array of bytes
*/
private fun fileLocation(relativePath: String): File {
val resource = Thread.currentThread().contextClassLoader
.getResource(relativePath) ?: throw IllegalStateException("File [$relativePath] is not present")
try {
return File(resource.toURI())
} catch (ex: URISyntaxException) {
throw IllegalStateException(ex)
}
}
/* REGEX */
fun regexProperty(value: Any) = RegexProperty(value)

View File

@@ -19,6 +19,7 @@ package org.springframework.cloud.contract.spec.internal
import org.springframework.cloud.contract.spec.Contract
import org.springframework.cloud.contract.spec.ContractConverter
import java.io.File
import java.net.URLClassLoader.newInstance
import javax.script.ScriptEngineManager
/**
@@ -39,10 +40,12 @@ class KotlinContractConverter: ContractConverter<List<Contract>> {
}
override fun convertFrom(file: File): Collection<Contract> {
val eval = file.reader().use {
// Get a new engine every time we need to process a file.
// Reusing the script engine could leak context and will fail subsequent evals
ScriptEngineManager().getEngineByExtension(ext).eval(it)
val eval = withUpdatedClassloader(file) {
file.reader().use {
// Get a new engine every time we need to process a file.
// Reusing the script engine could leak context and will fail subsequent evals
ScriptEngineManager().getEngineByExtension(ext).eval(it)
}
}
return when (eval) {
is Contract -> listOf(eval)
@@ -53,4 +56,15 @@ class KotlinContractConverter: ContractConverter<List<Contract>> {
}
override fun convertTo(contract: Collection<Contract>) = contract.toList()
private fun withUpdatedClassloader(file: File, block: ClassLoader.() -> Any): Any {
val currentClassLoader = Thread.currentThread().contextClassLoader
try {
val tempClassLoader = newInstance(arrayOf(file.parentFile.toURI().toURL()), currentClassLoader)
Thread.currentThread().contextClassLoader = tempClassLoader
return tempClassLoader.block()
} finally {
Thread.currentThread().contextClassLoader = currentClassLoader
}
}
}

View File

@@ -17,6 +17,7 @@
package org.springframework.cloud.contract.spec.internal
import org.springframework.cloud.contract.spec.toDslProperties
import org.springframework.cloud.contract.spec.toDslProperty
import java.util.regex.Pattern
/**
@@ -36,73 +37,45 @@ open class RequestDsl : CommonDsl(), RegexCreatingProperty<ClientDslProperty> {
var multipart: Multipart? = null
var bodyMatchers: BodyMatchers? = null
fun method(method: String) {
this.method = DslProperty(method)
}
fun method(method: String) = method.toDslProperty()
fun method(method: HttpMethods.HttpMethod) {
this.method(method.toString())
}
fun method(method: HttpMethods.HttpMethod) = this.method(method.toString())
fun url(url: String) {
this.url = Url(url)
}
fun url(url: String) = Url(url)
fun url(url: DslProperty<Any>) {
this.url = Url(url)
}
fun url(url: DslProperty<Any>) = Url(url)
fun urlPath(url: String) {
this.urlPath = UrlPath(url)
}
fun path(path: String) = UrlPath(path)
fun urlPath(url: DslProperty<Any>) {
this.urlPath = UrlPath(url)
}
fun path(path: DslProperty<Any>) = UrlPath(path)
fun headers(headers: Headers.() -> Unit) {
this.headers = Headers().apply(headers)
this.headers = Request.RequestHeaders().apply(headers)
}
fun cookies(cookies: Cookies.() -> Unit) {
this.cookies = Cookies().apply(cookies)
this.cookies = Request.RequestCookies().apply(cookies)
}
fun body(body: Map<String, Any>) {
this.body = Body(body.toDslProperties())
}
fun body(body: Map<String, Any>) = Body(body.toDslProperties())
fun body(vararg body: Pair<String, Any>) {
this.body = Body(body.toMap().toDslProperties())
}
fun body(vararg body: Pair<String, Any>) = Body(body.toMap().toDslProperties())
fun body(body: Pair<String, Any>) {
this.body = Body(mapOf(body).toDslProperties())
}
fun body(body: Pair<String, Any>) = Body(mapOf(body).toDslProperties())
fun body(body: List<Any>) {
this.body = Body(body.toDslProperties())
}
fun body(body: List<Any>) = Body(body.toDslProperties())
fun body(body: DslProperty<Any>) {
this.body = Body(body)
}
fun body(body: DslProperty<Any>) = Body(body)
fun multipart(multipart: Map<String, Any>) {
this.multipart = Multipart(multipart.toDslProperties())
}
fun body(body: Any) = Body(body)
fun multipart(multipart: List<Any>) {
this.multipart = Multipart(multipart.toDslProperties())
}
fun multipart(multipart: Map<String, Any>) = Multipart(multipart.toDslProperties())
fun multipart(multipart: DslProperty<Any>) {
this.multipart = Multipart(multipart)
}
fun multipart(multipart: List<Any>) = Multipart(multipart.toDslProperties())
fun multipart(multipart: Any) {
this.multipart = Multipart(multipart)
}
fun multipart(multipart: DslProperty<Any>) = Multipart(multipart)
fun multipart(multipart: Any) = Multipart(multipart)
fun bodyMatchers(block: BodyMatchers.() -> Unit) {
bodyMatchers = BodyMatchers().apply(block)

View File

@@ -36,45 +36,29 @@ class ResponseDsl : CommonDsl(), RegexCreatingProperty<ServerDslProperty> {
var async: Boolean = false
var bodyMatchers: ResponseBodyMatchers? = null
fun status(code: Int) {
this.status = DslProperty(code)
}
fun code(code: Int): DslProperty<Any> = code.toDslProperty()
fun fixedDelayMilliseconds(delay: Long) {
this.delay = delay.toDslProperty()
}
fun fixedMilliseconds(delay: Long) = delay.toDslProperty()
fun headers(headers: Headers.() -> Unit) {
this.headers = Headers().apply(headers)
this.headers = Response.ResponseHeaders().apply(headers)
}
fun cookies(cookies: Cookies.() -> Unit) {
this.cookies = Cookies().apply(cookies)
this.cookies = Response.ResponseCookies().apply(cookies)
}
fun body(body: Map<String, Any>) {
this.body = Body(body.toDslProperties())
}
fun body(body: Map<String, Any>) = Body(body.toDslProperties())
fun body(vararg body: Pair<String, Any>) {
this.body = Body(body.toMap().toDslProperties())
}
fun body(vararg body: Pair<String, Any>) = Body(body.toMap().toDslProperties())
fun body(body: Pair<String, Any>) {
this.body = Body(mapOf(body).toDslProperties())
}
fun body(body: Pair<String, Any>) = Body(mapOf(body).toDslProperties())
fun body(body: List<Any>) {
this.body = Body(body.toDslProperties())
}
fun body(body: List<Any>) = Body(body.toDslProperties())
fun body(body: DslProperty<Any>) {
this.body = Body(body.toDslProperty())
}
fun body(body: DslProperty<Any>) = Body(body.toDslProperty())
fun body(body: Any) {
this.body = Body(body)
}
fun body(body: Any) = Body(body)
fun bodyMatchers(bodyMatchers: ResponseBodyMatchers.() -> Unit) {
this.bodyMatchers = ResponseBodyMatchers().apply(bodyMatchers)
@@ -124,6 +108,9 @@ 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()
override fun anyAlphaUnicode() = delegate.anyAlphaUnicode()
override fun anyAlphaNumeric() = delegate.anyAlphaNumeric()

View File

@@ -24,8 +24,6 @@ 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
import org.springframework.cloud.contract.spec.internal.RegexProperty
import org.springframework.cloud.contract.spec.internal.Url
import org.springframework.cloud.contract.spec.internal.UrlPath
/**
* Tests written based on the Java contract tests written in Groovy
@@ -38,19 +36,19 @@ class ContractTests {
fun `should work for http`() {
val contract = contract {
request {
url = Url("/foo")
method(HttpMethods.HttpMethod.PUT)
url = url("/foo")
method = method(HttpMethods.HttpMethod.PUT)
headers {
header("foo", "bar")
}
body("foo" to "bar")
body = body("foo" to "bar")
}
response {
status(200)
status = code(200)
headers {
header("foo2", "bar")
}
body("foo2" to "bar")
body = body("foo2" to "bar")
}
}
@@ -87,10 +85,10 @@ class ContractTests {
fun `should fail when no method is present`() {
val contract = contract {
request {
url = Url("/foo")
url = url("/foo")
}
response {
status(200)
status = code(200)
}
}
@@ -105,10 +103,10 @@ class ContractTests {
fun `should fail when no url is present`() {
val contract = contract {
request {
method("GET")
method = method("GET")
}
response {
status(200)
status = code(200)
}
}
@@ -123,8 +121,8 @@ class ContractTests {
fun `should fail when no status is present`() {
val contract = contract {
request {
url = Url("/foo")
method("GET")
url = url("/foo")
method = method("GET")
}
response {
}
@@ -264,14 +262,14 @@ then:
fun `should make equals and hashcode work properly for URL`() {
val a: Contract = contract {
request {
method("GET")
url = Url("/1")
method = method("GET")
url = url("/1")
}
}
val b: Contract = contract {
request {
method("GET")
url = Url("/1")
method = method("GET")
url = url("/1")
}
}
@@ -287,14 +285,14 @@ then:
fun `should make equals and hashcode work properly for URL with consumer producer`() {
val a: Contract = contract {
request {
method("GET")
url = Url(value(c("/1"), p("/1")))
method = method("GET")
url = url(value(c("/1"), p("/1")))
}
}
val b: Contract = contract {
request {
method("GET")
url = Url(value(c("/1"), p("/1")))
method = method("GET")
url = url(value(c("/1"), p("/1")))
}
}
@@ -311,26 +309,26 @@ then:
val index = 1
val a: Contract = contract {
request {
method(HttpMethods.HttpMethod.PUT)
method = method(HttpMethods.HttpMethod.PUT)
headers {
contentType(applicationJson())
}
url = Url("/$index")
url = url("/$index")
}
response {
status(HttpStatus.OK())
status = code(HttpStatus.OK())
}
}
val b: Contract = contract {
request {
method(HttpMethods.HttpMethod.PUT)
method = method(HttpMethods.HttpMethod.PUT)
headers {
contentType(applicationJson())
}
url = Url("/$index")
url = url("/$index")
}
response {
status(HttpStatus.OK())
status = code(HttpStatus.OK())
}
}
@@ -347,27 +345,27 @@ then:
var index = 1
val a: Contract = contract {
request {
method(HttpMethods.HttpMethod.PUT)
method = method(HttpMethods.HttpMethod.PUT)
headers {
contentType(applicationJson())
}
url = Url("/$index")
url = url("/$index")
}
response {
status(HttpStatus.OK())
status = code(HttpStatus.OK())
}
}
index = 2
val b: Contract = contract {
request {
method(HttpMethods.HttpMethod.PUT)
method = method(HttpMethods.HttpMethod.PUT)
headers {
contentType(applicationJson())
}
url = Url("/$index")
url = url("/$index")
}
response {
status(HttpStatus.OK())
status = code(HttpStatus.OK())
}
}
@@ -383,8 +381,8 @@ then:
fun `should return true when comparing two equal complex contracts`() {
val a: Contract = contract {
request {
method(HttpMethods.HttpMethod.GET)
url = Url("/path")
method = method(HttpMethods.HttpMethod.GET)
url = url("/path")
headers {
header("Accept", value(
consumer(regex("text/.*")),
@@ -397,8 +395,8 @@ then:
}
}
response {
status(HttpStatus.OK())
body("id" to mapOf("value" to "132"),
status = code(HttpStatus.OK())
body = body("id" to mapOf("value" to "132"),
"surname" to "Kowalsky",
"name" to "Jan",
"created" to "2014-02-02 12:23:43"
@@ -410,8 +408,8 @@ then:
}
val b: Contract = contract {
request {
method(HttpMethods.HttpMethod.GET)
url = Url("/path")
method = method(HttpMethods.HttpMethod.GET)
url = url("/path")
headers {
header("Accept", value(
consumer(regex("text/.*")),
@@ -424,8 +422,8 @@ then:
}
}
response {
status(HttpStatus.OK())
body("id" to mapOf("value" to "132"),
status = code(HttpStatus.OK())
body = body("id" to mapOf("value" to "132"),
"surname" to "Kowalsky",
"name" to "Jan",
"created" to "2014-02-02 12:23:43"
@@ -482,15 +480,15 @@ then:
fun `should support bodyMatchers`() {
val contract = contract {
request {
method(HttpMethods.HttpMethod.GET)
url = Url("/path")
method = method(HttpMethods.HttpMethod.GET)
url = url("/path")
body("id" to mapOf("value" to "132"))
bodyMatchers {
jsonPath("$.id.value", byRegex(anInteger()))
}
}
response {
status(HttpStatus.OK())
status = code(HttpStatus.OK())
body("id" to mapOf("value" to "132"),
"surname" to "Kowalsky",
"name" to "Jan",
@@ -526,13 +524,13 @@ then:
fun `should support query parameters for url`() {
val contract = contract {
request {
method(HttpMethods.HttpMethod.GET)
url = Url("/path") withQueryParameters {
method = method(HttpMethods.HttpMethod.GET)
url = url("/path") withQueryParameters {
parameter("foo", "bar")
}
}
response {
status(HttpStatus.OK())
status = code(HttpStatus.OK())
}
}
@@ -559,13 +557,13 @@ then:
fun `should support query parameters for url path`() {
val contract = contract {
request {
method(HttpMethods.HttpMethod.GET)
urlPath = UrlPath("/path") withQueryParameters {
method = method(HttpMethods.HttpMethod.GET)
urlPath = path("/path") withQueryParameters {
parameter("foo", "bar")
}
}
response {
status(HttpStatus.OK())
status = code(HttpStatus.OK())
}
}
@@ -592,13 +590,13 @@ then:
fun `should work with list as body`() {
val contract = contract {
request {
method(HttpMethods.HttpMethod.PUT)
url = Url("/path")
body(listOf("foo", "bar"))
method = method(HttpMethods.HttpMethod.PUT)
url = url("/path")
body = body(listOf("foo", "bar"))
}
response {
status(HttpStatus.OK())
body(listOf("foo2", "bar2"))
status = code(HttpStatus.OK())
body = body(listOf("foo2", "bar2"))
}
}

View File

@@ -16,7 +16,8 @@
package org.springframework.cloud.contract.spec.internal
import org.junit.Test
import org.junit.jupiter.api.Test
import org.springframework.cloud.contract.spec.Contract
import java.io.File
import kotlin.test.assertEquals
import kotlin.test.assertTrue
@@ -42,5 +43,13 @@ class KotlinContractConverterTest {
assertEquals(2, contracts.size)
}
@Test
fun `should work with binary payload`() {
val converter = KotlinContractConverter()
val contracts = converter.convertFrom(file("contracts/shouldWorkWithBinaryPayload.kts"))
assertEquals(1, contracts.size)
Contract.assertContract(contracts.elementAt(0))
}
private fun file(filename: String) = File(javaClass.classLoader.getResource(filename)!!.toURI())
}

View File

@@ -25,7 +25,7 @@ arrayOf(
url("/frauds")
}
response {
status(200)
code(200)
body("count" to 200)
headers {
contentType("application/vnd.fraud.v1+json")
@@ -38,7 +38,7 @@ arrayOf(
url("/drunks")
}
response {
status(200)
code(200)
body("count" to 100)
headers {
contentType("application/vnd.fraud.v1+json")

View File

@@ -0,0 +1,38 @@
/*
* 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 contracts
import org.springframework.cloud.contract.spec.ContractDsl.Companion.contract
import org.springframework.cloud.contract.spec.internal.HttpMethods
contract {
request {
url = url("/1")
method = method(HttpMethods.PUT())
headers {
contentType(applicationOctetStream())
}
body = fileAsBytes("contracts/request.pdf")
}
response {
status = code(200)
body = fileAsBytes("contracts/response.pdf")
headers {
contentType(applicationOctetStream())
}
}
}

View File

@@ -31,7 +31,7 @@ contract {
}
response {
status(200)
code(200)
body(
"fraudCheckStatus" to "OK",
"rejectionReason" to listOf(value(consumer(null), producer("assertThatRejectionReasonIsNull(\$it)")))