Skip to content

Commit

Permalink
feat(compatibility-suite): Implement V3 mathing rule and generator sc…
Browse files Browse the repository at this point in the history
…enarios
  • Loading branch information
rholshausen committed Jul 20, 2023
1 parent 2daa449 commit bf66443
Show file tree
Hide file tree
Showing 9 changed files with 267 additions and 72 deletions.
122 changes: 122 additions & 0 deletions compatibility-suite/src/test/groovy/steps/v3/Generators.groovy
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
package steps.v3

import au.com.dius.pact.core.model.IRequest
import au.com.dius.pact.core.model.Request
import au.com.dius.pact.core.model.JsonUtils
import au.com.dius.pact.core.model.generators.GeneratorTestMode
import au.com.dius.pact.core.support.json.JsonParser
import au.com.dius.pact.core.support.json.JsonValue
import io.cucumber.datatable.DataTable
import io.cucumber.java.en.Given
import io.cucumber.java.en.Then
import io.cucumber.java.en.When

import static steps.shared.SharedSteps.configureBody

@SuppressWarnings('SpaceAfterOpeningBrace')
class Generators {
Request request
IRequest generatedRequest
Map<String, Object> context = [:]
GeneratorTestMode testMode = GeneratorTestMode.Provider
JsonValue originalJson
JsonValue generatedJson

@Given('a request configured with the following generators:')
void a_request_configured_with_the_following_generators(DataTable dataTable) {
request = new Request()
def entry = dataTable.entries().first()
if (entry['body']) {
configureBody(entry['body'], request)
}
if (entry['generators']) {
JsonValue json
if (entry['generators'].startsWith('JSON:')) {
json = JsonParser.INSTANCE.parseString(entry['generators'][5..-1])
} else {
File contents = new File("pact-compatibility-suite/fixtures/${entry['generators']}")
contents.withInputStream {
json = JsonParser.INSTANCE.parseStream(it)
}
}
request.generators = au.com.dius.pact.core.model.generators.Generators.fromJson(json)
}
}

@Given('the generator test mode is set as {string}')
void the_generator_test_mode_is_set_as(String mode) {
testMode = mode == 'Consumer' ? GeneratorTestMode.Consumer : GeneratorTestMode.Provider
}

@When('the request is prepared for use')
void the_request_prepared_for_use() {
generatedRequest = request.generatedRequest(context, testMode)
originalJson = JsonParser.INSTANCE.parseString(request.body.valueAsString())
generatedJson = JsonParser.INSTANCE.parseString(generatedRequest.body.valueAsString())
}

@When('the request is prepared for use with a {string} context:')
void the_request_is_prepared_for_use_with_a_context(String type, DataTable dataTable) {
context[type] = JsonParser.parseString(dataTable.values().first()).asObject().entries
generatedRequest = request.generatedRequest(context, testMode)
originalJson = JsonParser.INSTANCE.parseString(request.body.valueAsString())
generatedJson = JsonParser.INSTANCE.parseString(generatedRequest.body.valueAsString())
}

@Then('the value for {string} will have been replaced with a {string}')
void the_value_for_will_have_been_replaced_with_a_value(String path, String type) {
def originalElement = JsonUtils.INSTANCE.fetchPath(originalJson, path)
def element = JsonUtils.INSTANCE.fetchPath(generatedJson, path)
assert originalElement != element
switch (type) {
case 'integer' -> {
assert element.type() == 'Integer'
assert element.toString() ==~ /\d+/
}
case 'decimal number' -> {
assert element.type() == 'Decimal'
assert element.toString() ==~ /\d+\.\d+/
}
case 'hexadecimal number' -> {
assert element.type() == 'String'
assert element.toString() ==~ /[a-fA-F0-9]+/
}
case 'random string' -> {
assert element.type() == 'String'
}
case 'string from the regex' -> {
assert element.type() == 'String'
assert element.toString() ==~ /\d{1,8}/
}
case 'date' -> {
assert element.type() == 'String'
assert element.toString() ==~ /\d{4}-\d{2}-\d{2}/
}
case 'time' -> {
assert element.type() == 'String'
assert element.toString() ==~ /\d{2}:\d{2}:\d{2}\.\d{1,9}/
}
case 'date-time' -> {
assert element.type() == 'String'
assert element.toString() ==~ /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{1,9}/
}
case 'UUID' -> {
assert element.type() == 'String'
UUID.fromString(element.toString())
}
case 'boolean' -> {
assert element.type() ==~ /True|False/
}
default -> throw new AssertionError("Invalid type: $type")
}
}

@Then('the value for {string} will have been replaced with {string}')
void the_value_for_will_have_been_replaced_with_value(String path, String value) {
def originalElement = JsonUtils.INSTANCE.fetchPath(originalJson, path)
def element = JsonUtils.INSTANCE.fetchPath(generatedJson, path)
assert originalElement != element
assert element.type() == 'String'
assert element.toString() == value
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ class HttpMatching {
if (entry['matching rules']) {
JsonValue json
if (entry['matching rules'].startsWith('JSON:')) {
json = JsonParser.INSTANCE.parseString(entry['body'][5..-1])
json = JsonParser.INSTANCE.parseString(entry['matching rules'][5..-1])
} else {
File contents = new File("pact-compatibility-suite/fixtures/${entry['matching rules']}")
contents.withInputStream {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package au.com.dius.pact.core.model

import au.com.dius.pact.core.model.generators.JsonQueryResult
import au.com.dius.pact.core.support.json.JsonValue
import org.apache.commons.collections4.IteratorUtils

/**
* Utility functions for JSON
*/
object JsonUtils {
/**
* Fetches an element from the JSON given the path in Pact matching rule form
*/
fun fetchPath(json: JsonValue?, path: String): JsonValue? {
val pathExp = parsePath(path)
return if (json != null) {
val bodyJson = JsonQueryResult(json)
queryObjectGraph(pathExp.iterator(), bodyJson) {
it.jsonValue
}
} else null
}

@Suppress("ReturnCount")
fun <T> queryObjectGraph(pathExp: Iterator<PathToken>, body: JsonQueryResult, fn: (JsonQueryResult) -> T?): T? {
var bodyCursor = body
while (pathExp.hasNext()) {
val cursorValue = bodyCursor.value
when (val token = pathExp.next()) {
is PathToken.Field -> if (cursorValue is JsonValue.Object && cursorValue.has(token.name)) {
bodyCursor = JsonQueryResult(cursorValue[token.name], token.name, bodyCursor.jsonValue)
} else {
return null
}
is PathToken.Index -> if (cursorValue is JsonValue.Array && cursorValue.values.size > token.index) {
bodyCursor = JsonQueryResult(cursorValue[token.index], token.index, bodyCursor.jsonValue)
} else {
return null
}
is PathToken.Star -> if (cursorValue is JsonValue.Object) {
val pathIterator = IteratorUtils.toList(pathExp)
cursorValue.entries.forEach { (key, value) ->
queryObjectGraph(pathIterator.iterator(), JsonQueryResult(value, key, cursorValue), fn)
}
return null
} else {
return null
}
is PathToken.StarIndex -> if (cursorValue is JsonValue.Array) {
val pathIterator = IteratorUtils.toList(pathExp)
cursorValue.values.forEachIndexed { index, item ->
queryObjectGraph(pathIterator.iterator(), JsonQueryResult(item, index, cursorValue), fn)
}
return null
} else {
return null
}
else -> {}
}
}

return fn(bodyCursor)
}
}
Loading

0 comments on commit bf66443

Please sign in to comment.