Fixed equals and hashcode

This commit is contained in:
Marcin Grzejszczak
2016-12-06 17:22:52 +01:00
parent a328130418
commit 39bef2ce08
35 changed files with 567 additions and 111 deletions

View File

@@ -40,15 +40,43 @@ class Contract {
* take precedence * take precedence
*/ */
Integer priority Integer priority
/**
* The HTTP request part of the contract
*/
Request request Request request
/**
* The HTTP response part of the contract
*/
Response response Response response
/**
* The label by which you'll reference the contract on the message consumer side
*/
String label String label
/**
* Description of a contract. May be used in the documentation generation.
*/
String description String description
/**
* Name of the generated test / stub. If not provided then the file name will be used.
* If you have multiple contracts in a single file and you don't provide this value
* then a prefix will be added to the file with the index number while iterating
* over the collection of contracts.
*
* Remember to have a unique name for every single contract. Otherwise you might
* generate tests that have two identical methods or you will override the stubs.
*/
String name
/**
* The input side of a messaging contract.
*/
Input input Input input
/**
* The output side of a messaging contract.
*/
OutputMessage outputMessage OutputMessage outputMessage
/** /**
* Whether the contract should be ignored or not * Whether the contract should be ignored or not.
*/ */
boolean ignored boolean ignored
@@ -68,6 +96,10 @@ class Contract {
this.priority = priority this.priority = priority
} }
void name(String name) {
this.name = name
}
void label(String label) { void label(String label) {
this.label = label this.label = label
} }

View File

