Skip to content

Commit

Permalink
fix: Support V2 format with header/query params with encoded paths
Browse files Browse the repository at this point in the history
  • Loading branch information
rholshausen committed Apr 22, 2024
1 parent f079041 commit 8e0f0c3
Show file tree
Hide file tree
Showing 5 changed files with 149 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,33 @@ const val PATH_SPECIAL_CHARS = "'[].@ \t\n"
const val EXP_ALLOWED_SPECIAL_CHARS = "-_:#@"

sealed class PathToken {
object Root : PathToken()
data class Field(val name: String) : PathToken()
data class Index(val index: Int) : PathToken()
object Star : PathToken()
object StarIndex : PathToken()
object Root : PathToken() {
override fun toString() = "$"
}

data class Field(val name: String) : PathToken() {
override fun toString(): String {
return if (StringUtils.containsAny(this.name, PATH_SPECIAL_CHARS)) {
"['${this.name}']"
} else {
this.name
}
}
}

data class Index(val index: Int) : PathToken() {
override fun toString(): String {
return "[${this.index}]"
}
}

object Star : PathToken() {
override fun toString() = "*"
}

object StarIndex : PathToken() {
override fun toString() = "[*]"
}
}

// string_path -> [^']+
Expand Down Expand Up @@ -196,3 +218,25 @@ fun constructPath(path: List<String>) =
constructValidPath(segment, path)
}
}

/**
* This will combine the path tokens into a valid path
*/
fun pathFromTokens(tokens: List<PathToken>): String {
return tokens.fold("") { acc, token ->
acc + when (token) {
PathToken.Root -> "$"
is PathToken.Field -> {
val s = token.toString()
if (acc.isEmpty() || s.startsWith("[")) {
s
} else {
".$s"
}
}
is PathToken.Index -> "[${token.index}]"
PathToken.Star -> if (acc.isEmpty()) "*" else ".*"
PathToken.StarIndex -> "[*]"
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package au.com.dius.pact.core.model.matchingrules

import au.com.dius.pact.core.model.PactSpecVersion
import au.com.dius.pact.core.model.atLeast
import au.com.dius.pact.core.model.pathFromTokens
import au.com.dius.pact.core.model.parsePath
import au.com.dius.pact.core.support.Json
import au.com.dius.pact.core.support.json.JsonValue
import io.github.oshai.kotlinlogging.KLogging
Expand Down Expand Up @@ -29,17 +31,17 @@ class MatchingRulesImpl : MatchingRules {

fun fromV2Json(json: JsonValue.Object) {
json.entries.forEach { (key, value) ->
val path = key.split('.')
val path = parsePath(key)
if (key.startsWith("$.body")) {
if (key == "$.body") {
addV2Rule("body", "$", Json.toMap(value))
} else {
addV2Rule("body", "$${key.substring(6)}", Json.toMap(value))
}
} else if (key.startsWith("$.headers")) {
addV2Rule("header", path[2], Json.toMap(value))
addV2Rule("header", pathFromTokens(path.drop(2)), Json.toMap(value))
} else {
addV2Rule(path[1], if (path.size > 2) path[2] else null, Json.toMap(value))
addV2Rule(path[1].toString(), if (path.size > 2) pathFromTokens(path.drop(2)) else null, Json.toMap(value))
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package au.com.dius.pact.core.model

import au.com.dius.pact.core.model.matchingrules.MatchingRuleCategory
import au.com.dius.pact.core.model.matchingrules.MatchingRuleGroup
import au.com.dius.pact.core.model.matchingrules.MatchingRulesImpl
import au.com.dius.pact.core.model.matchingrules.RegexMatcher
import au.com.dius.pact.core.model.matchingrules.RuleLogic
import au.com.dius.pact.core.model.matchingrules.TypeMatcher
import au.com.dius.pact.core.model.messaging.MessagePact
import au.com.dius.pact.core.support.json.JsonParser
import com.amazonaws.services.s3.AmazonS3
Expand Down Expand Up @@ -74,6 +77,26 @@ class PactReaderSpec extends Specification {
'PATCH']
}

def 'loads a pact with V2 version and encoded paths for query parameters and headers'() {
given:
def pactUrl = PactReaderSpec.classLoader.getResource('v2-pact-encoded-query-headers.json')

when:
def pact = DefaultPactReader.INSTANCE.loadPact(pactUrl)
def interaction = pact.interactions.first()

then:
pact instanceof RequestResponsePact
interaction instanceof RequestResponseInteraction
interaction.request.headers == ['se-api-token': ['15123-234234-234asd'], 'se-token': ['ABC123']]
interaction.request.matchingRules.rules == [
header: new MatchingRuleCategory('header', [
'se-api-token': new MatchingRuleGroup([TypeMatcher.INSTANCE]),
'se-token[0]': new MatchingRuleGroup([TypeMatcher.INSTANCE])
])
]
}

def 'loads a pact with V3 version using V3 loader'() {
given:
def pactUrl = PactReaderSpec.classLoader.getResource('v3-pact.json')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -184,4 +184,24 @@ class PathExpressionsSpec extends Specification {
'a b' | 'a.b' || "a.b['a b']"
'$a.b' | 'a.b' || "a.b['\$a.b']"
}

def 'construct path from tokens'() {
expect:
PathExpressionsKt.pathFromTokens(tokens) == result

where:

tokens | result
[] | ''
[PathToken.Root.INSTANCE] | '$'
[PathToken.Root.INSTANCE, new PathToken.Field('a')] | '$.a'
[new PathToken.Field('a')] | 'a'
[PathToken.Root.INSTANCE, new PathToken.Field('a.b')] | '$[\'a.b\']'
[new PathToken.Field('a.b')] | '[\'a.b\']'
[PathToken.Root.INSTANCE, PathToken.Star.INSTANCE] | '$.*'
[PathToken.Star.INSTANCE] | '*'
[PathToken.Root.INSTANCE, PathToken.StarIndex.INSTANCE] | '$[*]'
[PathToken.StarIndex.INSTANCE] | '[*]'
[PathToken.Root.INSTANCE, new PathToken.Field('a'), PathToken.StarIndex.INSTANCE] | '$.a[*]'
}
}
52 changes: 52 additions & 0 deletions core/model/src/test/resources/v2-pact-encoded-query-headers.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
{
"consumer": {
"name": "Consumer"
},
"interactions": [
{
"description": "a request for bookings count",
"request": {
"headers": {
"se-api-token": "15123-234234-234asd",
"se-token": "ABC123"
},
"matchingRules": {
"$.header['se-api-token']": {
"match": "type"
},
"$.header['se-token'][0]": {
"match": "type"
}
},
"method": "GET",
"path": "/v4/users/1234/bookings/count"
},
"response": {
"body": {
"cancelled": 1,
"past": 2,
"upcoming": 3
},
"headers": {
"Content-Type": "application/json; charset=utf-8"
},
"status": 200
}
}
],
"metadata": {
"pact-js": {
"version": "11.0.2"
},
"pactRust": {
"ffi": "0.4.0",
"models": "1.0.4"
},
"pactSpecification": {
"version": "2.0.0"
}
},
"provider": {
"name": "Provider"
}
}

0 comments on commit 8e0f0c3

Please sign in to comment.