pact-foundation / pact-jvm

JVM version of Pact. Enables consumer driven contract testing, providing a mock service and DSL for the consumer project, and interaction playback and verification for the service provider project.
https://docs.pact.io
Apache License 2.0
1.08k stars 480 forks source link

How to get through authentication process using hasPactsFromPactBroker? #1787

Open vbutlerovskyi opened 7 months ago

vbutlerovskyi commented 7 months ago

Hi there,

I'm a beginner in PACT testing and I was given a challenge to have one up and running so I accepted it, albeit it's not going smoothly. Basically I'm trying to validate the JSON contract from our running broker between two of our services using hasPactsFromPactBroker() and it keeps giving me 401 no matter what.

I tried something like this:

@Property(name = "micronaut.server.port", value = "8080")
@MicronautTest(environments = ["test"])
class ProviderPactSpec extends Specification {

    @Shared
    ProviderInfo serviceProvider

    ProviderVerifier verifier

    def setupSpec() {
        serviceProvider = new ProviderInfo('service1')
        serviceProvider.protocol = 'https'
        serviceProvider.host = 'https://pact-broker.com'
        serviceProvider.port = 443
        serviceProvider.path = ''
        serviceProvider.insecure = true
        serviceProvider.hasPactsFromPactBroker('https://pact-broker.com/pacts/my_pact', Authorization: ['Bearer', 'token'])
        }

    def setup() {
        verifier = new ProviderVerifier()
    }

        @Unroll
    def "Provider Pact - With Consumer #consumer"() {
        expect:
        verifyConsumerPact(consumer) instanceof VerificationResult.Ok

        where:
        consumer << serviceProvider.consumers
    }

    private VerificationResult verifyConsumerPact(ConsumerInfo consumer) {
        verifier.initialiseReporters(serviceProvider)
        def result = verifier.runVerificationForConsumer([:], serviceProvider, consumer)

        if (result instanceof VerificationResult.Failed) {
            verifier.displayFailures([result])
        }

        result
    }

As a result, I keep getting 401 Unauthorized:

{"message":"Established active environments: [test]","timestamp":1712518131867,"log.level":"INFO","logger.name":"io.micronaut.context.DefaultApplicationContext$RuntimeConfiguredEnvironment","thread.name":"Test worker","class.name":"io.micronaut.test.extensions.spock.MicronautSpockExtension","method.name":"lambda$visitSpecAnnotation$3","line.number":108}
{"message":"Failed to fetch the root HAL document","timestamp":1712518133239,"log.level":"WARN","logger.name":"au.com.dius.pact.core.pactbroker.HalClient$Companion","thread.name":"Test worker","class.name":"au.com.dius.pact.provider.ProviderInfo","method.name":"hasPactsFromPactBroker","line.number":68}

java.lang.RuntimeException: Call to fetch pacts from Pact Broker failed with an exception
    at au.com.dius.pact.provider.ProviderInfo.hasPactsFromPactBrokerWithSelectorsV2(ProviderInfo.kt:140)
    at au.com.dius.pact.provider.ProviderInfo.hasPactsFromPactBrokerWithSelectorsV2(ProviderInfo.kt:105)
    at au.com.dius.pact.provider.ProviderInfo.hasPactsFromPactBrokerWithSelectors(ProviderInfo.kt:76)
    at au.com.dius.pact.provider.ProviderInfo.hasPactsFromPactBroker(ProviderInfo.kt:68)
Caused by: au.com.dius.pact.core.pactbroker.InvalidNavigationRequest: Failed to fetch the root HAL document
    at au.com.dius.pact.core.pactbroker.HalClient.navigate(HalClient.kt:255)
    at au.com.dius.pact.core.pactbroker.PactBrokerClient$fetchConsumersWithSelectorsV2$halClient$navigateResult$1.invoke(PactBrokerClient.kt:494)
    at au.com.dius.pact.core.support.KotlinLanguageSupportKt.handleWith(KotlinLanguageSupport.kt:35)
    at au.com.dius.pact.core.pactbroker.PactBrokerClient.fetchConsumersWithSelectorsV2(PactBrokerClient.kt:494)
    at au.com.dius.pact.provider.ProviderInfo.hasPactsFromPactBrokerWithSelectorsV2(ProviderInfo.kt:129)
    ... 4 more
Caused by: au.com.dius.pact.core.pactbroker.RequestFailedException: Request to path '/' failed with response 401
    at au.com.dius.pact.core.pactbroker.HalClient.handleHalResponse(HalClient.kt:347)
    at au.com.dius.pact.core.pactbroker.HalClient.access$handleHalResponse(HalClient.kt:164)
    at au.com.dius.pact.core.pactbroker.HalClient$getJson$1.invoke$lambda$0(HalClient.kt:318)
    at org.apache.hc.client5.http.impl.classic.CloseableHttpClient.execute(CloseableHttpClient.java:247)
    at org.apache.hc.client5.http.impl.classic.CloseableHttpClient.execute(CloseableHttpClient.java:188)
    at au.com.dius.pact.core.pactbroker.HalClient$getJson$1.invoke(HalClient.kt:317)
    at au.com.dius.pact.core.support.KotlinLanguageSupportKt.handleWith(KotlinLanguageSupport.kt:35)
    at au.com.dius.pact.core.pactbroker.HalClient.getJson(HalClient.kt:312)
    at au.com.dius.pact.core.pactbroker.HalClient.fetch(HalClient.kt:276)
    at au.com.dius.pact.core.pactbroker.HalClient.fetch(HalClient.kt:271)
    at au.com.dius.pact.core.pactbroker.HalClient.navigate(HalClient.kt:251)
    ... 8 more

I also tried another way using username and password plus specifying the path attribute deliberately:

    def setupSpec() {
        serviceProvider = new ProviderInfo('consumer')
        serviceProvider.protocol = 'https'
        serviceProvider.host = 'https://pact-broker.com'
        serviceProvider.port = 443
        serviceProvider.path = '/pacts/my_pact'
        serviceProvider.insecure = true
        serviceProvider.hasPactsFromPactBroker('https://pact-broker.com', authentication: ['Basic', 'username', 'password'])
}

And then the error looks slightly different yet the same:

Condition failed with Exception:

verifyConsumerPact(consumer) instanceof VerificationResult.Ok
|                  |         |
|                  |         class au.com.dius.pact.provider.VerificationResult$Ok
|                  ConsumerInfo(name='consumer', stateChange=null, stateChangeUsesBody=true, packagesToScan=[], verificationType=null, pactSource=BrokerUrlSource(url=https://pact-broker.com/pacts/my_pact, pactBrokerUrl=https://pact-broker.com, attributes={}, options={}, tag=null, result=PactBrokerResult(name=consumer, source=https://pact-broker.com/pacts/my_pact, pactBrokerUrl=https://pact-broker.com, pactFileAuthentication=[], notices=[], pending=false, tag=null, wip=false, usedNewEndpoint=false, auth=null)), notices=[], pending=false, wip=false)
au.com.dius.pact.core.pactbroker.RequestFailedException: Request to path 'https://pact-broker.com/pacts/my_pact' failed with response 401

I'm using these versions of dependencies in my build.gradle:

testImplementation("au.com.dius.pact.provider:junit5spring:4.6.8")
testImplementation "au.com.dius.pact.consumer:groovy:4.3.19"

It's worth mentioning that the very first version of request works well in Postman, here's the cURL:

curl --location 'https://pact-broker.com/pacts/my_pact' \
--header 'Authorization: Basic ${token}'

I believe I tried everything possible so any hint would be really appreciated!

Thanks in advance!

rholshausen commented 7 months ago

You're mixing versions, that may be causing the issue

testImplementation("au.com.dius.pact.provider:junit5spring:4.6.8")
testImplementation "au.com.dius.pact.consumer:groovy:4.3.19"
vbutlerovskyi commented 6 months ago

Hey @rholshausen, thanks for turnaround. Do you suggest to use either 4.6.8 or 4.3.19 in both places?

rholshausen commented 6 months ago

Yes, I would recommend 4.6.8 as 4.3.x is not maintained anymore.

vbutlerovskyi commented 6 months ago

Thanks again, I will try and write down my findings here

vbutlerovskyi commented 6 months ago

Hey @rholshausen, I tried what you suggested but unfortunately nothing changed... Do you end up having any other ideas? Thank you in advance for your time

rholshausen commented 6 months ago

The service provider is your service not the pact broker, so this is wrong (but not what is causing the issue)

serviceProvider.protocol = 'https'
        serviceProvider.host = 'https://pact-broker.com'
        serviceProvider.port = 443
        serviceProvider.path = '/pacts/my_pact'
        serviceProvider.insecure = true

Ah! this should be lower case Authorization:

vbutlerovskyi commented 6 months ago

Thanks for the hint, but unfortunately nothing changed... I tried: serviceProvider.hasPactsFromPactBroker('https://pact-broker.com', authorization: ['Bearer', 'bSwVWhZbdQ7EI9M6PwCp3Olx1DQ'])

serviceProvider.hasPactsFromPactBroker('https://pact-broker.com', authorization: ['Basic', 'bSwVWhZbdQ7EI9M6PwCp3Olx1DQ'])