Hi team, I'm trying to complete a PoC using pact ...
# documentation
j
Hi team, I'm trying to complete a PoC using pact for contract testing. I've managed to create Consumer Driven pact test and push them to a pact-broker running on a docker container locally. Now I would need to create a Provider verifier but I really don't understand the steps there (I'm using Rust, by the way). I would appreciate some guidance if you guys already went through this.
My application is event-driven using AMQP messages via RabbitMQ. I understand that the pact-broker is the one that will mimic the infrastructure, so I can focus on the messages themselves. I understand that I have to create something (I understood it has to be a Web Server) that hosts my provider code. Then I have to connect the pact-verify with this created web server and to the pact-broker. What I don't understand is what API shall the web server provide? Do I even need the web server really? Any guidance is highly appreciated, as this is the last missing piece for me.
y
you are possibly mixing up expectations between using http pact and message pact. If you are using message pact https://docs.pact.io/getting_started/how_pact_works#non-http-testing-message-pact you are testing the contents, and not the transport layer. You need to verify that your producer/provider can create the message the consumer specified in their contract. you can do this in a unit test without spinning anything up. for http testing, you are required on the provider side to have running provider, this is done by enabling the provider to run locally or in ci/cd so you are testing your implemented service, pre deployment. some applications may require tweaking to support this type of startup
j
ok that fits what I had in mind initially but got confused by separate discussions. In order to pull the pacts from a pact-broker, what do I need to integrate in my provider tests?
y
There is a CLI wrapper for the rust module https://github.com/pact-foundation/pact-reference/tree/master/rust/pact_verifier_cli which has these options for loading pacts, there are analogous methods in the rust verifier crate to access programatically
Copy code
Loading pacts options:
  -f, --file <file>
          Pact file to verify (can be repeated)
  -d, --dir <dir>
          Directory of pact files to verify (can be repeated)
  -u, --url <url>
          URL of pact file to verify (can be repeated)
  -b, --broker-url <broker-url>
          URL of the pact broker to fetch pacts from to verify (requires the provider name parameter) [env: PACT_BROKER_BASE_URL=]
      --webhook-callback-url <webhook-callback-url>
          URL of a Pact to verify via a webhook callback. Requires the broker-url to be set. [env: PACT_WEBHOOK_CALLBACK_URL=]
      --ignore-no-pacts-error
          Do not fail if no pacts are found to verify

Pact Broker options:
      --consumer-version-tags <consumer-version-tags>
          Consumer tags to use when fetching pacts from the Broker. Accepts comma-separated values.
      --consumer-version-selectors <consumer-version-selectors>
          Consumer version selectors to use when fetching pacts from the Broker. Accepts a JSON string as per <https://docs.pact.io/pact_broker/advanced_topics/consumer_version_selectors/>. Can be repeated.
      --enable-pending
          Enables Pending Pacts
      --include-wip-pacts-since <include-wip-pacts-since>
          Allow pacts that don't match given consumer selectors (or tags) to  be verified, without causing the overall task to fail. For more information, see <https://pact.io/wip>
m
Thanks Saf. Javier, just checking that you managed to find this test / example: https://github.com/pact-foundation/pact-reference/blob/master/rust/pact_verifier/tests/tests.rs#L725
It looks to be (cleverly?) using the Pact Mock server to be the API that will serve the message response. It’s a bit confusing, but the API itself is not important to test - it’s just there as the intermediary. You don’t need to use the Pact Mock server, anything that responds on HTTP to intermediate the messages is fine. Yousaf’s link to the verifier discusses how this works
Hope that helps!
j
But then do I have to create an HTTP server or can I just use the Rust testing framework as I did with the consumer pact tests?
m
(In some other languages, this extra step is hidden away within the Pact framework itself. That would be a nice addition to the rust language if you would be interested in contributing)
🙌 1
j
I will check all the documents before further asking questions.
m
I can’t say for sure, but as long as there is an HTTP endpoint accessible to the test that wraps your message functions, that should suffice.
j
I believe this endpoint is the one triggered by the pact-broker based on a signal send via webhook to it?
y
it is separate. the webhook from a pact broker would trigger a CI job, which you would use to checkout the correct version of your provider code and then initiate your tests. As Matt said in other pact client languages, they implement a http proxy server, example https://github.com/pact-foundation/pact-python/blob/master/src/pact/http_proxy.py which then has the states loaded into the server at the time the provider message test runs https://github.com/pact-foundation/pact-python/blob/ba665f529401c64a671a5309bcc62584e95f3816/src/pact/message_provider.py#L63-L91 When messages are replayed, if the state matches the messageHandler in your provider code, it is routed otherwise it returns an error saying it cannot match the consumers message with an appropriate handler on the provider side
But then do I have to create an HTTP server or can I just use the Rust testing framework as I did with the consumer pact tests?
I believe so, for message tests. I would be a good feature enhancement for rust users, using the crate directly rather than the cli
m
I believe this endpoint is the one triggered by the pact-broker based on a signal send via webhook to it?
I think this is a common misconception. The Pact Broker is really just the place the contracts are collaborated/exchanged - the SDKs are what actually do the testing bits. So in this case, the process is basically: 1. Create an API that can respond to POST calls, taking in the message payload (
{"description": "..", "providerStates": […]}
) , and returns the event/message details 2. Call the verifier SDK, passing in the details to the broker and how to communicate to the provider (in step 1) 3. The verifier speaks to the Pact Broker to discover the contracts (pacts) to validate 4. For each interaction in each pact file, the verifier calls your API and looks at the emitted events in the output
j
I believe I'm almost there:
Copy code
#[test_log::test(tokio::test)]
async fn when_instrument_state_switched_to_starting_up_then_example_unit_starts_up_itself() {
    // Arrange
    let provider = PactBuilder::new_v4("instrument-state-unit", "example-unit")
        .interaction("Instrument State changes to Starting Up and triggers units to startup", "", |mut i| {
            i.test_name("test_unit_reacts_to_instrument_state_starting_up");
            i.request.content_type("application/json");
            i.request.json_body(json_pattern!({
                "origin": like!("simulated-instrument-sn555"),
                "version": like!("1.0.0"),
                "name": like!("InstrumentStateChanged"),
                "old_state": like!("PoweredOff"),
                "new_state": like!("StartingUp")
            }));

            i.response.ok().content_type("application/json").json_body(json_pattern!({
                "result": "hello"
            }));

            i
        }).start_mock_server(None);

    let provider_info = ProviderInfo {
        name: "example-unit".to_string(),
        host: provider.url().host_str().unwrap().to_string(),
        transports: vec![ ProviderTransport {
            transport: "HTTP".to_string(),
            port: provider.url().port(),
            path: None,
            scheme: Some("http".to_string())
        } ],
        .. ProviderInfo::default()
    };

    // Create the pact source to pull pacts from the pact-broker
    let pact_source = PactSource::BrokerUrl("example-unit".to_string(), "<http://localhost:9292>".to_string(), None, vec![]);

    // Arrange
    let verification_options: VerificationOptions<NullRequestFilterExecutor> = VerificationOptions::default();
    let provider_state_executor = Arc::new(DummyProviderStateExecutor{});
    let publish_options = PublishOptions {
        provider_version: Some("1.0.0".to_string()),
        build_url: None,
        provider_tags: vec![],
        provider_branch: None,
    };

    let result = verify_provider_async(
        provider_info,
        vec![ pact_source ],
        FilterInfo::None,
        vec![ "instrument-state-unit".to_string() ],
        &verification_options,
        Some(&publish_options),
        &provider_state_executor,
        None
    ).await;

    // Assert
    expect!(result.unwrap().result).to(be_true());
}
The results of it is:
Failures:
1) Verifying a pact between instrument-state-unit and example-unit Given Unit is powered off And Hardware units works normally - Instrument State changes to Starting Up and triggers units to startup - error sending request for url (http://127.0.0.1:8080/)
2) Verifying a pact between instrument-state-unit and example-unit - Unit State Changed to StartingUp - error sending request for url (http://127.0.0.1:8080/)
3) Verifying a pact between instrument-state-unit and example-unit Given Unit is powered off And Hardware units works normally - Unit State Changed to StartingUp - error sending request for url (http://127.0.0.1:8080/)
There were 3 pact failures
So I could: 1. create a rust test that pulls the consumer pacts from a running pact-broker 2. Creates a MockServer (which seems to be now the source of the problem) 3. Provide dummy state data The issue is that the error message is quite unspecific. Any idea what could be the issue?
Of course, I expect this test to fail (because it returns ''result: hello'')
ah I finally managed
I finally needed the custom HTTP Server but in the end it worked out
excellent 1
y
Glad you managed to get sorted @Javier Jimenez Roda! Would really appreciate if you could add an example or update to the documentation if you can spare the time at some point in the future. Your learnings will certainly be valuable for others!