Skip to content

Provider verification against a Pact Broker hosted under a sub-path (context path) drops the base path when calling for-verification → 404 #962

Description

@Pacthost

Summary

When the Pact Broker is hosted behind a reverse proxy under a URL sub-path (context path, e.g. https://broker.example.com/pact-broker), provider verification in broker mode (consumer version selectors) fails to find pacts. The verifier requests the for-verification endpoint without the base-path prefix, gets a 404, reports "No pacts found", and — with pending enabled — passes the build having verified nothing (false green).

Direct curl to the correctly-prefixed for-verification URL returns 200 with the expected pacts, so the broker and proxy are configured correctly; the wrong URL is constructed by the verifier's HAL client. This looks like a recurrence / incomplete fix of #420.

Environment / versions

Component Version
pact-go 2.5.0
Verifier engine (reported by broker verifiedBy.implementation) Pact-Rust 1.3.3
libpact_ffi as bundled by pact-go v2.5.0
Pact Broker (self-hosted) 2.119.0 (Puma 7.2.1)
Pact specification 4.0
Go 1.26.0
Reverse proxy APISIX (behavior is generic to any proxy hosting the broker under a sub-path)

Broker deployment: served under a sub-path; PACT_BROKER_BASE_URL=https://broker.example.com/pact-broker. Reverse proxy strips the /pact-broker prefix before forwarding to the broker (broker serves at /).

What works ✅

  • Broker index GET https://broker.example.com/pact-broker — all HAL _links are correct (public host + /pact-broker prefix).
  • for-verification endpoint itself, called directly with the prefix, returns 200 + pacts:
curl -s -X POST \
  'https://broker.example.com/pact-broker/pacts/provider/<provider>/for-verification' \
  -H 'Content-Type: application/json' \
  -d '{"providerVersionBranch":"<branch>","consumerVersionSelectors":[{"mainBranch":true},{"matchingBranch":true}]}'
# → HTTP 200
# {"_embedded":{"pacts":[{ ... "verificationProperties": ... }]}, "_links":{...}}
  • Consumer publishing works (branch travels in the request body, not the URL path).
  • Verification via an explicit pact URL (PactURLs/PACT_URL) works end-to-end — verifies and publishes results back, because the follow-on links (publish-verification-results, branch-version PUT) are base_url-derived and correct.

What does NOT work ❌

Provider verification in broker mode (ConsumerVersionSelectors). The verifier logs the correctly-prefixed path but the actual HTTP request drops /pact-broker:

INFO  pact_verifier::pact_broker: Fetching path '' from pact broker
INFO  pact_verifier::pact_broker: Fetching path '/pact-broker/pacts/provider/<provider>/for-verification' from pact broker
INFO  pact_verifier::pact_broker: error response for pacts for verification: Link/Resource was not found -
      Request to pact broker path '/pacts/provider/<provider>/for-verification' failed: 404 Not Found.
      URL: 'https://broker.example.com/pact-broker'
ERROR pact_verifier: No pacts found for provider '<provider>' matching the given consumer version selectors
      in pact broker 'https://broker.example.com/pact-broker': ... failed: 404 Not Found.
      URL: 'https://broker.example.com/pact-broker'
WARN  pact_verifier: Ignoring no pacts error - No pacts found ...
--- PASS: TestPactProvider (1.82s)     # <-- false green: verified 0 pacts

Note the discrepancy: it logs /pact-broker/pacts/.../for-verification (correct) but requests /pacts/.../for-verification (base path dropped).

Proof that the dropped-prefix URL is what 404s (i.e. the URL the verifier actually hits):

curl -s -o /dev/null -w '%{http_code}\n' -X POST \
  'https://broker.example.com/pacts/provider/<provider>/for-verification' \
  -H 'Content-Type: application/json' -d '{...}'
# → 404      (no route without the /pact-broker prefix)

Secondary observation (broker-side link generation)

Within the same for-verification response, the embedded pact link is base_url-correct but the collection self link omits the base path and reflects the request host:

// embedded pact _links.self  — CORRECT (base_url-derived)
"href": "https://broker.example.com/pact-broker/pacts/provider/<provider>/consumer/<consumer>/pact-version/<pact-version-sha>/metadata/..."

// outer collection _links.self — WRONG (request-derived: no base path, internal host)
"href": "https://broker-internal.example/pacts/provider/<provider>/for-verification"

The index and embedded links honor PACT_BROKER_BASE_URL, but the for-verification collection self link does not. (This is secondary — the verifier fails on its own request before this link is used — but may be the same root cause on the broker side.)

Steps to reproduce

  1. Host Pact Broker behind a reverse proxy under a sub-path, e.g. https://broker.example.com/pact-broker; set PACT_BROKER_BASE_URL to that value; proxy strips the prefix to the broker.
  2. Publish a consumer pact on a branch .
  3. Run a pact-go v2 provider verification in broker mode:
verifier.VerifyProvider(t, provider.VerifyRequest{
    ProviderBaseURL:            "http://127.0.0.1:<port>",
    Provider:                   "<provider>",
    ProviderVersion:            "<provider-version-sha>",
    ProviderBranch:             "<branch>",
    BrokerURL:                  "https://broker.example.com/pact-broker",
    PublishVerificationResults: true,
    EnablePending:              true,
    ConsumerVersionSelectors: []provider.Selector{
        &provider.ConsumerVersionSelector{MainBranch: true},
        &provider.ConsumerVersionSelector{MatchingBranch: true},
    },
})
  1. Observe: for-verification request omits /pact-broker → 404 → "No pacts found" → PASS (nothing verified).

Expected vs actual

Expected: with BrokerURL (or PACT_BROKER_BASE_URL) containing a path component, the verifier preserves that base path when requesting for-verification (as it correctly does for the index and as curl does), fetches the pacts, and verifies them.
Actual: the base path is dropped from the for-verification request → 404 → no pacts → silent pass.

Root cause hypothesis

The HAL client appears to resolve the for-verification relation against the origin (scheme+host) and discard the base-URL path — classic RFC-3986 base-URL-without-trailing-slash / context-path handling. This matches #420 ("Unable to find pacts when using pactBrokerUrl with context path"), which was reportedly addressed previously but reproduces here on Pact-Rust 1.3.3 (via pact-go 2.5.0).

Also tried, no effect: adding/removing a trailing slash on the base URL; confirming the broker honors X-Forwarded-* and PACT_BROKER_BASE_URL (verified by hitting the broker pod directly).

Workaround in use

Verify via an explicit pact URL (PactURLs) instead of the broker's for-verification flow — this bypasses the broken URL construction and works (verifies + publishes results).

Questions for maintainers

  1. Is hosting the Pact Broker under a sub-path / context path a supported topology for provider verification, or is a broker at the root of a host required? (The reverse-proxy docs only show full-domain base URLs.)
  2. Is the base-path drop in the for-verification request a regression of feat: add endpoint to create pacts, pacticipant, version, tags in one request #420 in Pact-Rust 1.3.3 / pact-go 2.5.0, or expected behavior?
  3. Should the "No pacts found" outcome really resolve to a passing build under EnablePending when the cause was a transport 404 (vs. a genuine empty selector result)? The silent false-green is dangerous.
  4. Is the for-verification collection self link expected to honor base_url's path like the index/embedded links do?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions