diff --git a/consumer/src/main/kotlin/au/com/dius/pact/consumer/dsl/PactBuilder.kt b/consumer/src/main/kotlin/au/com/dius/pact/consumer/dsl/PactBuilder.kt index 08994a6db..70f7d0497 100644 --- a/consumer/src/main/kotlin/au/com/dius/pact/consumer/dsl/PactBuilder.kt +++ b/consumer/src/main/kotlin/au/com/dius/pact/consumer/dsl/PactBuilder.kt @@ -16,6 +16,7 @@ import au.com.dius.pact.core.model.InteractionMarkup import au.com.dius.pact.core.model.OptionalBody import au.com.dius.pact.core.model.PactSpecVersion import au.com.dius.pact.core.model.Provider +import au.com.dius.pact.core.model.ProviderState import au.com.dius.pact.core.model.UnknownPactSource import au.com.dius.pact.core.model.V4Interaction import au.com.dius.pact.core.model.V4Pact @@ -41,6 +42,7 @@ import java.nio.file.Path import java.nio.file.Paths import kotlin.io.path.exists +@Suppress("TooManyFunctions") open class PactBuilder( var consumer: String = "consumer", var provider: String = "provider", @@ -49,6 +51,7 @@ open class PactBuilder( private val plugins: MutableList = mutableListOf() private val interactions: MutableList = mutableListOf() private var currentInteraction: V4Interaction? = null + private val providerStates: MutableList = mutableListOf() private val pluginConfiguration: MutableMap> = mutableMapOf() private val additionalMetadata: MutableMap = mutableMapOf() @@ -65,7 +68,7 @@ open class PactBuilder( } /** - * Use the old HTTP Pact DSL + * Use the old Message Pact DSL */ fun usingLegacyMessageDsl(): MessagePactBuilder { return MessagePactBuilder(pactVersion).consumer(consumer).hasPactWith(provider) @@ -111,6 +114,59 @@ open class PactBuilder( } } + /** + * Describe the state the provider needs to be in for the pact test to be verified. Any parameters for the provider + * state can be provided in the second parameter. + */ + @JvmOverloads + fun given(state: String, params: Map = emptyMap()): PactBuilder { + if (currentInteraction != null) { + currentInteraction!!.providerStates.add(ProviderState(state, params)) + } else { + providerStates.add(ProviderState(state, params)) + } + return this + } + + /** + * Describe the state the provider needs to be in for the pact test to be verified. + * + * @param firstKey Key of first parameter element + * @param firstValue Value of first parameter element + * @param paramsKeyValuePair Additional parameters in key-value pairs + */ + fun given(state: String, firstKey: String, firstValue: Any?, vararg paramsKeyValuePair: Any): PactBuilder { + require(paramsKeyValuePair.size % 2 == 0) { + "Pairs of key value should be provided, but there is one key without value." + } + val params = mutableMapOf(firstKey to firstValue) + var i = 0 + while (i < paramsKeyValuePair.size) { + params[paramsKeyValuePair[i].toString()] = paramsKeyValuePair[i + 1] + i += 2 + } + if (currentInteraction != null) { + currentInteraction!!.providerStates.add(ProviderState(state, params)) + } else { + providerStates.add(ProviderState(state, params)) + } + return this + } + + /** + * Describe the state the provider needs to be in for the pact test to be verified. + * + * @param params Additional parameters in key-value pairs + */ + fun given(state: String, vararg params: Pair): PactBuilder { + if (currentInteraction != null) { + currentInteraction!!.providerStates.add(ProviderState(state, params.toMap())) + } else { + providerStates.add(ProviderState(state, params.toMap())) + } + return this + } + /** * Adds an interaction with the given description and type. If interactionType is not specified (is the empty string) * will default to an HTTP interaction @@ -139,6 +195,12 @@ open class PactBuilder( TODO("Interactions of type '$interactionType' are not currently supported") } } + + if (providerStates.isNotEmpty()) { + currentInteraction!!.providerStates.addAll(providerStates) + providerStates.clear() + } + return this } @@ -155,7 +217,8 @@ open class PactBuilder( } /** - * Values to configure the interaction + * Values to configure the interaction. In the case of an interaction configured by a plugin, you need to follow + * the plugin documentation of what values must be specified here. */ fun with(values: Map): PactBuilder { require(currentInteraction != null) { @@ -216,6 +279,7 @@ open class PactBuilder( return this } + @Suppress("LongMethod") private fun setupMessageContents( contents: Any?, interaction: V4Interaction diff --git a/consumer/src/test/groovy/au/com/dius/pact/consumer/dsl/PactBuilderSpec.groovy b/consumer/src/test/groovy/au/com/dius/pact/consumer/dsl/PactBuilderSpec.groovy index bbe79eb0b..4aa771647 100644 --- a/consumer/src/test/groovy/au/com/dius/pact/consumer/dsl/PactBuilderSpec.groovy +++ b/consumer/src/test/groovy/au/com/dius/pact/consumer/dsl/PactBuilderSpec.groovy @@ -5,10 +5,11 @@ import au.com.dius.pact.core.model.HttpRequest import au.com.dius.pact.core.model.HttpResponse import au.com.dius.pact.core.model.OptionalBody import au.com.dius.pact.core.model.PactSpecVersion +import au.com.dius.pact.core.model.ProviderState import au.com.dius.pact.core.model.V4Pact +import kotlin.Pair import spock.lang.Issue import au.com.dius.pact.core.model.V4Interaction -import spock.lang.Ignore import spock.lang.Specification import spock.lang.Unroll @@ -64,8 +65,6 @@ class PactBuilderSpec extends Specification { builder.currentInteraction instanceof V4Interaction.SynchronousHttp } - @Ignore - // This test is currently failing due to dependency linking issues def 'supports configuring the HTTP interaction attributes'() { given: def builder = new PactBuilder('test', 'test', PactSpecVersion.V4) @@ -93,6 +92,29 @@ class PactBuilderSpec extends Specification { then: http.request == new HttpRequest('PUT', '/reports/report002.csv', [a: ['b']], ['x-a': ['b']], OptionalBody.body('"a"', ContentType.JSON)) - http.response == new HttpResponse(205, ['x-b': ['a']], OptionalBody.body('"b"', ContentType.JSON)) + http.response == new HttpResponse(200, ['x-b': ['b']], OptionalBody.body('"b"', ContentType.JSON)) + } + + @Issue('#1646') + def 'supports setting up provider states'() { + given: + def builder = new PactBuilder('test', 'test', PactSpecVersion.V4) + + when: + def pact = builder + .given('test1') + .given('test2', [a: 'b', c: 'd']) + .expectsToReceive('test interaction', '') + .given('test3', 'a', 100) + .given('test4', new Pair('a', 100), new Pair('b', 1000)) + .toPact() + + then: + pact.interactions.first().providerStates == [ + new ProviderState('test1'), + new ProviderState('test2', [a: 'b', c: 'd']), + new ProviderState('test3', [a: 100]), + new ProviderState('test4', [a: 100, b: 1000]) + ] } } diff --git a/core/model/src/main/kotlin/au/com/dius/pact/core/model/BaseInteraction.kt b/core/model/src/main/kotlin/au/com/dius/pact/core/model/BaseInteraction.kt index 9042d0701..d3fa2d119 100644 --- a/core/model/src/main/kotlin/au/com/dius/pact/core/model/BaseInteraction.kt +++ b/core/model/src/main/kotlin/au/com/dius/pact/core/model/BaseInteraction.kt @@ -5,7 +5,7 @@ import au.com.dius.pact.core.support.json.JsonValue abstract class BaseInteraction( override val interactionId: String? = null, override val description: String, - override val providerStates: List = listOf(), + override val providerStates: MutableList = mutableListOf(), override val comments: MutableMap = mutableMapOf() ) : Interaction { fun displayState(): String { diff --git a/core/model/src/main/kotlin/au/com/dius/pact/core/model/RequestResponseInteraction.kt b/core/model/src/main/kotlin/au/com/dius/pact/core/model/RequestResponseInteraction.kt index d25fde4c7..cd61f47d4 100644 --- a/core/model/src/main/kotlin/au/com/dius/pact/core/model/RequestResponseInteraction.kt +++ b/core/model/src/main/kotlin/au/com/dius/pact/core/model/RequestResponseInteraction.kt @@ -14,7 +14,7 @@ open class RequestResponseInteraction @JvmOverloads constructor( override val request: Request = Request(), override val response: Response = Response(), interactionId: String? = null -) : BaseInteraction(interactionId, description, providerStates), SynchronousRequestResponse { +) : BaseInteraction(interactionId, description, providerStates.toMutableList()), SynchronousRequestResponse { override fun toString() = "Interaction: $description\n\tin states ${displayState()}\nrequest:\n$request\n\nresponse:\n$response" diff --git a/core/model/src/main/kotlin/au/com/dius/pact/core/model/V4Pact.kt b/core/model/src/main/kotlin/au/com/dius/pact/core/model/V4Pact.kt index 9bf25c5f2..bedd84a7b 100644 --- a/core/model/src/main/kotlin/au/com/dius/pact/core/model/V4Pact.kt +++ b/core/model/src/main/kotlin/au/com/dius/pact/core/model/V4Pact.kt @@ -136,7 +136,7 @@ sealed class V4Interaction( val pluginConfiguration: MutableMap> = mutableMapOf(), var interactionMarkup: InteractionMarkup = InteractionMarkup(), var transport: String? = null -) : BaseInteraction(interactionId, description, providerStates, comments) { +) : BaseInteraction(interactionId, description, providerStates.toMutableList(), comments) { override fun conflictsWith(other: Interaction): Boolean { return false } diff --git a/core/model/src/main/kotlin/au/com/dius/pact/core/model/messaging/Message.kt b/core/model/src/main/kotlin/au/com/dius/pact/core/model/messaging/Message.kt index 942007910..3ba8229b1 100644 --- a/core/model/src/main/kotlin/au/com/dius/pact/core/model/messaging/Message.kt +++ b/core/model/src/main/kotlin/au/com/dius/pact/core/model/messaging/Message.kt @@ -37,7 +37,7 @@ class Message @JvmOverloads constructor( var generators: Generators = Generators(), var metadata: MutableMap = mutableMapOf(), interactionId: String? = null -) : BaseInteraction(interactionId, description, providerStates), MessageInteraction { +) : BaseInteraction(interactionId, description, providerStates.toMutableList()), MessageInteraction { fun contentsAsBytes() = when { isKafkaSchemaRegistryJson() -> KafkaSchemaRegistryWireFormatter.addMagicBytes(contents.orEmpty())