Skip to content

Commit

Permalink
Handled some edge cases in the convergence algorithm
Browse files Browse the repository at this point in the history
  • Loading branch information
joelrosario committed Jun 1, 2020
1 parent 181ce7b commit c42896b
Show file tree
Hide file tree
Showing 2 changed files with 143 additions and 23 deletions.
39 changes: 20 additions & 19 deletions core/src/main/kotlin/run/qontract/core/value/TypeDeclaration.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,44 +4,45 @@ import run.qontract.core.pattern.*

data class TypeDeclaration(val typeValue: String, val types: Map<String, Pattern> = emptyMap(), val collidingName: String? = null)

fun convergeTypeDeclarations(converged: TypeDeclaration, current: TypeDeclaration): TypeDeclaration {
fun convergeTypeDeclarations(accumulator: TypeDeclaration, newPattern: TypeDeclaration): TypeDeclaration {
return try {
val differences = converged.types.filterKeys { it !in current.types }.plus(current.types.filterKeys { it !in converged.types })
val differences = accumulator.types.filterKeys { it !in newPattern.types }.plus(newPattern.types.filterKeys { it !in accumulator.types })

val similarities = converged.types.filterKeys { it in current.types }.mapValues {
val (pattern1, pattern2) = listOf(converged, current).map { typeDeclaration -> typeDeclaration.types.getValue(it.key) as TabularPattern }
val similarities = accumulator.types.filterKeys { it in newPattern.types }.mapValues {
val (pattern1, pattern2) = listOf(accumulator, newPattern).map { typeDeclaration -> typeDeclaration.types.getValue(it.key) as TabularPattern }
converge(pattern1, pattern2)
}

TypeDeclaration(converged.typeValue, differences.plus(similarities))
TypeDeclaration(accumulator.typeValue, differences.plus(similarities))
} catch(e: ShortCircuitException) {
println(e.localizedMessage)
converged
accumulator
}
}

fun converge(pattern1: TabularPattern, pattern2: TabularPattern): TabularPattern {
val json1 = pattern1.pattern
val json2 = pattern2.pattern
fun converge(accumulator: TabularPattern, newPattern: TabularPattern): TabularPattern {
val json1 = accumulator.pattern
val json2 = newPattern.pattern

val missingIn2 = json1.filterKeys { it !in json2 }.mapKeys { "${it.key}?" }
val missingIn1 = json2.filterKeys { it !in json1 }.mapKeys { "${it.key}?" }
val missingIn2 = json1.filterKeys { withoutOptionality(it) !in json2 }.mapKeys { "${withoutOptionality(it.key)}?" }

val common = json1.filterKeys { it in json2 }.mapValues {
val json1KeysWithoutOptionality = json1.keys.map { withoutOptionality(it) }
val missingIn1 = json2.filterKeys { it !in json1KeysWithoutOptionality }.mapKeys { "${it.key}?" }

val common = json1.filterKeys { withoutOptionality(it) in json2 }.mapValues {
val val1 = json1.getValue(it.key) as DeferredPattern
val val2 = json2.getValue(it.key) as DeferredPattern
val val2 = json2.getValue(withoutOptionality(it.key)) as DeferredPattern

when {
val1 == val2 -> val1
val1.pattern == "(null)" -> DeferredPattern("(${withoutPatternDelimiters(val2.pattern)}?)", val1.key)
val2.pattern == "(null)" -> DeferredPattern("(${withoutPatternDelimiters(val1.pattern)}?)", val1.key)
val1.pattern == "(null)" && val2.pattern == "(null)" -> DeferredPattern("(null)")
withoutOptionality(withoutPatternDelimiters(val1.pattern)) == withoutPatternDelimiters(val2.pattern) -> val1
val1.pattern == "(null)" -> DeferredPattern("(${withoutPatternDelimiters(val2.pattern)}?)")
val2.pattern == "(null)" -> DeferredPattern("(${withoutOptionality(withoutPatternDelimiters(val1.pattern))}?)")
else -> throw(ShortCircuitException("Found two different types (${val1.pattern} and ${val2.pattern}) in one of the lists, can't converge on a common type for it"))
}
}

return TabularPattern(common.plus(missingIn1).plus(missingIn2))
}

class ShortCircuitException(message: String) : Throwable() {

}
class ShortCircuitException(message: String) : Throwable()
127 changes: 123 additions & 4 deletions core/src/test/kotlin/run/qontract/mock/ScenarioStubKtTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -390,7 +390,7 @@ internal class ScenarioStubKtTest {
}

@Test
fun `converts array of json objects request body where the first contains a key not in the other to gherkin`() {
fun `converts array of json objects in the request body where the first contains a key not in the other to gherkin`() {
val mockText = """
{
"http-request": {
Expand Down Expand Up @@ -427,7 +427,7 @@ internal class ScenarioStubKtTest {
}

@Test
fun `converts array of json objects request body where the second contains a key not in the other to gherkin`() {
fun `converts array of json objects in the request body where the second contains a key not in the other to gherkin`() {
val mockText = """
{
"http-request": {
Expand Down Expand Up @@ -464,7 +464,7 @@ internal class ScenarioStubKtTest {
}

@Test
fun `converts array of json objects request body where the keys are identical but a value in the first is to gherkin`() {
fun `converts array of 2 json objects in the request body where a value in the second is null to gherkin`() {
val mockText = """
{
"http-request": {
Expand Down Expand Up @@ -501,7 +501,126 @@ internal class ScenarioStubKtTest {
}

@Test
fun `converts array of json objects request body where the keys are identical but a value in the second is to gherkin`() {
fun `converts array of 3 json objects in the request body where a value in the first is null to gherkin`() {
val mockText = """
{
"http-request": {
"method": "POST",
"path": "/square",
"body": [
{
"name": null
},
{
"name": "John Doe",
"address": null
},
{
"name": "John Doe",
"address": "High Street"
}
]
},
"http-response": {
"status": 200,
"body": 100
}
}
""".trim()

val mock = mockFromJSON(jsonStringToValueMap((mockText)))
validateStubAndQontract(mock.request, mock.response, """Feature: New Feature
Scenario: New scenario
Given type RequestBody
| name | (string?) |
| address? | (string?) |
When POST /square
And request-body (RequestBody*)
Then status 200
And response-body (number)""")
}

@Test
fun `converts array of 3 json objects in the request body where a value in the first and second is null to gherkin`() {
val mockText = """
{
"http-request": {
"method": "POST",
"path": "/square",
"body": [
{
"name": null
},
{
"name": null,
"address": null
},
{
"name": "John Doe",
"address": "High Street"
}
]
},
"http-response": {
"status": 200,
"body": 100
}
}
""".trim()

val mock = mockFromJSON(jsonStringToValueMap((mockText)))
validateStubAndQontract(mock.request, mock.response, """Feature: New Feature
Scenario: New scenario
Given type RequestBody
| name | (string?) |
| address? | (string?) |
When POST /square
And request-body (RequestBody*)
Then status 200
And response-body (number)""")
}
@Test
fun `converts array of 3 json objects in the request body where a value in the first and third is null to gherkin`() {
val mockText = """
{
"http-request": {
"method": "POST",
"path": "/square",
"body": [
{
"name": null
},
{
"name": "John Doe"
},
{
"name": null
}
]
},
"http-response": {
"status": 200,
"body": 100
}
}
""".trim()

val mock = mockFromJSON(jsonStringToValueMap((mockText)))
validateStubAndQontract(mock.request, mock.response, """Feature: New Feature
Scenario: New scenario
Given type RequestBody
| name | (string?) |
When POST /square
And request-body (RequestBody*)
Then status 200
And response-body (number)""")
}

@Test
fun `converts array of json objects in the request body where the keys are identical but a value in the second is to gherkin`() {
val mockText = """
{
"http-request": {
Expand Down

0 comments on commit c42896b

Please sign in to comment.