Skip to content

Commit ba845e8

Browse files
committed
fix: Support provider tests with multiple targets using different transports #1819
1 parent ae9b230 commit ba845e8

File tree

11 files changed

+107
-11
lines changed

11 files changed

+107
-11
lines changed

consumer/groovy/src/main/kotlin/au/com/dius/pact/consumer/groovy/messaging/SynchronousMessageBuilder.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ open class SynchronousMessageBuilder(
120120

121121
override fun addPluginConfiguration(matcher: ContentMatcher, pactConfiguration: Map<String, JsonValue>) {
122122
if (pluginConfiguration.containsKey(matcher.pluginName)) {
123-
pluginConfiguration[matcher.pluginName].deepMerge(pactConfiguration)
123+
pluginConfiguration[matcher.pluginName] = pluginConfiguration[matcher.pluginName].deepMerge(pactConfiguration)
124124
} else {
125125
pluginConfiguration[matcher.pluginName] = pactConfiguration.toMutableMap()
126126
}

consumer/src/main/kotlin/au/com/dius/pact/consumer/dsl/PactBuilder.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -315,7 +315,7 @@ open class PactBuilder(
315315

316316
override fun addPluginConfiguration(matcher: ContentMatcher, pactConfiguration: Map<String, JsonValue>) {
317317
if (pluginConfiguration.containsKey(matcher.pluginName)) {
318-
pluginConfiguration[matcher.pluginName].deepMerge(pactConfiguration)
318+
pluginConfiguration[matcher.pluginName] = pluginConfiguration[matcher.pluginName].deepMerge(pactConfiguration)
319319
} else {
320320
pluginConfiguration[matcher.pluginName] = pactConfiguration.toMutableMap()
321321
}
@@ -392,7 +392,7 @@ open class PactBuilder(
392392
logger.debug { "Plugin config: $config" }
393393

394394
if (config.interactionConfiguration.isNotEmpty()) {
395-
interaction.addPluginConfiguration(matcher.pluginName, config.interactionConfiguration)
395+
interaction.addPluginConfiguration(matcher.pluginName, part.transformConfig(config.interactionConfiguration))
396396
}
397397
if (config.pactConfiguration.isNotEmpty()) {
398398
addPluginConfiguration(matcher, config.pactConfiguration)

core/model/src/main/kotlin/au/com/dius/pact/core/model/HttpPart.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,13 @@ interface IHttpPart {
3535
fun setupGenerators(category: Category, context: Map<String, Any>): Map<String, Generator>
3636

3737
fun hasHeader(name: String): Boolean
38+
39+
/**
40+
* Allows the part of the interaction to transform the config so that it is keyed correctly. For instance,
41+
* an HTTP interaction may have both a request and response body from a plugin. This allows the request and
42+
* response parts to set the config for the correct part of the interaction.
43+
*/
44+
fun transformConfig(config: MutableMap<String, JsonValue>): Map<String, JsonValue> = config
3845
}
3946

4047
/**

core/model/src/main/kotlin/au/com/dius/pact/core/model/V4HttpParts.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,10 @@ data class HttpRequest @JvmOverloads constructor(
9797

9898
override fun hasHeader(name: String) = headers.any { (key, _) -> key.lowercase() == name }
9999

100+
override fun transformConfig(config: MutableMap<String, JsonValue>): Map<String, JsonValue> {
101+
return mapOf("request" to JsonValue.Object(config))
102+
}
103+
100104
companion object {
101105
@JvmStatic
102106
fun fromJson(json: JsonValue): HttpRequest {
@@ -227,6 +231,10 @@ data class HttpResponse @JvmOverloads constructor(
227231

228232
override fun copyResponse() = this.copy()
229233

234+
override fun transformConfig(config: MutableMap<String, JsonValue>): Map<String, JsonValue> {
235+
return mapOf("response" to JsonValue.Object(config))
236+
}
237+
230238
companion object {
231239
fun fromJson(json: JsonValue): HttpResponse {
232240
val status = when {

core/model/src/main/kotlin/au/com/dius/pact/core/model/V4Pact.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ sealed class V4Interaction(
180180
*/
181181
fun addPluginConfiguration(plugin: String, config: Map<String, JsonValue>) {
182182
if (pluginConfiguration.containsKey(plugin)) {
183-
pluginConfiguration[plugin]!!.deepMerge(config)
183+
pluginConfiguration[plugin] = pluginConfiguration[plugin]!!.deepMerge(config)
184184
} else {
185185
pluginConfiguration[plugin] = config.toMutableMap()
186186
}

provider/junit5/src/main/kotlin/au/com/dius/pact/provider/junit5/PactVerificationContext.kt

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,23 @@ data class PactVerificationContext @JvmOverloads constructor(
9393
interactionMessage += " [PENDING]"
9494
}
9595

96+
val targetForInteraction = currentTarget()
97+
if (targetForInteraction == null) {
98+
val transport = interaction.asV4Interaction().transport ?: "http"
99+
val message = "Did not find a test target to execute for the interaction transport '" +
100+
transport + "'"
101+
return listOf(
102+
VerificationResult.Failed(
103+
message, interactionMessage,
104+
mapOf(
105+
interaction.interactionId.orEmpty() to
106+
listOf(VerificationFailureType.InvalidInteractionFailure(message))
107+
),
108+
consumer.pending
109+
)
110+
)
111+
}
112+
96113
when (providerInfo.verificationType) {
97114
null, PactVerification.REQUEST_RESPONSE -> {
98115
return try {
@@ -104,7 +121,7 @@ data class PactVerificationContext @JvmOverloads constructor(
104121
val pactPluginData = pact.asV4Pact().get()?.pluginData() ?: emptyList()
105122
val expectedResponse = DefaultResponseGenerator.generateResponse(reqResInteraction.response, context,
106123
GeneratorTestMode.Provider, pactPluginData, pluginData)
107-
val actualResponse = target.executeInteraction(client, request)
124+
val actualResponse = targetForInteraction.executeInteraction(client, request)
108125

109126
listOf(
110127
verifier!!.verifyRequestResponsePact(
@@ -148,7 +165,7 @@ data class PactVerificationContext @JvmOverloads constructor(
148165
)
149166
}
150167
return listOf(verifier!!.verifyInteractionViaPlugin(providerInfo, consumer, v4pact, interaction.asV4Interaction(),
151-
client, request, context + ("userConfig" to target.userConfig)))
168+
client, request, context + ("userConfig" to targetForInteraction.userConfig)))
152169
}
153170
else -> {
154171
return listOf(verifier!!.verifyResponseByInvokingProviderMethods(providerInfo, consumer, interaction,

provider/junit5/src/main/kotlin/au/com/dius/pact/provider/junit5/PluginTestTarget.kt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,13 @@ class PluginTestTarget(private val config: MutableMap<String, Any?> = mutableMap
110110
return false
111111
}
112112

113-
override fun supportsInteraction(interaction: Interaction) = interaction.isV4()
113+
override fun supportsInteraction(interaction: Interaction): Boolean {
114+
return interaction.isV4() &&
115+
(
116+
!config.containsKey("transport") ||
117+
config["transport"] == interaction.asV4Interaction().transport
118+
)
119+
}
114120

115121
override fun executeInteraction(client: Any?, request: Any?): ProviderResponse {
116122
return ProviderResponse()

provider/junit5/src/test/groovy/au/com/dius/pact/provider/junit5/PactVerificationContextSpec.groovy

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ class PactVerificationContextSpec extends Specification {
3939
}
4040
TestTarget target = Stub {
4141
executeInteraction(_, _) >> { throw new IOException('Boom!') }
42+
supportsInteraction(_) >> true
4243
}
4344
IProviderVerifier verifier = Stub()
4445
ValueResolver valueResolver = Stub()
@@ -65,6 +66,46 @@ class PactVerificationContextSpec extends Specification {
6566
context.testExecutionResult[0].failures['12345'][0] instanceof VerificationFailureType.ExceptionFailure
6667
}
6768

69+
@SuppressWarnings('LineLength')
70+
def 'sets the test result to an error result if no test target is found to execute the interaction'() {
71+
given:
72+
PactVerificationContext context
73+
ExtensionContext.Store store = Stub {
74+
get(_) >> { args ->
75+
if (args[0] == 'interactionContext') {
76+
context
77+
}
78+
}
79+
}
80+
ExtensionContext extContext = Stub {
81+
getStore(_) >> store
82+
}
83+
TestTarget target = Stub {
84+
supportsInteraction(_) >> false
85+
}
86+
IProviderVerifier verifier = Stub()
87+
ValueResolver valueResolver = Stub()
88+
IProviderInfo provider = Stub {
89+
getName() >> 'Stub'
90+
}
91+
IConsumerInfo consumer = new ConsumerInfo('Test')
92+
Interaction interaction = new RequestResponseInteraction('Test Interaction', [], new Request(),
93+
new Response(), '12345')
94+
def pact = new RequestResponsePact(new Provider(), new Consumer(), [interaction ])
95+
List<VerificationResult> testResults = []
96+
97+
context = new PactVerificationContext(store, extContext, target, verifier, valueResolver,
98+
provider, consumer, interaction, pact, testResults)
99+
100+
when:
101+
context.verifyInteraction()
102+
103+
then:
104+
thrown(AssertionError)
105+
context.testExecutionResult[0] instanceof VerificationResult.Failed
106+
context.testExecutionResult[0].description == "Did not find a test target to execute for the interaction transport 'http'"
107+
}
108+
68109
def 'only throw an exception if there are non-pending failures'() {
69110
given:
70111
PactVerificationContext context
@@ -80,6 +121,7 @@ class PactVerificationContextSpec extends Specification {
80121
}
81122
TestTarget target = Stub {
82123
executeInteraction(_, _) >> { throw new IOException('Boom!') }
124+
supportsInteraction(_) >> true
83125
}
84126
IProviderVerifier verifier = Stub()
85127
ValueResolver valueResolver = Stub()
@@ -125,6 +167,7 @@ class PactVerificationContextSpec extends Specification {
125167
}
126168
TestTarget target = Stub {
127169
executeInteraction(_, _) >> { throw new IOException('Boom!') }
170+
supportsInteraction(_) >> true
128171
}
129172
IProviderVerifier verifier = Mock()
130173
ValueResolver valueResolver = Stub()

provider/junit5/src/test/groovy/au/com/dius/pact/provider/junit5/PluginTestTargetSpec.groovy

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,19 @@ class PluginTestTargetSpec extends Specification {
3030
new V4Interaction.SynchronousHttp('test') | true
3131
}
3232

33+
def 'only supports interactions that have a matching transport'() {
34+
given:
35+
def interaction1 = new V4Interaction.SynchronousHttp('test')
36+
interaction1.transport = 'http'
37+
def interaction2 = new V4Interaction.SynchronousHttp('test')
38+
interaction2.transport = 'xttp'
39+
def pluginTarget = new PluginTestTarget([transport: 'xttp'])
40+
41+
expect:
42+
!pluginTarget.supportsInteraction(interaction1)
43+
pluginTarget.supportsInteraction(interaction2)
44+
}
45+
3346
def 'when calling a plugin, prepareRequest must merge the provider state test context config'() {
3447
given:
3548
def config = [

provider/junit5spring/src/main/kotlin/au/com/dius/pact/provider/spring/junit5/PactVerificationSpringExtension.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,10 @@ open class PactVerificationSpringExtension(
2323
override fun supportsParameter(parameterContext: ParameterContext, extensionContext: ExtensionContext): Boolean {
2424
val store = extensionContext.getStore(ExtensionContext.Namespace.create("pact-jvm"))
2525
val testContext = store.get("interactionContext") as PactVerificationContext
26+
val target = testContext.currentTarget()
2627
return when (parameterContext.parameter.type) {
27-
MockHttpServletRequestBuilder::class.java -> testContext.target is MockMvcTestTarget
28-
WebTestClient.RequestHeadersSpec::class.java -> testContext.target is WebFluxTarget
28+
MockHttpServletRequestBuilder::class.java -> target is MockMvcTestTarget
29+
WebTestClient.RequestHeadersSpec::class.java -> target is WebFluxTarget
2930
else -> super.supportsParameter(parameterContext, extensionContext)
3031
}
3132
}

provider/spring6/src/main/kotlin/au/com/dius/pact/provider/spring/spring6/PactVerificationSpring6Extension.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,10 @@ open class PactVerificationSpring6Extension(
2323
override fun supportsParameter(parameterContext: ParameterContext, extensionContext: ExtensionContext): Boolean {
2424
val store = extensionContext.getStore(ExtensionContext.Namespace.create("pact-jvm"))
2525
val testContext = store.get("interactionContext") as PactVerificationContext
26+
val target = testContext.currentTarget()
2627
return when (parameterContext.parameter.type) {
27-
MockHttpServletRequestBuilder::class.java -> testContext.target is Spring6MockMvcTestTarget
28-
WebTestClient.RequestHeadersSpec::class.java -> testContext.target is WebFluxSpring6Target
28+
MockHttpServletRequestBuilder::class.java -> target is Spring6MockMvcTestTarget
29+
WebTestClient.RequestHeadersSpec::class.java -> target is WebFluxSpring6Target
2930
else -> super.supportsParameter(parameterContext, extensionContext)
3031
}
3132
}

0 commit comments

Comments
 (0)