Merge branch 'jersey-tests-support' of https://github.com/dstepanov/accurest into dstepanov-jersey-tests-support

# Conflicts:
#	accurest-core/src/main/groovy/io/codearte/accurest/builder/SpockMethodBodyBuilder.groovy
This commit is contained in:
Olga Maciaszek-Sharma
2015-07-31 12:54:38 +02:00
40 changed files with 1672 additions and 149 deletions

View File

@@ -11,6 +11,7 @@ import static io.codearte.accurest.builder.MethodBuilder.createTestMethod
import static io.codearte.accurest.util.NamesUtil.capitalize
class SingleTestGenerator {
private final AccurestConfigProperties configProperties
SingleTestGenerator(AccurestConfigProperties configProperties) {
@@ -34,7 +35,9 @@ class SingleTestGenerator {
}
}
if (configProperties.testMode == TestMode.MOCKMVC) {
if (configProperties.testMode == TestMode.JAXRSCLIENT) {
clazz.addStaticImport('javax.ws.rs.client.Entity.*')
} else if (configProperties.testMode == TestMode.MOCKMVC) {
clazz.addStaticImport('com.jayway.restassured.module.mockmvc.RestAssuredMockMvc.*')
} else {
clazz.addStaticImport('com.jayway.restassured.RestAssured.*')
@@ -52,7 +55,7 @@ class SingleTestGenerator {
}
listOfFiles.each {
clazz.addMethod(createTestMethod(it, configProperties.targetFramework))
clazz.addMethod(createTestMethod(it, configProperties))
}
return clazz.build()
}

View File

@@ -0,0 +1,95 @@
package io.codearte.accurest.builder
import groovy.transform.PackageScope
import groovy.transform.TypeChecked
import io.codearte.accurest.dsl.GroovyDsl
import io.codearte.accurest.dsl.internal.Header
import io.codearte.accurest.dsl.internal.QueryParameter
@PackageScope
@TypeChecked
class JaxRsClientSpockMethodBodyBuilder extends SpockMethodBodyBuilder {
JaxRsClientSpockMethodBodyBuilder(GroovyDsl stubDefinition) {
super(stubDefinition)
}
@Override
protected void givenBlock(BlockBuilder bb) {
}
@Override
protected void when(BlockBuilder bb) {
bb.addLine("def response = webTarget")
bb.indent()
appendUrlPathAndQueryParameters(bb)
appendRequestWithRequiredResponseContentType(bb)
appendHeaders(bb)
appendMethodAndBody(bb)
bb.unindent()
bb.addEmptyLine()
bb.addLine("String responseAsString = response.readEntity(String)")
}
protected void appendRequestWithRequiredResponseContentType(BlockBuilder bb) {
String acceptHeader = getHeader("Accept")
if (acceptHeader) {
bb.addLine(".request('$acceptHeader')")
} else {
bb.addLine(".request()")
}
}
protected void appendUrlPathAndQueryParameters(BlockBuilder bb) {
if (request.url) {
bb.addLine(".path('$request.url.serverValue')")
} else if (request.urlPath) {
bb.addLine(".path('$request.urlPath.serverValue')")
request.urlPath.queryParameters?.parameters.findAll(this.&allowedQueryParameter).each { QueryParameter param ->
bb.addLine(".queryParam('$param.name', '${resolveParamValue(param).toString()}')")
}
}
}
protected void appendMethodAndBody(BlockBuilder bb) {
String method = request.method.serverValue.toString().toLowerCase()
if (request.body) {
String contentType = getHeader('Content-Type') ?: getRequestContentType().mimeType
bb.addLine(".method('$method', entity('$bodyAsString', '$contentType'))")
} else {
bb.addLine(".method('$method')")
}
}
protected appendHeaders(BlockBuilder bb) {
request.headers?.collect { Header header ->
if (header.name == 'Content-Type' || header.name == 'Accept') return // Particular headers are set via 'request' / 'entity' methods
bb.addLine(".header('${header.name}', '${header.serverValue}')")
}
}
protected String getHeader(String name) {
return request.headers?.entries.find { it.name == name }?.serverValue
}
@Override
protected void validateResponseCodeBlock(BlockBuilder bb) {
bb.addLine("response.status == $response.status.serverValue")
}
@Override
protected void validateResponseHeadersBlock(BlockBuilder bb) {
response.headers?.collect { Header header ->
bb.addLine("response.getHeaderString('$header.name') == '$header.serverValue'")
}
}
@Override
protected String getResponseAsString() {
return 'responseAsString'
}
}

View File

@@ -1,7 +1,9 @@
package io.codearte.accurest.builder
import groovy.util.logging.Slf4j
import io.codearte.accurest.config.AccurestConfigProperties
import io.codearte.accurest.config.TestFramework
import io.codearte.accurest.config.TestMode
import io.codearte.accurest.dsl.GroovyDsl
import io.codearte.accurest.util.NamesUtil
@@ -13,28 +15,36 @@ class MethodBuilder {
private final String methodName
private final GroovyDsl stubContent
private final TestFramework lang
private final AccurestConfigProperties configProperties
private MethodBuilder(String methodName, GroovyDsl stubContent, TestFramework lang) {
private MethodBuilder(String methodName, GroovyDsl stubContent, AccurestConfigProperties configProperties) {
this.stubContent = stubContent
this.methodName = methodName
this.lang = lang
this.configProperties = configProperties
}
static MethodBuilder createTestMethod(File stubsFile, TestFramework lang) {
static MethodBuilder createTestMethod(File stubsFile, AccurestConfigProperties configProperties) {
log.debug("Stub content from file [${stubsFile.text}]")
GroovyDsl stubContent = new GroovyShell(this.classLoader).evaluate(stubsFile)
log.debug("Stub content Groovy DSL [$stubContent]")
String methodName = NamesUtil.camelCase(NamesUtil.toLastDot(NamesUtil.afterLast(stubsFile.path, File.separator)))
return new MethodBuilder(methodName, stubContent, lang)
return new MethodBuilder(methodName, stubContent, configProperties)
}
void appendTo(BlockBuilder blockBuilder) {
if (lang == TestFramework.JUNIT) {
if (configProperties.targetFramework == TestFramework.JUNIT) {
blockBuilder.addLine('@Test')
}
blockBuilder.addLine(lang.methodModifier + "$methodName() {")
new SpockMethodBodyBuilder(stubContent).appendTo(blockBuilder)
blockBuilder.addLine(configProperties.targetFramework.methodModifier + "$methodName() {")
getMethodBodyBuilder().appendTo(blockBuilder)
blockBuilder.addLine('}')
}
private SpockMethodBodyBuilder getMethodBodyBuilder() {
if (configProperties.testMode == TestMode.JAXRSCLIENT) {
return new JaxRsClientSpockMethodBodyBuilder(stubContent)
}
return new MockMvcSpockMethodBodyBuilder(stubContent)
}
}

View File

@@ -0,0 +1,77 @@
package io.codearte.accurest.builder
import groovy.transform.PackageScope
import groovy.transform.TypeChecked
import groovy.transform.TypeCheckingMode
import io.codearte.accurest.dsl.GroovyDsl
import io.codearte.accurest.dsl.internal.Header
import io.codearte.accurest.dsl.internal.QueryParameter
import io.codearte.accurest.dsl.internal.Request
import io.codearte.accurest.dsl.internal.UrlPath
@PackageScope
@TypeChecked
class MockMvcSpockMethodBodyBuilder extends SpockMethodBodyBuilder {
MockMvcSpockMethodBodyBuilder(GroovyDsl stubDefinition) {
super(stubDefinition)
}
protected void given(BlockBuilder bb) {
bb.addLine('def request = given()')
bb.indent()
request.headers?.collect { Header header ->
bb.addLine(".header('${header.name}', '${header.serverValue}')")
}
if (request.body) {
bb.addLine(".body('$bodyAsString')")
}
bb.unindent()
}
protected void when(BlockBuilder bb) {
bb.addLine('def response = given().spec(request)')
bb.indent()
String url = buildUrl(request)
String method = request.method.serverValue.toString().toLowerCase()
bb.addLine(/.${method}("$url")/)
bb.unindent()
}
protected void validateResponseCodeBlock(BlockBuilder bb) {
bb.addLine("response.statusCode == $response.status.serverValue")
}
protected void validateResponseHeadersBlock(BlockBuilder bb) {
response.headers?.collect { Header header ->
bb.addLine("response.header('$header.name') == '$header.serverValue'")
}
}
@Override
protected String getResponseAsString() {
return 'response.body.asString()'
}
protected String buildUrl(Request request) {
if (request.url)
return request.url.serverValue;
if (request.urlPath)
return buildUrlFromUrlPath(request.urlPath)
throw new IllegalStateException("URL is not set!")
}
@TypeChecked(TypeCheckingMode.SKIP)
protected String buildUrlFromUrlPath(UrlPath urlPath) {
String params = urlPath.queryParameters.parameters
.findAll(this.&allowedQueryParameter)
.inject([] as List<String>) { List<String> result, QueryParameter param ->
result << "${param.name}=${resolveParamValue(param).toString()}"
}
.join('&')
return "$urlPath.serverValue?$params"
}
}

View File

@@ -1,16 +1,14 @@
package io.codearte.accurest.builder
import groovy.json.JsonOutput
import groovy.transform.PackageScope
import groovy.transform.TypeChecked
import io.codearte.accurest.dsl.GroovyDsl
import io.codearte.accurest.dsl.internal.DslProperty
import io.codearte.accurest.dsl.internal.ExecutionProperty
import io.codearte.accurest.dsl.internal.Header
import io.codearte.accurest.dsl.internal.MatchingStrategy
import io.codearte.accurest.dsl.internal.QueryParameter
import io.codearte.accurest.dsl.internal.Request
import io.codearte.accurest.dsl.internal.Response
import io.codearte.accurest.dsl.internal.UrlPath
import io.codearte.accurest.util.ContentType
import io.codearte.accurest.util.JsonConverter
@@ -24,87 +22,95 @@ import static io.codearte.accurest.util.ContentUtils.recognizeContentTypeFromHea
* @author Jakub Kubrynski
*/
@PackageScope
class SpockMethodBodyBuilder {
private final GroovyDsl stubDefinition
@TypeChecked
abstract class SpockMethodBodyBuilder {
protected final Request request
protected final Response response
SpockMethodBodyBuilder(GroovyDsl stubDefinition) {
this.stubDefinition = stubDefinition
this.request = stubDefinition.request
this.response = stubDefinition.response
}
void appendTo(BlockBuilder blockBuilder) {
Request request = stubDefinition.request
Response response = stubDefinition.response
blockBuilder.with {
startBlock()
addLine('given:').startBlock()
addLine('def request = given()')
indent()
request.headers?.collect { Header header ->
addLine(".header('${header.name}', '${header.serverValue}')")
}
if (request.body) {
Object bodyValue = extractServerValueFromBody(request.body.serverValue)
String matches = trimRepeatedQuotes(new JsonOutput().toJson(bodyValue))
addLine(".body('$matches')")
}
blockBuilder.startBlock()
unindent().endBlock().addEmptyLine()
givenBlock(blockBuilder)
whenBlock(blockBuilder)
thenBlock(blockBuilder)
addLine('when:').startBlock()
addLine('def response = given().spec(request)')
indent()
blockBuilder.endBlock()
}
String url = buildUrl(request)
String method = request.method.serverValue.toLowerCase()
protected void thenBlock(BlockBuilder bb) {
bb.addLine('then:')
bb.startBlock()
then(bb)
bb.endBlock()
}
blockBuilder.addLine(/.${method}("$url")/)
unindent().endBlock().addEmptyLine()
protected void whenBlock(BlockBuilder bb) {
bb.addLine('when:')
bb.startBlock()
when(bb)
bb.endBlock().addEmptyLine()
}
addLine('then:').startBlock()
addLine("response.statusCode == $response.status.serverValue")
protected void givenBlock(BlockBuilder bb) {
bb.addLine('given:')
bb.startBlock()
given(bb)
bb.endBlock().addEmptyLine()
}
response.headers?.collect { Header header ->
addLine("response.header('$header.name') == '$header.serverValue'")
}
if (response.body) {
endBlock()
addLine('and:').startBlock()
def responseBody = response.body.serverValue
ContentType contentType = recognizeContentTypeFromHeader(response.headers)
if (contentType == ContentType.UNKNOWN) {
contentType = recognizeContentTypeFromContent(responseBody)
}
if (responseBody instanceof GString) {
responseBody = extractValue(responseBody, contentType, { DslProperty dslProperty -> dslProperty.serverValue })
}
if (contentType == ContentType.JSON) {
addLine('def responseBody = new JsonSlurper().parseText(response.body.asString())')
if (responseBody instanceof List) {
processArrayElements(responseBody, "", blockBuilder)
} else if (responseBody instanceof Map) {
processMapElement(responseBody, blockBuilder, "")
} else {
processBodyElement(blockBuilder, '', responseBody)
}
} else if (contentType == ContentType.XML) {
addLine('def responseBody = new XmlSlurper().parseText(response.body.asString())')
// TODO xml validation
}
}
endBlock()
protected void given(BlockBuilder bb) {}
endBlock()
protected abstract void when(BlockBuilder bb)
protected abstract void validateResponseCodeBlock(BlockBuilder bb)
protected abstract void validateResponseHeadersBlock(BlockBuilder bb)
protected abstract String getResponseAsString()
protected void then(BlockBuilder bb) {
validateResponseCodeBlock(bb)
if (response.headers) {
validateResponseHeadersBlock(bb)
}
if (response.body) {
bb.endBlock()
bb.addLine('and:').startBlock()
validateResponseBodyBlock(bb)
}
}
private String trimRepeatedQuotes(String toTrim) {
if (toTrim.startsWith('"')) {
return toTrim.replaceAll('"', '')
protected void validateResponseBodyBlock(BlockBuilder bb) {
def responseBody = response.body.serverValue
ContentType contentType = getResponseContentType()
if (responseBody instanceof GString) {
responseBody = extractValue(responseBody, contentType, { DslProperty dslProperty -> dslProperty.serverValue })
}
if (contentType == ContentType.JSON) {
bb.addLine("def responseBody = new JsonSlurper().parseText($responseAsString)")
processBodyElement(bb, "", responseBody)
} else if (contentType == ContentType.XML) {
bb.addLine("def responseBody = new XmlSlurper().parseText($responseAsString)")
// TODO xml validation
}
return toTrim
}
private Object extractServerValueFromBody(bodyValue) {
protected String getBodyAsString() {
Object bodyValue = extractServerValueFromBody(request.body.serverValue)
return trimRepeatedQuotes(new JsonOutput().toJson(bodyValue))
}
protected String trimRepeatedQuotes(String toTrim) {
return toTrim.startsWith('"') ? toTrim.replaceAll('"', '') : toTrim
}
protected Object extractServerValueFromBody(bodyValue) {
if (bodyValue instanceof GString) {
bodyValue = extractValue(bodyValue, { DslProperty dslProperty -> dslProperty.serverValue })
} else {
@@ -113,88 +119,86 @@ class SpockMethodBodyBuilder {
return bodyValue
}
private String buildUrl(Request request) {
if (request.url)
return request.url.serverValue;
if (request.urlPath)
return buildUrlFromUrlPath(request.urlPath)
throw new IllegalStateException("URL is not set!")
}
private String buildUrlFromUrlPath(UrlPath urlPath) {
String params = urlPath.queryParameters.parameters
.findAll(this.&allowedQueryParameter)
.inject([]) { result, param ->
result << "${param.name}=${resolveParamValue(param).toString()}"
}.join('&')
return "$urlPath.serverValue?$params"
}
private boolean allowedQueryParameter(QueryParameter param) {
protected boolean allowedQueryParameter(QueryParameter param) {
return allowedQueryParameter(param.serverValue)
}
private boolean allowedQueryParameter(MatchingStrategy matchingStrategy) {
protected boolean allowedQueryParameter(MatchingStrategy matchingStrategy) {
return matchingStrategy.type != MatchingStrategy.Type.ABSENT
}
private boolean allowedQueryParameter(Object o) {
protected boolean allowedQueryParameter(Object o) {
return true
}
private String resolveParamValue(QueryParameter param) {
resolveParamValue(param.serverValue)
protected String resolveParamValue(QueryParameter param) {
return resolveParamValue(param.serverValue)
}
private String resolveParamValue(Object value) {
value.toString()
protected String resolveParamValue(Object value) {
return value.toString()
}
private String resolveParamValue(MatchingStrategy matchingStrategy) {
matchingStrategy.serverValue.toString()
protected String resolveParamValue(MatchingStrategy matchingStrategy) {
return matchingStrategy.serverValue.toString()
}
private void processBodyElement(BlockBuilder blockBuilder, String property, def value) {
if (value instanceof String) {
if (value.startsWith('$')) {
value = value.substring(1).replaceAll('\\$value', "responseBody$property")
blockBuilder.addLine(value)
} else {
blockBuilder.addLine("responseBody$property == \"${value}\"")
}
} else if (value instanceof Map) {
processMapElement(value, blockBuilder, property)
} else if (value instanceof Map.Entry) {
processEntryElement(blockBuilder, property, value)
} else if (value instanceof List) {
processArrayElements(value, property, blockBuilder)
} else if (value instanceof Pattern) {
blockBuilder.addLine("responseBody$property ==~ java.util.regex.Pattern.compile('${value}')")
} else if (value instanceof DslProperty) {
processBodyElement(blockBuilder, property, value.serverValue)
} else if (value instanceof ExecutionProperty) {
ExecutionProperty exec = (ExecutionProperty) value
blockBuilder.addLine("${exec.insertValue("responseBody$property")}")
protected void processBodyElement(BlockBuilder blockBuilder, String property, Object value) {
blockBuilder.addLine("responseBody$property == ${value}")
}
protected void processBodyElement(BlockBuilder blockBuilder, String property, String value) {
if (value.startsWith('$')) {
value = value.substring(1).replaceAll('\\$value', "responseBody$property")
blockBuilder.addLine(value)
} else {
blockBuilder.addLine("responseBody$property == ${value}")
blockBuilder.addLine("responseBody$property == \"${value}\"")
}
}
private void processMapElement(def value, BlockBuilder blockBuilder, String property) {
value.each { entry -> processEntryElement(blockBuilder, property, entry) }
protected void processBodyElement(BlockBuilder blockBuilder, String property, Pattern pattern) {
blockBuilder.addLine("responseBody$property ==~ java.util.regex.Pattern.compile('${pattern.pattern()}')")
}
private def processEntryElement(BlockBuilder blockBuilder, String property, def entry) {
return processBodyElement(blockBuilder, property + "." + entry.key, entry.value)
protected void processBodyElement(BlockBuilder blockBuilder, String property, DslProperty dslProperty) {
processBodyElement(blockBuilder, property, dslProperty.serverValue)
}
private void processArrayElements(List responseBody, String property, BlockBuilder blockBuilder) {
responseBody.eachWithIndex {
listElement, listIndex ->
listElement.each { entry ->
String prop = "$property[$listIndex]" ?: ''
processBodyElement(blockBuilder, prop, entry)
}
protected void processBodyElement(BlockBuilder blockBuilder, String property, ExecutionProperty exec) {
blockBuilder.addLine("${exec.insertValue("responseBody$property")}")
}
protected void processBodyElement(BlockBuilder blockBuilder, String property, Map.Entry entry) {
processBodyElement(blockBuilder, property + "." + entry.key, entry.value)
}
protected void processBodyElement(BlockBuilder blockBuilder, String property, Map map) {
map.each {
processBodyElement(blockBuilder, property, it)
}
}
protected void processBodyElement(BlockBuilder blockBuilder, String property, List list) {
list.eachWithIndex { listElement, listIndex ->
String prop = "$property[$listIndex]" ?: ''
processBodyElement(blockBuilder, prop, listElement)
}
}
protected ContentType getRequestContentType() {
ContentType contentType = recognizeContentTypeFromHeader(request.headers)
if (contentType == ContentType.UNKNOWN) {
contentType = recognizeContentTypeFromContent(request.body.serverValue)
}
return contentType
}
protected ContentType getResponseContentType() {
ContentType contentType = recognizeContentTypeFromHeader(response.headers)
if (contentType == ContentType.UNKNOWN) {
contentType = recognizeContentTypeFromContent(response.body.serverValue)
}
return contentType
}
}

View File

@@ -4,5 +4,5 @@ package io.codearte.accurest.config
* @author Jakub Kubrynski
*/
enum TestMode {
MOCKMVC, EXPLICIT
MOCKMVC, EXPLICIT, JAXRSCLIENT
}

View File

@@ -1,5 +1,15 @@
package io.codearte.accurest.util
enum ContentType {
JSON, XML, UNKNOWN
JSON("application/json"),
XML("application/xml"),
UNKNOWN("application/octet-stream")
final String mimeType
ContentType(String mimeType) {
this.mimeType = mimeType
}
}

View File

@@ -0,0 +1,362 @@
package io.codearte.accurest.builder
import io.codearte.accurest.dsl.GroovyDsl
import spock.lang.Issue
import spock.lang.Specification
class JaxRsClientSpockMethodBuilderSpec extends Specification {
def "should generate assertions for simple response body"() {
given:
GroovyDsl contractDsl = GroovyDsl.make {
request {
method "GET"
url "test"
}
response {
status 200
body """{
"property1": "a",
"property2": "b"
}"""
}
}
JaxRsClientSpockMethodBodyBuilder builder = new JaxRsClientSpockMethodBodyBuilder(contractDsl)
BlockBuilder blockBuilder = new BlockBuilder(" ")
when:
builder.appendTo(blockBuilder)
then:
blockBuilder.toString().contains("responseBody.property1 == \"a\"")
blockBuilder.toString().contains("responseBody.property2 == \"b\"")
}
@Issue("#79")
def "should generate assertions for simple response body constructed from map with a list"() {
given:
GroovyDsl contractDsl = GroovyDsl.make {
request {
method "GET"
url "test"
}
response {
status 200
body(
property1: 'a',
property2: [
[a: 'sth'],
[b: 'sthElse']
]
)
}
}
JaxRsClientSpockMethodBodyBuilder builder = new JaxRsClientSpockMethodBodyBuilder(contractDsl)
BlockBuilder blockBuilder = new BlockBuilder(" ")
when:
builder.appendTo(blockBuilder)
then:
blockBuilder.toString().contains("responseBody.property1 == \"a\"")
blockBuilder.toString().contains("responseBody.property2[0].a == \"sth\"")
blockBuilder.toString().contains("responseBody.property2[1].b == \"sthElse\"")
}
@Issue("#82")
def "should generate proper request when body constructed from map with a list"() {
given:
GroovyDsl contractDsl = GroovyDsl.make {
request {
method "GET"
url "test"
body(
items: ['HOP']
)
}
response {
status 200
}
}
JaxRsClientSpockMethodBodyBuilder builder = new JaxRsClientSpockMethodBodyBuilder(contractDsl)
BlockBuilder blockBuilder = new BlockBuilder(" ")
when:
builder.appendTo(blockBuilder)
then:
blockBuilder.toString().contains("entity('{\"items\":[\"HOP\"]}', 'application/json')")
}
@Issue("#88")
def "should generate proper request when body constructed from GString"() {
given:
GroovyDsl contractDsl = GroovyDsl.make {
request {
method "GET"
url "test"
body(
"property1=VAL1"
)
}
response {
status 200
}
}
JaxRsClientSpockMethodBodyBuilder builder = new JaxRsClientSpockMethodBodyBuilder(contractDsl)
BlockBuilder blockBuilder = new BlockBuilder(" ")
when:
builder.appendTo(blockBuilder)
then:
blockBuilder.toString().contains("entity('property1=VAL1', 'application/octet-stream')")
}
def "should generate assertions for array in response body"() {
given:
GroovyDsl contractDsl = GroovyDsl.make {
request {
method "GET"
url "test"
}
response {
status 200
body """[
{
"property1": "a"
},
{
"property2": "b"
}]"""
}
}
JaxRsClientSpockMethodBodyBuilder builder = new JaxRsClientSpockMethodBodyBuilder(contractDsl)
BlockBuilder blockBuilder = new BlockBuilder(" ")
when:
builder.appendTo(blockBuilder)
then:
blockBuilder.toString().contains("responseBody[0].property1 == \"a\"")
blockBuilder.toString().contains("responseBody[1].property2 == \"b\"")
}
def "should generate assertions for array inside response body element"() {
given:
GroovyDsl contractDsl = GroovyDsl.make {
request {
method "GET"
url "test"
}
response {
status 200
body """{
"property1": [
{ "property2": "test1"},
{ "property3": "test2"}
]
}"""
}
}
JaxRsClientSpockMethodBodyBuilder builder = new JaxRsClientSpockMethodBodyBuilder(contractDsl)
BlockBuilder blockBuilder = new BlockBuilder(" ")
when:
builder.appendTo(blockBuilder)
then:
blockBuilder.toString().contains("responseBody.property1[0].property2 == \"test1\"")
blockBuilder.toString().contains("responseBody.property1[1].property3 == \"test2\"")
}
def "should generate assertions for nested objects in response body"() {
given:
GroovyDsl contractDsl = GroovyDsl.make {
request {
method "GET"
url "test"
}
response {
status 200
body '''\
{
"property1": "a",
"property2": {"property3": "b"}
}
'''
}
}
JaxRsClientSpockMethodBodyBuilder builder = new JaxRsClientSpockMethodBodyBuilder(contractDsl)
BlockBuilder blockBuilder = new BlockBuilder(" ")
when:
builder.appendTo(blockBuilder)
then:
blockBuilder.toString().contains("responseBody.property1 == \"a\"")
blockBuilder.toString().contains("responseBody.property2.property3 == \"b\"")
}
def "should generate regex assertions for map objects in response body"() {
given:
GroovyDsl contractDsl = GroovyDsl.make {
request {
method "GET"
url "test"
}
response {
status 200
body(
property1: "a",
property2: value(
client('123'),
server(regex('[0-9]{3}'))
)
)
headers {
header('Content-Type': 'application/json')
}
}
}
JaxRsClientSpockMethodBodyBuilder builder = new JaxRsClientSpockMethodBodyBuilder(contractDsl)
BlockBuilder blockBuilder = new BlockBuilder(" ")
when:
builder.appendTo(blockBuilder)
then:
blockBuilder.toString().contains("responseBody.property1 == \"a\"")
blockBuilder.toString().contains("responseBody.property2 ==~ java.util.regex.Pattern.compile('[0-9]{3}')")
}
def "should generate regex assertions for string objects in response body"() {
given:
GroovyDsl contractDsl = GroovyDsl.make {
request {
method "GET"
url "test"
}
response {
status 200
body("""{"property1":"a","property2":"${value(client('123'), server(regex('[0-9]{3}')))}"}""")
headers {
header('Content-Type': 'application/json')
}
}
}
JaxRsClientSpockMethodBodyBuilder builder = new JaxRsClientSpockMethodBodyBuilder(contractDsl)
BlockBuilder blockBuilder = new BlockBuilder(" ")
when:
builder.appendTo(blockBuilder)
then:
blockBuilder.toString().contains("responseBody.property1 == \"a\"")
blockBuilder.toString().contains("responseBody.property2 ==~ java.util.regex.Pattern.compile('[0-9]{3}')")
}
def "should ignore 'Accept' header and use 'request' method"() {
given:
GroovyDsl contractDsl = GroovyDsl.make {
request {
method "GET"
url "test"
headers {
header("Accept", "text/plain")
}
}
response {
status 200
}
}
JaxRsClientSpockMethodBodyBuilder builder = new JaxRsClientSpockMethodBodyBuilder(contractDsl)
BlockBuilder blockBuilder = new BlockBuilder(" ")
when:
builder.appendTo(blockBuilder)
then:
blockBuilder.toString().contains("request('text/plain')")
}
def "should ignore 'Content-Type' header and use 'entity' method"() {
given:
GroovyDsl contractDsl = GroovyDsl.make {
request {
method "GET"
url "test"
headers {
header("Content-Type", "text/plain")
header("Timer", "123")
}
body ''
}
response {
status 200
}
}
JaxRsClientSpockMethodBodyBuilder builder = new JaxRsClientSpockMethodBodyBuilder(contractDsl)
BlockBuilder blockBuilder = new BlockBuilder(" ")
when:
builder.appendTo(blockBuilder)
then:
blockBuilder.toString().contains("entity('', 'text/plain')")
blockBuilder.toString().contains("header('Timer', '123')")
!blockBuilder.toString().contains("header('Content-Type'")
}
def "should generate a call with an url path and query parameters"() {
given:
GroovyDsl contractDsl = GroovyDsl.make {
request {
method 'GET'
urlPath('/users') {
queryParameters {
parameter 'limit': $(client(equalTo("20")), server(equalTo("10")))
parameter 'offset': $(client(containing("20")), server(equalTo("20")))
parameter 'filter': "email"
parameter 'sort': equalTo("name")
parameter 'search': $(client(notMatching(~/^\/[0-9]{2}$/)), server("55"))
parameter 'age': $(client(notMatching("^\\w*\$")), server("99"))
parameter 'name': $(client(matching("Denis.*")), server("Denis.Stepanov"))
parameter 'email': "bob@email.com"
parameter 'hello': $(client(matching("Denis.*")), server(absent()))
parameter 'hello': absent()
}
}
}
response {
status 200
body """
{
"property1": "a",
"property2": "b"
}
"""
}
}
JaxRsClientSpockMethodBodyBuilder builder = new JaxRsClientSpockMethodBodyBuilder(contractDsl)
BlockBuilder blockBuilder = new BlockBuilder(" ")
when:
builder.appendTo(blockBuilder)
def spockTest = blockBuilder.toString()
then:
spockTest.contains("queryParam('limit', '10'")
spockTest.contains("queryParam('offset', '20'")
spockTest.contains("queryParam('filter', 'email'")
spockTest.contains("queryParam('sort', 'name'")
spockTest.contains("queryParam('search', '55'")
spockTest.contains("queryParam('age', '99'")
spockTest.contains("queryParam('name', 'Denis.Stepanov'")
spockTest.contains("queryParam('email', 'bob@email.com'")
spockTest.contains('responseBody.property1 == "a"')
spockTest.contains('responseBody.property2 == "b"')
}
def "should generate test for empty body"() {
given:
GroovyDsl contractDsl = GroovyDsl.make {
request {
method('POST')
url("/ws/payments")
body("")
}
response {
status 406
}
}
JaxRsClientSpockMethodBodyBuilder builder = new JaxRsClientSpockMethodBodyBuilder(contractDsl)
BlockBuilder blockBuilder = new BlockBuilder(" ")
when:
builder.appendTo(blockBuilder)
def spockTest = blockBuilder.toString()
then:
spockTest.contains("entity('', 'application/octet-stream')")
}
}

View File

@@ -7,7 +7,7 @@ import spock.lang.Specification
/**
* @author Jakub Kubrynski
*/
class SpockMethodBuilderSpec extends Specification {
class MockMvcSpockMethodBuilderSpec extends Specification {
def "should generate assertions for simple response body"() {
given:
@@ -24,7 +24,7 @@ class SpockMethodBuilderSpec extends Specification {
}"""
}
}
SpockMethodBodyBuilder builder = new SpockMethodBodyBuilder(contractDsl)
MockMvcSpockMethodBodyBuilder builder = new MockMvcSpockMethodBodyBuilder(contractDsl)
BlockBuilder blockBuilder = new BlockBuilder(" ")
when:
builder.appendTo(blockBuilder)
@@ -52,7 +52,7 @@ class SpockMethodBuilderSpec extends Specification {
)
}
}
SpockMethodBodyBuilder builder = new SpockMethodBodyBuilder(contractDsl)
MockMvcSpockMethodBodyBuilder builder = new MockMvcSpockMethodBodyBuilder(contractDsl)
BlockBuilder blockBuilder = new BlockBuilder(" ")
when:
builder.appendTo(blockBuilder)
@@ -77,7 +77,7 @@ class SpockMethodBuilderSpec extends Specification {
status 200
}
}
SpockMethodBodyBuilder builder = new SpockMethodBodyBuilder(contractDsl)
MockMvcSpockMethodBodyBuilder builder = new MockMvcSpockMethodBodyBuilder(contractDsl)
BlockBuilder blockBuilder = new BlockBuilder(" ")
when:
builder.appendTo(blockBuilder)
@@ -100,7 +100,7 @@ class SpockMethodBuilderSpec extends Specification {
status 200
}
}
SpockMethodBodyBuilder builder = new SpockMethodBodyBuilder(contractDsl)
MockMvcSpockMethodBodyBuilder builder = new MockMvcSpockMethodBodyBuilder(contractDsl)
BlockBuilder blockBuilder = new BlockBuilder(" ")
when:
builder.appendTo(blockBuilder)
@@ -126,7 +126,7 @@ class SpockMethodBuilderSpec extends Specification {
}]"""
}
}
SpockMethodBodyBuilder builder = new SpockMethodBodyBuilder(contractDsl)
MockMvcSpockMethodBodyBuilder builder = new MockMvcSpockMethodBodyBuilder(contractDsl)
BlockBuilder blockBuilder = new BlockBuilder(" ")
when:
builder.appendTo(blockBuilder)
@@ -152,7 +152,7 @@ class SpockMethodBuilderSpec extends Specification {
}"""
}
}
SpockMethodBodyBuilder builder = new SpockMethodBodyBuilder(contractDsl)
MockMvcSpockMethodBodyBuilder builder = new MockMvcSpockMethodBodyBuilder(contractDsl)
BlockBuilder blockBuilder = new BlockBuilder(" ")
when:
builder.appendTo(blockBuilder)
@@ -178,7 +178,7 @@ class SpockMethodBuilderSpec extends Specification {
'''
}
}
SpockMethodBodyBuilder builder = new SpockMethodBodyBuilder(contractDsl)
MockMvcSpockMethodBodyBuilder builder = new MockMvcSpockMethodBodyBuilder(contractDsl)
BlockBuilder blockBuilder = new BlockBuilder(" ")
when:
builder.appendTo(blockBuilder)
@@ -210,7 +210,7 @@ class SpockMethodBuilderSpec extends Specification {
}
}
SpockMethodBodyBuilder builder = new SpockMethodBodyBuilder(contractDsl)
MockMvcSpockMethodBodyBuilder builder = new MockMvcSpockMethodBodyBuilder(contractDsl)
BlockBuilder blockBuilder = new BlockBuilder(" ")
when:
builder.appendTo(blockBuilder)
@@ -236,7 +236,7 @@ class SpockMethodBuilderSpec extends Specification {
}
}
SpockMethodBodyBuilder builder = new SpockMethodBodyBuilder(contractDsl)
MockMvcSpockMethodBodyBuilder builder = new MockMvcSpockMethodBodyBuilder(contractDsl)
BlockBuilder blockBuilder = new BlockBuilder(" ")
when:
builder.appendTo(blockBuilder)
@@ -275,7 +275,7 @@ class SpockMethodBuilderSpec extends Specification {
"""
}
}
SpockMethodBodyBuilder builder = new SpockMethodBodyBuilder(contractDsl)
MockMvcSpockMethodBodyBuilder builder = new MockMvcSpockMethodBodyBuilder(contractDsl)
BlockBuilder blockBuilder = new BlockBuilder(" ")
when:
builder.appendTo(blockBuilder)
@@ -298,7 +298,7 @@ class SpockMethodBuilderSpec extends Specification {
status 406
}
}
SpockMethodBodyBuilder builder = new SpockMethodBodyBuilder(contractDsl)
MockMvcSpockMethodBodyBuilder builder = new MockMvcSpockMethodBodyBuilder(contractDsl)
BlockBuilder blockBuilder = new BlockBuilder(" ")
when:
builder.appendTo(blockBuilder)

View File

@@ -0,0 +1,21 @@
package io.codearte.accurest.plugin
import nebula.test.IntegrationSpec
import spock.lang.Stepwise
@Stepwise
class SampleJerseyProjectSpec extends IntegrationSpec {
void setup() {
copyResources("functionalTest/sampleJerseyProject", "")
runTasksSuccessfully('clean') //delete accidental output when previously importing SimpleBoot into Idea to tweak it
}
def "should pass basic flow"() {
given:
assert fileExists('build.gradle')
expect:
runTasksSuccessfully('check')
}
}

View File

@@ -0,0 +1,95 @@
buildscript {
repositories {
mavenLocal()
mavenCentral()
}
dependencies {
classpath "org.springframework.boot:spring-boot-gradle-plugin:1.2.1.RELEASE"
}
}
ext {
spockVersion = '0.7-groovy-2.0'
restAssuredVersion = '2.4.0'
accurestStubsBaseDirectory = 'src/test/resources/stubs'
}
subprojects {
apply plugin: 'groovy'
repositories {
mavenCentral()
mavenLocal()
}
dependencies {
testCompile "org.codehaus.groovy:groovy-all:2.3.7"
testCompile "org.spockframework:spock-core:$spockVersion"
testCompile("junit:junit:4.12")
testCompile('com.github.tomakehurst:wiremock:1.52') {
exclude group: 'org.mortbay.jetty', module: 'servlet-api'
}
}
}
configure([project(':fraudDetectionService'), project(':loanApplicationService')]) {
apply plugin: 'spring-boot'
apply plugin: 'accurest'
ext {
wireMockStubsOutputDirRoot = file("${project.buildDir}/production/${project.name}-stubs/")
wireMockStubsOutputDir = new File(wireMockStubsOutputDirRoot, 'mappings/')
}
accurest {
targetFramework = 'Spock'
testMode = 'JaxRsClient'
baseClassForTests = 'com.blogspot.toomuchcoding.MvcSpec'
contractsDslDir = file("${project.projectDir.absolutePath}/mappings/")
generatedTestSourcesDir = file("${project.buildDir}/generated-sources/")
stubsOutputDir = wireMockStubsOutputDir
}
jar {
version = '0.0.1'
}
dependencies {
compile "javax.ws.rs:javax.ws.rs-api:2.0.1"
compile 'org.glassfish.jersey.containers:jersey-container-jetty-http:2.15'
compile('org.springframework.boot:spring-boot-starter-jersey:1.2.5.RELEASE') {
exclude module: "spring-boot-starter-tomcat"
}
compile 'org.springframework.boot:spring-boot-starter-jetty:1.2.5.RELEASE'
testRuntime "org.spockframework:spock-spring:$spockVersion"
compile 'org.glassfish.jersey.connectors:jersey-apache-connector:2.15'
testCompile 'org.springframework:spring-test:4.1.7.RELEASE'
}
task cleanup(type: Delete) {
delete 'src/test/resources/mappings', 'src/test/resources/stubs'
}
clean.dependsOn('cleanup')
}
configure(project(':fraudDetectionService')) {
test.dependsOn('generateWireMockClientStubs')
}
configure(project(':loanApplicationService')) {
task copyCollaboratorStubs(type: Copy) {
File fraudBuildDir = project(':fraudDetectionService').buildDir
from(new File(fraudBuildDir, "/production/${project(':fraudDetectionService').name}-stubs/"))
into "src/test/resources/"
}
generateAccurest.dependsOn('copyCollaboratorStubs')
}

View File

@@ -0,0 +1,27 @@
io.codearte.accurest.dsl.GroovyDsl.make {
request {
method """PUT"""
url """/fraudcheck"""
body("""
{
"clientPesel":"${value(client(regex('[0-9]{10}')), server('1234567890'))}",
"loanAmount":99999}
"""
)
headers {
header("""Content-Type""", """application/vnd.fraud.v1+json""")
}
}
response {
status 200
body( """{
"fraudCheckStatus": "${value(client('FRAUD'), server(regex('[A-Z]{5}')))}",
"rejectionReason": "Amount too high"
}""")
headers {
header('Content-Type': 'application/vnd.fraud.v1+json')
}
}
}

View File

@@ -0,0 +1,28 @@
io.codearte.accurest.dsl.GroovyDsl.make {
request {
method 'PUT'
url '/fraudcheck'
body("""
{
"clientPesel":"${value(client(regex('[0-9]{10}')), server('1234567890'))}",
"loanAmount":123.123
}
"""
)
headers {
header('Content-Type', 'application/vnd.fraud.v1+json')
}
}
response {
status 200
body(
fraudCheckStatus: "OK",
rejectionReason: $(client(null), server(execute('assertThatRejectionReasonIsNull($it)')))
)
headers {
header('Content-Type': 'application/vnd.fraud.v1+json')
}
}
}

View File

@@ -0,0 +1,25 @@
package com.blogspot.toomuchcoding.frauddetection;
import org.glassfish.jersey.client.HttpUrlConnectorProvider;
import org.glassfish.jersey.server.ResourceConfig;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@EnableAutoConfiguration
@ComponentScan
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@Bean
ResourceConfig resourceConfig() {
return ResourceConfig.forApplication(new FraudRestApplication());
}
}

View File

@@ -0,0 +1,38 @@
package com.blogspot.toomuchcoding.frauddetection;
import com.blogspot.toomuchcoding.frauddetection.model.FraudCheck;
import com.blogspot.toomuchcoding.frauddetection.model.FraudCheckResult;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import javax.ws.rs.*;
import java.math.BigDecimal;
import static com.blogspot.toomuchcoding.frauddetection.model.FraudCheckStatus.FRAUD;
import static com.blogspot.toomuchcoding.frauddetection.model.FraudCheckStatus.OK;
@Controller
@Path("/")
public class FraudDetectionController {
private static final String FRAUD_SERVICE_JSON_VERSION_1 = "application/vnd.fraud.v1+json";
private static final String NO_REASON = null;
private static final String AMOUNT_TOO_HIGH = "Amount too high";
private static final BigDecimal MAX_AMOUNT = new BigDecimal("5000");
@PUT
@Path("/fraudcheck")
@Produces(FRAUD_SERVICE_JSON_VERSION_1)
@Consumes(FRAUD_SERVICE_JSON_VERSION_1)
public FraudCheckResult fraudCheck(@RequestBody(required = false) FraudCheck fraudCheck) {
if (amountGreaterThanThreshold(fraudCheck)) {
return new FraudCheckResult(FRAUD, AMOUNT_TOO_HIGH);
}
return new FraudCheckResult(OK, NO_REASON);
}
private boolean amountGreaterThanThreshold(FraudCheck fraudCheck) {
return MAX_AMOUNT.compareTo(fraudCheck.getLoanAmount()) < 0;
}
}

View File

@@ -0,0 +1,13 @@
package com.blogspot.toomuchcoding.frauddetection;
import java.util.Collections;
import java.util.Set;
public class FraudRestApplication extends javax.ws.rs.core.Application {
@Override
public Set<Class<?>> getClasses() {
return Collections.<Class<?>>singleton(FraudDetectionController.class);
}
}

View File

@@ -0,0 +1,29 @@
package com.blogspot.toomuchcoding.frauddetection.model;
import java.math.BigDecimal;
public class FraudCheck {
private String clientPesel;
private BigDecimal loanAmount;
public FraudCheck() {
}
public String getClientPesel() {
return clientPesel;
}
public void setClientPesel(String clientPesel) {
this.clientPesel = clientPesel;
}
public BigDecimal getLoanAmount() {
return loanAmount;
}
public void setLoanAmount(BigDecimal loanAmount) {
this.loanAmount = loanAmount;
}
}

View File

@@ -0,0 +1,32 @@
package com.blogspot.toomuchcoding.frauddetection.model;
public class FraudCheckResult {
private FraudCheckStatus fraudCheckStatus;
private String rejectionReason;
public FraudCheckResult() {
}
public FraudCheckResult(FraudCheckStatus fraudCheckStatus, String rejectionReason) {
this.fraudCheckStatus = fraudCheckStatus;
this.rejectionReason = rejectionReason;
}
public FraudCheckStatus getFraudCheckStatus() {
return fraudCheckStatus;
}
public void setFraudCheckStatus(FraudCheckStatus fraudCheckStatus) {
this.fraudCheckStatus = fraudCheckStatus;
}
public String getRejectionReason() {
return rejectionReason;
}
public void setRejectionReason(String rejectionReason) {
this.rejectionReason = rejectionReason;
}
}

View File

@@ -0,0 +1,5 @@
package com.blogspot.toomuchcoding.frauddetection.model;
public enum FraudCheckStatus {
OK, FRAUD
}

View File

@@ -0,0 +1,57 @@
package com.blogspot.toomuchcoding
import com.blogspot.toomuchcoding.frauddetection.Application
import com.blogspot.toomuchcoding.frauddetection.FraudRestApplication
import org.eclipse.jetty.server.Server
import org.glassfish.jersey.apache.connector.ApacheConnectorProvider
import org.glassfish.jersey.client.ClientConfig
import org.glassfish.jersey.jetty.JettyHttpContainerFactory
import org.glassfish.jersey.server.ResourceConfig
import org.springframework.context.annotation.AnnotationConfigApplicationContext
import spock.lang.Shared
import spock.lang.Specification
import javax.ws.rs.client.Client
import javax.ws.rs.client.ClientBuilder
import javax.ws.rs.client.WebTarget
import javax.ws.rs.core.UriBuilder
import static org.springframework.util.SocketUtils.findAvailableTcpPort
abstract class MvcSpec extends Specification {
@Shared
WebTarget webTarget
@Shared
private Server server
@Shared
private Client client
def setupSpec() {
URI baseUri = UriBuilder.fromUri("http://localhost").port(findAvailableTcpPort(8000)).build()
ResourceConfig resourceConfig = ResourceConfig.forApplication(new FraudRestApplication())
resourceConfig.property("contextConfig", new AnnotationConfigApplicationContext(Application))
server = JettyHttpContainerFactory.createServer(baseUri, resourceConfig, true)
ClientConfig clientConfig = new ClientConfig()
clientConfig.connectorProvider(new ApacheConnectorProvider())
client = ClientBuilder.newClient(clientConfig)
webTarget = client.target(baseUri)
server.start()
}
def cleanupSpec() {
client?.close()
server?.stop()
}
void assertThatRejectionReasonIsNull(def rejectionReason) {
assert !rejectionReason
}
}

View File

@@ -0,0 +1,6 @@
#Wed Jan 28 00:32:44 CET 2015
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=http\://services.gradle.org/distributions/gradle-2.4-all.zip

View File

@@ -0,0 +1,164 @@
#!/usr/bin/env bash
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn ( ) {
echo "$*"
}
die ( ) {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
esac
# For Cygwin, ensure paths are in UNIX format before anything is touched.
if $cygwin ; then
[ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
fi
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >&-
APP_HOME="`pwd -P`"
cd "$SAVED" >&-
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
function splitJvmOpts() {
JVM_OPTS=("$@")
}
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"

View File

@@ -0,0 +1,90 @@
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windowz variants
if not "%OS%" == "Windows_NT" goto win9xME_args
if "%@eval[2+2]" == "4" goto 4NT_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
goto execute
:4NT_args
@rem Get arguments from the 4NT Shell from JP Software
set CMD_LINE_ARGS=%$
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

View File

@@ -0,0 +1,17 @@
package com.blogspot.toomuchcoding.frauddetection;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@EnableAutoConfiguration
@ComponentScan
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}

View File

@@ -0,0 +1,62 @@
package com.blogspot.toomuchcoding.frauddetection;
import com.blogspot.toomuchcoding.frauddetection.model.FraudCheckStatus;
import com.blogspot.toomuchcoding.frauddetection.model.FraudServiceRequest;
import com.blogspot.toomuchcoding.frauddetection.model.FraudServiceResponse;
import com.blogspot.toomuchcoding.frauddetection.model.LoanApplication;
import com.blogspot.toomuchcoding.frauddetection.model.LoanApplicationResult;
import com.blogspot.toomuchcoding.frauddetection.model.LoanApplicationStatus;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
@Service
public class LoanApplicationService {
private static final String FRAUD_SERVICE_JSON_VERSION_1 =
"application/vnd.fraud.v1+json";
private final RestTemplate restTemplate;
public LoanApplicationService() {
this.restTemplate = new RestTemplate();
}
public LoanApplicationResult loanApplication(LoanApplication loanApplication) {
FraudServiceRequest request =
new FraudServiceRequest(loanApplication);
FraudServiceResponse response =
sendRequestToFraudDetectionService(request);
return buildResponseFromFraudResult(response);
}
private FraudServiceResponse sendRequestToFraudDetectionService(
FraudServiceRequest request) {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.add(HttpHeaders.CONTENT_TYPE, FRAUD_SERVICE_JSON_VERSION_1);
ResponseEntity<FraudServiceResponse> response =
restTemplate.exchange("http://localhost:8080/fraudcheck", HttpMethod.PUT,
new HttpEntity<>(request, httpHeaders),
FraudServiceResponse.class);
return response.getBody();
}
private LoanApplicationResult buildResponseFromFraudResult(FraudServiceResponse response) {
LoanApplicationStatus applicationStatus = null;
if (FraudCheckStatus.OK == response.getFraudCheckStatus()) {
applicationStatus = LoanApplicationStatus.LOAN_APPLIED;
} else if (FraudCheckStatus.FRAUD == response.getFraudCheckStatus()) {
applicationStatus = LoanApplicationStatus.LOAN_APPLICATION_REJECTED;
}
return new LoanApplicationResult(applicationStatus, response.getRejectionReason());
}
}

View File

@@ -0,0 +1,14 @@
package com.blogspot.toomuchcoding.frauddetection.model;
public class Client {
private String pesel;
public String getPesel() {
return pesel;
}
public void setPesel(String pesel) {
this.pesel = pesel;
}
}

View File

@@ -0,0 +1,5 @@
package com.blogspot.toomuchcoding.frauddetection.model;
public enum FraudCheckStatus {
OK, FRAUD
}

View File

@@ -0,0 +1,34 @@
package com.blogspot.toomuchcoding.frauddetection.model;
import java.math.BigDecimal;
public class FraudServiceRequest {
private String clientPesel;
private BigDecimal loanAmount;
public FraudServiceRequest() {
}
public FraudServiceRequest(LoanApplication loanApplication) {
this.clientPesel = loanApplication.getClient().getPesel();
this.loanAmount = loanApplication.getAmount();
}
public String getClientPesel() {
return clientPesel;
}
public void setClientPesel(String clientPesel) {
this.clientPesel = clientPesel;
}
public BigDecimal getLoanAmount() {
return loanAmount;
}
public void setLoanAmount(BigDecimal loanAmount) {
this.loanAmount = loanAmount;
}
}

View File

@@ -0,0 +1,27 @@
package com.blogspot.toomuchcoding.frauddetection.model;
public class FraudServiceResponse {
private FraudCheckStatus fraudCheckStatus;
private String rejectionReason;
public FraudServiceResponse() {
}
public FraudCheckStatus getFraudCheckStatus() {
return fraudCheckStatus;
}
public void setFraudCheckStatus(FraudCheckStatus fraudCheckStatus) {
this.fraudCheckStatus = fraudCheckStatus;
}
public String getRejectionReason() {
return rejectionReason;
}
public void setRejectionReason(String rejectionReason) {
this.rejectionReason = rejectionReason;
}
}

View File

@@ -0,0 +1,36 @@
package com.blogspot.toomuchcoding.frauddetection.model;
import java.math.BigDecimal;
public class LoanApplication {
private Client client;
private BigDecimal amount;
private String loanApplicationId;
public Client getClient() {
return client;
}
public void setClient(Client client) {
this.client = client;
}
public BigDecimal getAmount() {
return amount;
}
public void setAmount(BigDecimal amount) {
this.amount = amount;
}
public String getLoanApplicationId() {
return loanApplicationId;
}
public void setLoanApplicationId(String loanApplicationId) {
this.loanApplicationId = loanApplicationId;
}
}

View File

@@ -0,0 +1,32 @@
package com.blogspot.toomuchcoding.frauddetection.model;
public class LoanApplicationResult {
private LoanApplicationStatus loanApplicationStatus;
private String rejectionReason;
public LoanApplicationResult() {
}
public LoanApplicationResult(LoanApplicationStatus loanApplicationStatus, String rejectionReason) {
this.loanApplicationStatus = loanApplicationStatus;
this.rejectionReason = rejectionReason;
}
public LoanApplicationStatus getLoanApplicationStatus() {
return loanApplicationStatus;
}
public void setLoanApplicationStatus(LoanApplicationStatus loanApplicationStatus) {
this.loanApplicationStatus = loanApplicationStatus;
}
public String getRejectionReason() {
return rejectionReason;
}
public void setRejectionReason(String rejectionReason) {
this.rejectionReason = rejectionReason;
}
}

View File

@@ -0,0 +1,5 @@
package com.blogspot.toomuchcoding.frauddetection.model;
public enum LoanApplicationStatus {
LOAN_APPLIED, LOAN_APPLICATION_REJECTED
}

View File

@@ -0,0 +1,50 @@
package com.blogspot.toomuchcoding
import com.blogspot.toomuchcoding.frauddetection.Application
import com.blogspot.toomuchcoding.frauddetection.LoanApplicationService
import com.blogspot.toomuchcoding.frauddetection.model.Client
import com.blogspot.toomuchcoding.frauddetection.model.LoanApplication
import com.blogspot.toomuchcoding.frauddetection.model.LoanApplicationResult
import com.blogspot.toomuchcoding.frauddetection.model.LoanApplicationStatus
import com.github.tomakehurst.wiremock.junit.WireMockClassRule
import org.junit.ClassRule
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.SpringApplicationContextLoader
import org.springframework.test.context.ContextConfiguration
import spock.lang.Shared
import spock.lang.Specification
@ContextConfiguration(loader = SpringApplicationContextLoader, classes = Application)
class LoanApplicationServiceSpec extends Specification {
@ClassRule
@Shared
WireMockClassRule wireMockRule = new WireMockClassRule()
@Autowired
LoanApplicationService sut
def 'should successfully apply for loan'() {
given:
LoanApplication application =
new LoanApplication(client: new Client(pesel: '1234567890'), amount: 123.123)
when:
LoanApplicationResult loanApplication = sut.loanApplication(application)
then:
loanApplication.loanApplicationStatus == LoanApplicationStatus.LOAN_APPLIED
loanApplication.rejectionReason == null
}
def 'should be rejected due to abnormal loan amount'() {
given:
LoanApplication application =
new LoanApplication(client: new Client(pesel: '1234567890'), amount: 99_999)
when:
LoanApplicationResult loanApplication = sut.loanApplication(application)
then:
loanApplication.loanApplicationStatus == LoanApplicationStatus.LOAN_APPLICATION_REJECTED
loanApplication.rejectionReason == 'Amount too high'
}
}

View File

@@ -0,0 +1,23 @@
{
"request": {
"method": "PUT",
"headers": {
"Content-Type": {
"equalTo": "application/vnd.fraud.v1+json"
}
},
"url": "/fraudcheck",
"bodyPatterns": [
{
"matches": "\\s*\\{\\s*\"clientPesel\"\\s*:\\s*\"?[0-9]{10}\"?\\s*,\\s*\"loanAmount\"\\s*:\\s*\"?99999\"?\\s*\\}\\s*"
}
]
},
"response": {
"status": 200,
"headers": {
"Content-Type": "application/vnd.fraud.v1+json"
},
"body": "{\"fraudCheckStatus\":\"FRAUD\",\"rejectionReason\":\"Amount too high\"}"
}
}

View File

@@ -0,0 +1,23 @@
{
"request": {
"method": "PUT",
"headers": {
"Content-Type": {
"equalTo": "application/vnd.fraud.v1+json"
}
},
"url": "/fraudcheck",
"bodyPatterns": [
{
"matches": "\\s*\\{\\s*\"clientPesel\"\\s*:\\s*\"?[0-9]{10}\"?\\s*,\\s*\"loanAmount\"\\s*:\\s*\"?123.123\"?\\s*\\}\\s*"
}
]
},
"response": {
"status": 200,
"headers": {
"Content-Type": "application/vnd.fraud.v1+json"
},
"body": "{\"fraudCheckStatus\":\"OK\",\"rejectionReason\":null}"
}
}

View File

@@ -0,0 +1,2 @@
include ':fraudDetectionService'
include ':loanApplicationService'