@@ -25,8 +25,8 @@ import groovy.transform.ToString
* *
* @since 1.0.0 * @since 1.0.0
*/ */
@ToString(includePackage = false, includeFields = true, includeNames = true) @ToString(includePackage = false, includeFields = true, includeNames = true, includeSuper = true)
@EqualsAndHashCode(includeFields = true) @EqualsAndHashCode(callSuper = true)
@CompileStatic @CompileStatic
class Body extends DslProperty { class Body extends DslProperty {

View File

@@ -17,6 +17,8 @@
package org.springframework.cloud.contract.spec.internal package org.springframework.cloud.contract.spec.internal
import groovy.transform.CompileStatic import groovy.transform.CompileStatic
import groovy.transform.EqualsAndHashCode
import groovy.transform.ToString
/** /**
* Represents a client side {@link DslProperty} * Represents a client side {@link DslProperty}
@@ -24,6 +26,8 @@ import groovy.transform.CompileStatic
* @since 1.0.0 * @since 1.0.0
*/ */
@CompileStatic @CompileStatic
@EqualsAndHashCode(callSuper = true)
@ToString(includeSuper = true)
class ClientDslProperty extends DslProperty { class ClientDslProperty extends DslProperty {
ClientDslProperty(Object singleValue) { ClientDslProperty(Object singleValue) {

View File

@@ -20,7 +20,6 @@ import groovy.transform.PackageScope
import groovy.transform.TypeChecked import groovy.transform.TypeChecked
import java.util.regex.Pattern import java.util.regex.Pattern
/** /**
* Contains useful common methods for the DSL. * Contains useful common methods for the DSL.
* *

View File

@@ -19,14 +19,13 @@ package org.springframework.cloud.contract.spec.internal
import groovy.transform.CompileStatic import groovy.transform.CompileStatic
import groovy.transform.EqualsAndHashCode import groovy.transform.EqualsAndHashCode
import groovy.transform.ToString import groovy.transform.ToString
/** /**
* Represents an element of a DSL that can contain client or sever side values * Represents an element of a DSL that can contain client or sever side values
* *
* @since 1.0.0 * @since 1.0.0
*/ */
@CompileStatic @CompileStatic
@EqualsAndHashCode(includeFields = true) @EqualsAndHashCode
@ToString(includePackage = false, includeNames = true) @ToString(includePackage = false, includeNames = true)
class DslProperty<T> { class DslProperty<T> {

View File

@@ -17,6 +17,7 @@
package org.springframework.cloud.contract.spec.internal package org.springframework.cloud.contract.spec.internal
import groovy.transform.CompileStatic import groovy.transform.CompileStatic
import groovy.transform.EqualsAndHashCode
/** /**
* Represents a property that will become an executable method in the * Represents a property that will become an executable method in the
@@ -25,6 +26,7 @@ import groovy.transform.CompileStatic
* @since 1.0.0 * @since 1.0.0
*/ */
@CompileStatic @CompileStatic
@EqualsAndHashCode
class ExecutionProperty { class ExecutionProperty {
private static final String PLACEHOLDER_VALUE = '\\$it' private static final String PLACEHOLDER_VALUE = '\\$it'

View File

@@ -24,8 +24,8 @@ import groovy.transform.ToString
* *
* @since 1.0.0 * @since 1.0.0
*/ */
@EqualsAndHashCode(includeFields = true) @EqualsAndHashCode(includeFields = true, callSuper = true)
@ToString(includePackage = false, includeFields = true, ignoreNulls = true, includeNames = true) @ToString(includePackage = false, includeFields = true, ignoreNulls = true, includeNames = true, includeSuper = true)
@CompileStatic @CompileStatic
class Header extends DslProperty { class Header extends DslProperty {

View File

@@ -11,7 +11,7 @@ import groovy.transform.ToString
* @since 1.0.2 * @since 1.0.2
*/ */
@CompileStatic @CompileStatic
@EqualsAndHashCode @EqualsAndHashCode(includeFields = true)
@ToString(includePackage = false) @ToString(includePackage = false)
class HttpMethods { class HttpMethods {

View File

@@ -88,6 +88,8 @@ class Input extends Common {
return new DslProperty(client.clientValue, clientValue) return new DslProperty(client.clientValue, clientValue)
} }
@EqualsAndHashCode(includeFields = true, callSuper = true)
@ToString(includeSuper = true)
static class BodyType extends DslProperty { static class BodyType extends DslProperty {
BodyType(Object clientValue, Object serverValue) { BodyType(Object clientValue, Object serverValue) {

View File

@@ -25,8 +25,8 @@ import groovy.transform.ToString;
* *
* @since 1.0.0 * @since 1.0.0
*/ */
@EqualsAndHashCode(includeFields = true) @EqualsAndHashCode(includeFields = true, callSuper = true)
@ToString(includePackage = false, includeFields = true, ignoreNulls = true, includeNames = true) @ToString(includePackage = false, includeFields = true, ignoreNulls = true, includeNames = true, includeSuper = true)
@CompileStatic @CompileStatic
class MatchingStrategy extends DslProperty { class MatchingStrategy extends DslProperty {

View File

@@ -20,8 +20,8 @@ import groovy.transform.CompileStatic
import groovy.transform.EqualsAndHashCode import groovy.transform.EqualsAndHashCode
import groovy.transform.ToString import groovy.transform.ToString
@ToString(includePackage = false, includeFields = true, includeNames = true) @ToString(includePackage = false, includeFields = true, includeNames = true, includeSuper = true)
@EqualsAndHashCode(includeFields = true) @EqualsAndHashCode(callSuper = true)
@CompileStatic @CompileStatic
class Multipart extends DslProperty { class Multipart extends DslProperty {

View File

@@ -4,7 +4,6 @@ import groovy.transform.CompileStatic
import groovy.transform.PackageScope import groovy.transform.PackageScope
import java.util.regex.Pattern import java.util.regex.Pattern
/** /**
* @author Marcin Grzejszczak * @author Marcin Grzejszczak
*/ */

View File

@@ -27,8 +27,8 @@ import static org.springframework.cloud.contract.spec.util.ValidateUtils.validat
* *
* @since 1.0.0 * @since 1.0.0
*/ */
@EqualsAndHashCode(includeFields = true) @EqualsAndHashCode(includeFields = true, callSuper = true)
@ToString(includePackage = false, includeFields = true, ignoreNulls = true, includeNames = true) @ToString(includePackage = false, includeFields = true, ignoreNulls = true, includeNames = true, includeSuper = true)
@CompileStatic @CompileStatic
class QueryParameter extends DslProperty { class QueryParameter extends DslProperty {

View File

@@ -213,7 +213,7 @@ class Request extends Common {
} }
@CompileStatic @CompileStatic
@EqualsAndHashCode @EqualsAndHashCode(includeFields = true)
@ToString(includePackage = false) @ToString(includePackage = false)
private class ServerRequest extends Request { private class ServerRequest extends Request {
ServerRequest(Request request) { ServerRequest(Request request) {
@@ -222,7 +222,7 @@ class Request extends Common {
} }
@CompileStatic @CompileStatic
@EqualsAndHashCode @EqualsAndHashCode(includeFields = true)
@ToString(includePackage = false) @ToString(includePackage = false)
private class ClientRequest extends Request { private class ClientRequest extends Request {
ClientRequest(Request request) { ClientRequest(Request request) {
@@ -231,7 +231,7 @@ class Request extends Common {
} }
@CompileStatic @CompileStatic
@EqualsAndHashCode @EqualsAndHashCode(includeFields = true)
@ToString(includePackage = false) @ToString(includePackage = false)
private class RequestHeaders extends Headers { private class RequestHeaders extends Headers {
@@ -243,7 +243,7 @@ class Request extends Common {
} }
@CompileStatic @CompileStatic
@EqualsAndHashCode @EqualsAndHashCode(includeFields = true)
@ToString(includePackage = false) @ToString(includePackage = false)
private class ClientPatternValueDslProperty extends PatternValueDslProperty<ClientDslProperty> { private class ClientPatternValueDslProperty extends PatternValueDslProperty<ClientDslProperty> {

View File

@@ -30,7 +30,7 @@ import java.util.regex.Pattern
* @since 1.0.0 * @since 1.0.0
*/ */
@TypeChecked @TypeChecked
@EqualsAndHashCode @EqualsAndHashCode(includeFields = true)
@ToString(includePackage = false, includeFields = true) @ToString(includePackage = false, includeFields = true)
class Response extends Common { class Response extends Common {
@@ -120,7 +120,7 @@ class Response extends Common {
} }
@CompileStatic @CompileStatic
@EqualsAndHashCode @EqualsAndHashCode(callSuper = true)
@ToString(includePackage = false) @ToString(includePackage = false)
private class ServerResponse extends Response { private class ServerResponse extends Response {
ServerResponse(Response request) { ServerResponse(Response request) {
@@ -129,7 +129,7 @@ class Response extends Common {
} }
@CompileStatic @CompileStatic
@EqualsAndHashCode @EqualsAndHashCode(callSuper = true)
@ToString(includePackage = false) @ToString(includePackage = false)
private class ClientResponse extends Response { private class ClientResponse extends Response {
ClientResponse(Response request) { ClientResponse(Response request) {
@@ -138,7 +138,7 @@ class Response extends Common {
} }
@CompileStatic @CompileStatic
@EqualsAndHashCode @EqualsAndHashCode(includeFields = true)
@ToString(includePackage = false) @ToString(includePackage = false)
private class ResponseHeaders extends Headers { private class ResponseHeaders extends Headers {
@@ -150,7 +150,7 @@ class Response extends Common {
} }
@CompileStatic @CompileStatic
@EqualsAndHashCode @EqualsAndHashCode(includeFields = true)
@ToString(includePackage = false) @ToString(includePackage = false)
private class ServerPatternValueDslProperty extends PatternValueDslProperty<ServerDslProperty> { private class ServerPatternValueDslProperty extends PatternValueDslProperty<ServerDslProperty> {

View File

@@ -26,8 +26,8 @@ import groovy.transform.ToString
* @since 1.0.0 * @since 1.0.0
*/ */
@CompileStatic @CompileStatic
@EqualsAndHashCode(includeFields = true) @EqualsAndHashCode(callSuper = true)
@ToString(includePackage = false) @ToString(includePackage = false, includeSuper = true)
class ServerDslProperty extends DslProperty { class ServerDslProperty extends DslProperty {
ServerDslProperty(Object singleValue) { ServerDslProperty(Object singleValue) {

View File

@@ -27,8 +27,8 @@ import static org.springframework.cloud.contract.spec.util.ValidateUtils.validat
* *
* @since 1.0.0 * @since 1.0.0
*/ */
@ToString(includePackage = false, includeFields = true, includeNames = true) @ToString(includePackage = false, includeFields = true, includeNames = true, includeSuper = true)
@EqualsAndHashCode(includeFields = true) @EqualsAndHashCode(includeFields = true, callSuper = true)
@CompileStatic @CompileStatic
class Url extends DslProperty { class Url extends DslProperty {

View File

@@ -25,8 +25,8 @@ import groovy.transform.ToString
* *
* @since 1.0.0 * @since 1.0.0
*/ */
@ToString(includePackage = false, includeFields = true, includeNames = true) @ToString(includePackage = false, includeFields = true, includeNames = true, includeSuper = true)
@EqualsAndHashCode(includeFields = true) @EqualsAndHashCode(includeFields = true, callSuper = true)
@CompileStatic @CompileStatic
class UrlPath extends Url { class UrlPath extends Url {

View File

@@ -102,4 +102,154 @@ then:
} }
// end::ignored[] // end::ignored[]
} }
def 'should make equals and hashcode work properly for URL'() {
expect:
def a = Contract.make {
request {
url("/1")
}
}
def b = Contract.make {
request {
url("/1")
}
}
a == b
}
def 'should make equals and hashcode work properly for URL with consumer producer'() {
expect:
Contract.make {
request {
url($(c("/1"), p("/1")))
}
} == Contract.make {
request {
url($(c("/1"), p("/1")))
}
}
}
def 'should return true when comparing two equal contracts with gstring'() {
expect:
int index = 1
def a = Contract.make {
request {
method(PUT())
headers {
contentType(applicationJson())
}
url "/${index}"
}
response {
status 200
}
}
def b = Contract.make {
request {
method(PUT())
headers {
contentType(applicationJson())
}
url "/${index}"
}
response {
status 200
}
}
a == b
}
def 'should return false when comparing two unequal contracts with gstring'() {
expect:
int index = 1
def a = Contract.make {
request {
method(PUT())
headers {
contentType(applicationJson())
}
url "/${index}"
}
response {
status 200
}
}
int index2 = 2
def b = Contract.make {
request {
method(PUT())
headers {
contentType(applicationJson())
}
url "/${index2}"
}
response {
status 200
}
}
a != b
}
def 'should return true when comparing two equal complex contracts'() {
expect:
def a = Contract.make {
request {
method 'GET'
url '/path'
headers {
header('Accept': $(
consumer(regex('text/.*')),
producer('text/plain')
))
header('X-Custom-Header': $(
consumer(regex('^.*2134.*$')),
producer('121345')
))
}
}
response {
status 200
body(
id: [value: '132'],
surname: 'Kowalsky',
name: 'Jan',
created: '2014-02-02 12:23:43'
)
headers {
header 'Content-Type': 'text/plain'
}
}
}
def b = Contract.make {
request {
method 'GET'
url '/path'
headers {
header('Accept': $(
consumer(regex('text/.*')),
producer('text/plain')
))
header('X-Custom-Header': $(
consumer(regex('^.*2134.*$')),
producer('121345')
))
}
}
response {
status 200
body(
id: [value: '132'],
surname: 'Kowalsky',
name: 'Jan',
created: '2014-02-02 12:23:43'
)
headers {
header 'Content-Type': 'text/plain'
}
}
}
a == b
}
} }

View File

@@ -131,7 +131,7 @@ class StubRepository {
File file = path.toFile(); File file = path.toFile();
if (isContractDescriptor(file)) { if (isContractDescriptor(file)) {
mappingDescriptors mappingDescriptors
.add(ContractVerifierDslConverter.convert(file)); .addAll(ContractVerifierDslConverter.convertAsCollection(file));
} }
return super.visitFile(path, attrs); return super.visitFile(path, attrs);
} }

View File

@@ -17,6 +17,7 @@
package org.springframework.cloud.contract.verifier.converter package org.springframework.cloud.contract.verifier.converter
import groovy.transform.CompileStatic import groovy.transform.CompileStatic
import org.springframework.cloud.contract.spec.Contract
import org.springframework.cloud.contract.verifier.file.ContractMetadata import org.springframework.cloud.contract.verifier.file.ContractMetadata
/** /**
@@ -46,7 +47,9 @@ interface SingleFileConverter {
/** /**
* Returns the name of the converted stub file. If you have multiple contracts * Returns the name of the converted stub file. If you have multiple contracts
* in a single file then a prefix will be added to the generated file. * in a single file then a prefix will be added to the generated file. If you
* provide the {@link Contract#name} field then that field will override the
* generated file name.
* *
* Example: name of file with 2 contracts is {@code foo.groovy}, it will be * Example: name of file with 2 contracts is {@code foo.groovy}, it will be
* converted by the implementation to {@code foo.json}. The recursive file * converted by the implementation to {@code foo.json}. The recursive file

View File

@@ -20,6 +20,7 @@ import groovy.transform.CompileStatic
import org.springframework.cloud.contract.spec.Contract import org.springframework.cloud.contract.spec.Contract
import org.springframework.cloud.contract.verifier.dsl.wiremock.WireMockStubStrategy import org.springframework.cloud.contract.verifier.dsl.wiremock.WireMockStubStrategy
import org.springframework.cloud.contract.verifier.file.ContractMetadata import org.springframework.cloud.contract.verifier.file.ContractMetadata
import org.springframework.cloud.contract.verifier.util.NamesUtil
import java.nio.charset.StandardCharsets import java.nio.charset.StandardCharsets
@@ -49,7 +50,7 @@ class DslToWireMockClientConverter extends DslToWireMockConverter {
} }
List<String> convertedContracts = [] List<String> convertedContracts = []
contract.convertedContract.eachWithIndex { Contract dsl, int index -> contract.convertedContract.eachWithIndex { Contract dsl, int index ->
String name = "${rootName}_${index}" String name = dsl.name ? NamesUtil.convertIllegalPackageChars(dsl.name) : "${rootName}_${index}"
convertedContracts << convertASingleContract(name, contract, dsl) convertedContracts << convertASingleContract(name, contract, dsl)
} }
return convertedContracts return convertedContracts

View File

@@ -47,7 +47,7 @@ abstract class DslToWireMockConverter implements SingleFileConverter {
return "" return ""
} }
protected List<Contract> createGroovyDSLFromStringContent(String groovyDslAsString) { protected Collection<Contract> createGroovyDSLFromStringContent(String groovyDslAsString) {
return ContractVerifierDslConverter.convertAsCollection(groovyDslAsString) return ContractVerifierDslConverter.convertAsCollection(groovyDslAsString)
} }
} }

View File

@@ -25,6 +25,7 @@ import org.springframework.cloud.contract.verifier.converter.SingleFileConverter
import org.springframework.cloud.contract.verifier.converter.SingleFileConvertersHolder import org.springframework.cloud.contract.verifier.converter.SingleFileConvertersHolder
import org.springframework.cloud.contract.verifier.file.ContractFileScanner import org.springframework.cloud.contract.verifier.file.ContractFileScanner
import org.springframework.cloud.contract.verifier.file.ContractMetadata import org.springframework.cloud.contract.verifier.file.ContractMetadata
import org.springframework.cloud.contract.verifier.util.NamesUtil
import java.nio.charset.StandardCharsets import java.nio.charset.StandardCharsets
import java.nio.file.Files import java.nio.file.Files
@@ -94,7 +95,7 @@ class RecursiveFilesConverter {
} }
Path absoluteTargetPath = createAndReturnTargetDirectory(sourceFile) Path absoluteTargetPath = createAndReturnTargetDirectory(sourceFile)
File newJsonFile = createTargetFileWithProperName(singleFileConverter, absoluteTargetPath, File newJsonFile = createTargetFileWithProperName(singleFileConverter, absoluteTargetPath,
sourceFile, contractsSize, index) sourceFile, contractsSize, index, dsl)
newJsonFile.setText(convertedContent, StandardCharsets.UTF_8.toString()) newJsonFile.setText(convertedContent, StandardCharsets.UTF_8.toString())
} }
} catch (Exception e) { } catch (Exception e) {
@@ -112,11 +113,22 @@ class RecursiveFilesConverter {
} }
private File createTargetFileWithProperName(SingleFileConverter singleFileConverter, Path absoluteTargetPath, private File createTargetFileWithProperName(SingleFileConverter singleFileConverter, Path absoluteTargetPath,
File sourceFile, int contractsSize, int index) { File sourceFile, int contractsSize, int index, Contract dsl) {
String generatedName = singleFileConverter.generateOutputFileNameForInput(sourceFile.name) String name = generateName(dsl, contractsSize, singleFileConverter, sourceFile, index)
String name = contractsSize == 1 ? generatedName : "${index}_${generatedName}"
File newJsonFile = new File(absoluteTargetPath.toFile(), name) File newJsonFile = new File(absoluteTargetPath.toFile(), name)
log.info("Creating new json [$newJsonFile.path]") log.info("Creating new stub [$newJsonFile.path]")
return newJsonFile return newJsonFile
} }
private String generateName(Contract dsl, int contractsSize, SingleFileConverter converter,
File sourceFile, int index) {
String generatedName = converter.generateOutputFileNameForInput(sourceFile.name)
String extension = NamesUtil.afterLastDot(generatedName)
if (dsl.name) {
return "${dsl.name}.${extension}"
} else if (contractsSize == 1) {
return generatedName
}
return "${index}_${generatedName}"
}
} }

View File

@@ -35,7 +35,8 @@ class RecursiveFilesConverterSpec extends Specification {
private static private static
final Set<Path> EXPECTED_TARGET_FILES = [Paths.get("dslRoot.json"), Paths.get("dir1/dsl1.json"), final Set<Path> EXPECTED_TARGET_FILES = [Paths.get("dslRoot.json"), Paths.get("dir1/dsl1.json"),
Paths.get("dir1/dsl1b.json"), Paths.get("dir2/dsl2.json"), Paths.get("dir1/dsl1b.json"), Paths.get("dir2/dsl2.json"),
Paths.get("dir1/0_dsl1_list.json"), Paths.get("dir1/1_dsl1_list.json")] Paths.get("dir1/0_dsl1_list.json"), Paths.get("dir1/1_dsl1_list.json"),
Paths.get("dir1/shouldHaveIndex1.json"), Paths.get("dir1/shouldHaveIndex2.json")]
@Rule @Rule
public TemporaryFolder tmpFolder = new TemporaryFolder(); public TemporaryFolder tmpFolder = new TemporaryFolder();

View File

@@ -17,10 +17,12 @@
package org.springframework.cloud.contract.verifier.wiremock package org.springframework.cloud.contract.verifier.wiremock
import com.github.tomakehurst.wiremock.stubbing.StubMapping import com.github.tomakehurst.wiremock.stubbing.StubMapping
import org.springframework.cloud.contract.verifier.util.ContractVerifierDslConverter
import org.springframework.cloud.contract.spec.Contract import org.springframework.cloud.contract.spec.Contract
import org.springframework.cloud.contract.verifier.util.ContractVerifierDslConverter
import spock.lang.Specification import spock.lang.Specification
import java.util.regex.Pattern
class WireMockToDslConverterSpec extends Specification { class WireMockToDslConverterSpec extends Specification {
def 'should produce a Groovy DSL from WireMock stub'() { def 'should produce a Groovy DSL from WireMock stub'() {
@@ -69,12 +71,14 @@ class WireMockToDslConverterSpec extends Specification {
} }
response { response {
status 200 status 200
body( body("""{
id: [value: '132'], "id": {
surname: 'Kowalsky', "value": "132"
name: 'Jan', },
created: '2014-02-02 12:23:43' "surname": "Kowalsky",
) "name": "Jan",
"created": "2014-02-02 12:23:43"
}""")
headers { headers {
header 'Content-Type': 'text/plain' header 'Content-Type': 'text/plain'
@@ -84,10 +88,12 @@ class WireMockToDslConverterSpec extends Specification {
when: when:
String groovyDsl = WireMockToDslConverter.fromWireMockStub(wireMockStub) String groovyDsl = WireMockToDslConverter.fromWireMockStub(wireMockStub)
then: then:
ContractVerifierDslConverter.convert( def a = ContractVerifierDslConverter.convert(
"""org.springframework.cloud.contract.spec.Contract.make { """org.springframework.cloud.contract.spec.Contract.make {
$groovyDsl $groovyDsl
}""") == expectedGroovyDsl }""")
def b = expectedGroovyDsl
a == b
} }
@@ -97,7 +103,7 @@ class WireMockToDslConverterSpec extends Specification {
{ {
"request": { "request": {
"method": "DELETE", "method": "DELETE",
"urlPattern": "/credit-card-verification-data/[0-9]+", "urlPattern": "1",
"headers": { "headers": {
"Content-Type": { "Content-Type": {
"equalTo": "application/vnd.mymoid-adapter.v2+json; charset=UTF-8" "equalTo": "application/vnd.mymoid-adapter.v2+json; charset=UTF-8"
@@ -119,29 +125,30 @@ class WireMockToDslConverterSpec extends Specification {
Contract expectedGroovyDsl = Contract.make { Contract expectedGroovyDsl = Contract.make {
request { request {
method 'DELETE' method 'DELETE'
url $(consumer(~/\/credit-card-verification-data\/[0-9]+/), producer('/credit-card-verification-data/1')) url $(consumer(~/1/), producer('1'))
headers { headers {
header('Content-Type': 'application/vnd.mymoid-adapter.v2+json; charset=UTF-8') header('Content-Type': 'application/vnd.mymoid-adapter.v2+json; charset=UTF-8')
} }
} }
response { response {
status 200 status 200
body("""{ body( """{
"status": "OK" "status": "OK"
}""") }""")
headers { headers {
header 'Content-Type': 'application/json' header 'Content-Type': 'application/json'
} }
} }
} }
when: when:
String groovyDsl = WireMockToDslConverter.fromWireMockStub(wireMockStub) String groovyDsl = WireMockToDslConverter.fromWireMockStub(wireMockStub)
then: then:
ContractVerifierDslConverter.convert( def a = ContractVerifierDslConverter.convert(
"""org.springframework.cloud.contract.spec.Contract.make { """org.springframework.cloud.contract.spec.Contract.make {
$groovyDsl $groovyDsl
}""") == expectedGroovyDsl }""")
def b = expectedGroovyDsl
(a.request.url.clientValue as Pattern).pattern() == (b.request.url.clientValue as Pattern).pattern()
} }
def 'should convert WireMock stub with response body containing integer'() { def 'should convert WireMock stub with response body containing integer'() {
@@ -230,11 +237,14 @@ class WireMockToDslConverterSpec extends Specification {
} }
response { response {
status 200 status 200
body([ body( """[
[a: 1, c: '3'], {
'b', "a": 1,
'a' "c": "3"
]) },
"b",
"a"
]""")
headers { headers {
header 'Content-Type': 'application/json' header 'Content-Type': 'application/json'
} }
@@ -282,26 +292,26 @@ class WireMockToDslConverterSpec extends Specification {
response { response {
status 200 status 200
body("""[ body("""[
{ {
"amount": 1.01, "amount": 1.01,
"name": "Name", "name": "Name",
"info": { "info": {
"title": "title1", "title": "title1",
"payload": null "payload": null
}, },
"booleanvalue": true, "booleanvalue": true,
"user": null "user": null
}, },
{ {
"amount": 2.01, "amount": 2.01,
"name": "Name2", "name": "Name2",
"info": { "info": {
"title": "title2", "title": "title2",
"payload": null "payload": null
}, },
"booleanvalue": true, "booleanvalue": true,
"user": null "user": null
} }
]""") ]""")
} }
} }
@@ -337,7 +347,7 @@ class WireMockToDslConverterSpec extends Specification {
request { request {
method 'POST' method 'POST'
url '/test' url '/test'
body ('''{"property1":"abc","property2":"2017-01","property3":"666","property4":1428566412}''') body ('''{"property1":"abc", "property2":"2017-01", "property3":"666", "property4":1428566412}''')
} }
response { response {
status 200 status 200
@@ -359,15 +369,15 @@ class WireMockToDslConverterSpec extends Specification {
String wireMockStub = '''\ String wireMockStub = '''\
{ {
"request": { "request": {
"method": "POST", "method": "POST",
"url": "/test", "url": "/test",
"bodyPatterns": [{ "bodyPatterns": [{
"matches": "[0-9]{5}" "matches": "1"
}] }]
}, },
"response": { "response": {
"status": 200 "status": 200
} }
} }
''' '''
and: and:
@@ -377,7 +387,7 @@ class WireMockToDslConverterSpec extends Specification {
request { request {
method 'POST' method 'POST'
url '/test' url '/test'
body $(consumer(~/[0-9]{5}/), producer('12345')) body $(consumer(~/1/), producer('1'))
} }
response { response {
status 200 status 200
@@ -391,7 +401,7 @@ class WireMockToDslConverterSpec extends Specification {
$groovyDsl $groovyDsl
}""") }""")
and: and:
evaluatedGroovyDsl == expectedGroovyDsl (evaluatedGroovyDsl.request.body.clientValue as Pattern).pattern() == (expectedGroovyDsl.request.body.clientValue as Pattern).pattern()
} }
def 'should convert WireMock stub with request body with equalToJson'() { def 'should convert WireMock stub with request body with equalToJson'() {
@@ -418,7 +428,7 @@ class WireMockToDslConverterSpec extends Specification {
request { request {
method 'POST' method 'POST'
url '/test' url '/test'
body '''{"pan":"4855141150107894","expirationDate":"2017-01","dcvx":"178"}''' body '''{"pan":"4855141150107894", "expirationDate":"2017-01", "dcvx":"178"}'''
} }
response { response {
status 200 status 200
@@ -458,7 +468,7 @@ class WireMockToDslConverterSpec extends Specification {
request { request {
method 'POST' method 'POST'
url '/test' url '/test'
body '''{"pan":"4855141150107894","expirationDate":"2017-01","dcvx":"178"}''' body '''{"pan":"4855141150107894", "expirationDate":"2017-01", "dcvx":"178"}'''
} }
response { response {
status 200 status 200
@@ -483,7 +493,7 @@ class WireMockToDslConverterSpec extends Specification {
"url" : "/test", "url" : "/test",
"method" : "POST", "method" : "POST",
"bodyPatterns" : [ { "bodyPatterns" : [ {
"matches" : "[0-9]{2}" "matches" : "1"
} ] } ]
}, },
"response" : { "response" : {
@@ -498,7 +508,7 @@ class WireMockToDslConverterSpec extends Specification {
request { request {
method 'POST' method 'POST'
url '/test' url '/test'
body $(consumer(~/[0-9]{2}/), producer('12')) body $(consumer(~/1/), producer('1'))
} }
response { response {
status 200 status 200
@@ -512,7 +522,7 @@ class WireMockToDslConverterSpec extends Specification {
$groovyDsl $groovyDsl
}""") }""")
and: and:
evaluatedGroovyDsl == expectedGroovyDsl (evaluatedGroovyDsl.request.body.clientValue as Pattern).pattern() == (expectedGroovyDsl.request.body.clientValue as Pattern).pattern()
} }
def 'should convert WireMock stub with priorities'() { def 'should convert WireMock stub with priorities'() {

View File

@@ -0,0 +1,33 @@
import org.springframework.cloud.contract.spec.Contract
/*
* Copyright 2013-2016 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
*
* http://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.
*/
(1..2).collect { int index ->
Contract.make {
name("shouldHaveIndex${index}")
request {
method(PUT())
headers {
contentType(applicationJson())
}
url "/${index}"
}
response {
status 200
}
}
}

View File

@@ -113,16 +113,17 @@ class JavaTestGenerator implements SingleTestGenerator {
log.debug("Stub content from file [${stubsFile.text}]") log.debug("Stub content from file [${stubsFile.text}]")
} }
List<Contract> stubContents = metadata.convertedContract List<Contract> stubContents = metadata.convertedContract
dsls << stubContents.collectEntries { Contract stubContent -> Map<ParsedDsl, TestType> entries = stubContents.collectEntries { Contract stubContent ->
TestType testType = (stubContent.input || stubContent.outputMessage) ? TestType.MESSAGING : TestType.HTTP TestType testType = (stubContent.input || stubContent.outputMessage) ? TestType.MESSAGING : TestType.HTTP
return [(new ParsedDsl(metadata, stubContent, stubsFile)): testType] return [(new ParsedDsl(metadata, stubContent, stubsFile)): testType]
} }
dsls.putAll(entries)
} }
return dsls return dsls
} }
@Canonical @Canonical
@EqualsAndHashCode @EqualsAndHashCode(includeFields = true)
private static class ParsedDsl { private static class ParsedDsl {
ContractMetadata contract ContractMetadata contract
Contract groovyDsl Contract groovyDsl

View File

@@ -57,10 +57,24 @@ class MethodBuilder {
if (log.isDebugEnabled()) { if (log.isDebugEnabled()) {
log.debug("Stub content Groovy DSL [$stubContent]") log.debug("Stub content Groovy DSL [$stubContent]")
} }
String methodName = NamesUtil.camelCase(NamesUtil.toLastDot(NamesUtil.afterLast(stubsFile.path, File.separator))) String methodName = methodName(contract, stubsFile, stubContent)
return new MethodBuilder(methodName, stubContent, configProperties, contract.ignored || stubContent.ignored) return new MethodBuilder(methodName, stubContent, configProperties, contract.ignored || stubContent.ignored)
} }
private static String methodName(ContractMetadata contract, File stubsFile, Contract stubContent) {
if (stubContent.name) {
return NamesUtil.camelCase(stubContent.name)
} else if (contract.convertedContract.size() > 1) {
int index = contract.convertedContract.findIndexOf { it == stubContent}
return "${camelCasedMethodFromFileName(stubsFile)}_${index}"
}
return camelCasedMethodFromFileName(stubsFile)
}
private static String camelCasedMethodFromFileName(File stubsFile) {
return NamesUtil.camelCase(NamesUtil.toLastDot(NamesUtil.afterLast(stubsFile.path, File.separator)))
}
/** /**
* Appends to the {@link BlockBuilder} the contents of the test * Appends to the {@link BlockBuilder} the contents of the test
*/ */

View File

@@ -17,6 +17,7 @@
package org.springframework.cloud.contract.verifier.file package org.springframework.cloud.contract.verifier.file
import groovy.transform.CompileStatic import groovy.transform.CompileStatic
import groovy.transform.EqualsAndHashCode
import groovy.transform.ToString import groovy.transform.ToString
import org.springframework.cloud.contract.spec.Contract import org.springframework.cloud.contract.spec.Contract
import org.springframework.cloud.contract.verifier.util.ContractVerifierDslConverter import org.springframework.cloud.contract.verifier.util.ContractVerifierDslConverter
@@ -30,6 +31,7 @@ import java.nio.file.Path
* @since 1.0.0 * @since 1.0.0
*/ */
@CompileStatic @CompileStatic
@EqualsAndHashCode
@ToString @ToString
class ContractMetadata { class ContractMetadata {
/** /**

View File

@@ -80,7 +80,7 @@ class ContractVerifierDslConverter {
return new GroovyShell(ContractVerifierDslConverter.classLoader, new Binding(), new CompilerConfiguration(sourceEncoding: 'UTF-8')) return new GroovyShell(ContractVerifierDslConverter.classLoader, new Binding(), new CompilerConfiguration(sourceEncoding: 'UTF-8'))
} }
private static List<Contract> listOfContracts(object) { private static Collection<Contract> listOfContracts(object) {
if (object instanceof Collection) { if (object instanceof Collection) {
return object as Collection<Contract> return object as Collection<Contract>
} else if (!object instanceof Contract) { } else if (!object instanceof Contract) {

View File

@@ -106,6 +106,6 @@ class NamesUtil {
* Converts illegal package characters to underscores * Converts illegal package characters to underscores
*/ */
static String convertIllegalPackageChars(String packageName) { static String convertIllegalPackageChars(String packageName) {
return packageName.replace('-', '_') return packageName.replace('-', '_').replace(" ", "_")
} }
} }

View File

@@ -38,7 +38,7 @@ class MockMvcMethodBodyBuilderSpec extends Specification implements WireMockStub
Contract dslWithOptionalsInString = Contract.make { Contract dslWithOptionalsInString = Contract.make {
priority 1 priority 1
request { request {
method 'POST' method POST()
url '/users/password' url '/users/password'
headers { headers {
contentType(applicationJson()) contentType(applicationJson())
@@ -65,7 +65,7 @@ class MockMvcMethodBodyBuilderSpec extends Specification implements WireMockStub
Contract dslWithOptionals = Contract.make { Contract dslWithOptionals = Contract.make {
priority 1 priority 1
request { request {
method 'POST' method POST()
url '/users/password' url '/users/password'
headers { headers {
contentType(applicationJson()) contentType(applicationJson())

View File

@@ -26,7 +26,7 @@ import spock.lang.Specification
import static org.springframework.cloud.contract.verifier.config.TestFramework.JUNIT import static org.springframework.cloud.contract.verifier.config.TestFramework.JUNIT
import static org.springframework.cloud.contract.verifier.config.TestFramework.SPOCK import static org.springframework.cloud.contract.verifier.config.TestFramework.SPOCK
import static org.springframework.cloud.contract.verifier.util.ContractVerifierDslConverter.convert import static org.springframework.cloud.contract.verifier.util.ContractVerifierDslConverter.convertAsCollection
class SingleTestGeneratorSpec extends Specification { class SingleTestGeneratorSpec extends Specification {
@@ -64,7 +64,7 @@ class SingleTestGeneratorSpec extends Specification {
given: given:
ContractVerifierConfigProperties properties = new ContractVerifierConfigProperties(); ContractVerifierConfigProperties properties = new ContractVerifierConfigProperties();
properties.targetFramework = testFramework properties.targetFramework = testFramework
ContractMetadata contract = new ContractMetadata(file.toPath(), true, 1, 2, convert(file)) ContractMetadata contract = new ContractMetadata(file.toPath(), true, 1, 2, convertAsCollection(file))
contract.ignored >> true contract.ignored >> true
contract.order >> 2 contract.order >> 2
JavaTestGenerator testGenerator = new JavaTestGenerator() JavaTestGenerator testGenerator = new JavaTestGenerator()
@@ -86,7 +86,7 @@ class SingleTestGeneratorSpec extends Specification {
ContractVerifierConfigProperties properties = new ContractVerifierConfigProperties(); ContractVerifierConfigProperties properties = new ContractVerifierConfigProperties();
properties.testMode = TestMode.JAXRSCLIENT properties.testMode = TestMode.JAXRSCLIENT
properties.targetFramework = testFramework properties.targetFramework = testFramework
ContractMetadata contract = new ContractMetadata(file.toPath(), true, 1, 2, convert(file)) ContractMetadata contract = new ContractMetadata(file.toPath(), true, 1, 2, convertAsCollection(file))
contract.ignored >> true contract.ignored >> true
contract.order >> 2 contract.order >> 2
JavaTestGenerator testGenerator = new JavaTestGenerator() JavaTestGenerator testGenerator = new JavaTestGenerator()
@@ -124,11 +124,11 @@ class SingleTestGeneratorSpec extends Specification {
and: and:
ContractVerifierConfigProperties properties = new ContractVerifierConfigProperties(); ContractVerifierConfigProperties properties = new ContractVerifierConfigProperties();
properties.targetFramework = testFramework properties.targetFramework = testFramework
ContractMetadata contract = new ContractMetadata(file.toPath(), true, 1, 2, convert(file)) ContractMetadata contract = new ContractMetadata(file.toPath(), true, 1, 2, convertAsCollection(file))
contract.ignored >> true contract.ignored >> true
contract.order >> 2 contract.order >> 2
and: and:
ContractMetadata contract2 = new ContractMetadata(secondFile.toPath(), true, 1, 2, convert(secondFile)) ContractMetadata contract2 = new ContractMetadata(secondFile.toPath(), true, 1, 2, convertAsCollection(secondFile))
contract2.ignored >> true contract2.ignored >> true
contract2.order >> 2 contract2.order >> 2
and: and:
@@ -167,7 +167,7 @@ class SingleTestGeneratorSpec extends Specification {
ContractVerifierConfigProperties properties = new ContractVerifierConfigProperties(); ContractVerifierConfigProperties properties = new ContractVerifierConfigProperties();
properties.targetFramework = testFramework properties.targetFramework = testFramework
and: and:
ContractMetadata contract2 = new ContractMetadata(secondFile.toPath(), true, 1, 2, convert(file)) ContractMetadata contract2 = new ContractMetadata(secondFile.toPath(), true, 1, 2, convertAsCollection(file))
contract2.ignored >> false contract2.ignored >> false
contract2.order >> 2 contract2.order >> 2
and: and:
@@ -186,5 +186,98 @@ class SingleTestGeneratorSpec extends Specification {
SPOCK | spockClassStrings SPOCK | spockClassStrings
} }
def "should pick the contract's name as the test method"() {
given:
File secondFile = tmpFolder.newFile()
secondFile.write("""
org.springframework.cloud.contract.spec.Contract.make {
name("MySuperMethod")
request {
method 'PUT'
url 'url'
}
response {
status 200
}
}
""")
and:
ContractVerifierConfigProperties properties = new ContractVerifierConfigProperties();
properties.targetFramework = testFramework
ContractMetadata contract = new ContractMetadata(secondFile.toPath(), false, 1, null, convertAsCollection(secondFile))
JavaTestGenerator testGenerator = new JavaTestGenerator()
when:
String clazz = testGenerator.buildClass(properties, [contract], "test", "test", 'com/foo')
then:
clazz.contains("validate_mySuperMethod()")
where:
testFramework << [JUNIT, SPOCK]
}
def "should pick the contract's name as the test method when there are multiple contracts"() {
given:
File secondFile = tmpFolder.newFile()
secondFile.write('''
(1..2).collect { int index ->
org.springframework.cloud.contract.spec.Contract.make {
name("shouldHaveIndex${index}")
request {
method(PUT())
headers {
contentType(applicationJson())
}
url "/${index}"
}
response {
status 200
}
}
}''')
and:
ContractVerifierConfigProperties properties = new ContractVerifierConfigProperties();
properties.targetFramework = testFramework
ContractMetadata contract = new ContractMetadata(secondFile.toPath(), false, 1, null, convertAsCollection(secondFile))
JavaTestGenerator testGenerator = new JavaTestGenerator()
when:
String clazz = testGenerator.buildClass(properties, [contract], "test", "test", 'com/foo')
then:
clazz.contains("validate_shouldHaveIndex1()")
clazz.contains("validate_shouldHaveIndex2()")
where:
testFramework << [JUNIT, SPOCK]
}
def "should generate the test method when there are multiple contracts without name field"() {
given:
File secondFile = tmpFolder.newFile()
secondFile.write('''
(1..2).collect { int index ->
org.springframework.cloud.contract.spec.Contract.make {
request {
method(PUT())
headers {
contentType(applicationJson())
}
url "/${index}"
}
response {
status 200
}
}
}''')
and:
ContractVerifierConfigProperties properties = new ContractVerifierConfigProperties();
properties.targetFramework = testFramework
ContractMetadata contract = new ContractMetadata(secondFile.toPath(), false, 1, null, convertAsCollection(secondFile))
JavaTestGenerator testGenerator = new JavaTestGenerator()
when:
String clazz = testGenerator.buildClass(properties, [contract], "test", "test", 'com/foo')
then:
clazz.contains("_0() throws Exception")
clazz.contains("_1() throws Exception")
where:
testFramework << [JUNIT, SPOCK]
}
} }

View File

@@ -0,0 +1,99 @@
package org.springframework.cloud.contract.verifier.util
import spock.lang.Specification
/**
* @author Marcin Grzejszczak
*/
class NamesUtilSpec extends Specification {
def "should return the whole string before the last one"() {
given:
String string = "a.b.c.d.e"
expect:
"a.b.c.d" == NamesUtil.beforeLast(string, ".")
}
def "should return empty string when no token was found for before last"() {
given:
String string = "a.b.c.d.e"
expect:
"" == NamesUtil.beforeLast(string, "/")
}
def "should return first token after the last one"() {
given:
String string = "a.b.c.d.e"
expect:
"e" == NamesUtil.afterLast(string, ".")
}
def "should return the input string when no token was found for after last"() {
given:
String string = "a.b.c.d.e"
expect:
string == NamesUtil.afterLast(string, "/")
}
def "should return first token after the last dot"() {
given:
String string = "a.b.c.d.e"
expect:
"e" == NamesUtil.afterLastDot(string)
}
def "should return the input string when no token was found for after last dot"() {
given:
String string = "abcde"
expect:
string == NamesUtil.afterLastDot(string)
}
def "should return camel case version of a string"() {
given:
String string = "BlaBlaBla"
expect:
"blaBlaBla" == NamesUtil.camelCase(string)
}
def "should return capitalized version of a string"() {
given:
String string = "blaBlaBla"
expect:
"BlaBlaBla" == NamesUtil.capitalize(string)
}
def "should return all text to last dot"() {
given:
String string = "a.b.c.d.e"
expect:
"a.b.c.d" == NamesUtil.toLastDot(string)
}
def "should return the input string when no token was found for to last dot"() {
given:
String string = "abcde"
expect:
string == NamesUtil.toLastDot(string)
}
def "should convert a package notation to directory"() {
given:
String string = "a.b.c.d.e"
expect:
"a/b/c/d/e".replace("/", File.separator) == NamesUtil.packageToDirectory(string)
}
def "should convert a directory notation to package"() {
given:
String string = "a/b/c/d/e".replace("/", File.separator)
expect:
"a.b.c.d.e" == NamesUtil.directoryToPackage(string)
}
def "should convert all illegal package chars to legal ones"() {
given:
String string = "a-b c"
expect:
"a_b_c" == NamesUtil.convertIllegalPackageChars(string)
}
}