From 733d79bd9f6a98fe24e5ecd7c13da783bc3ac306 Mon Sep 17 00:00:00 2001 From: Alex Strizhak Date: Thu, 9 Feb 2023 19:46:40 +0000 Subject: [PATCH] feat([PoC]): Contract for sequence of messages --- .ruby-version | 2 +- Gemfile | 6 +- Gemfile.lock | 20 ++++-- app/consumers/test_message_consumer.rb | 6 +- app/producers/test_message_producer.rb | 21 +++--- log/pact.log | 9 +++ reports/pacts/help.md | 25 +++++++ spec/consumer_spec.rb | 69 ++++++++----------- ...essage_consumer-test_message_producer.json | 23 +++++-- spec/service_consumers/pact_helper.rb | 33 +++++---- ...ovider_states_for_test_message_consumer.rb | 11 +-- 11 files changed, 132 insertions(+), 93 deletions(-) create mode 100644 log/pact.log create mode 100644 reports/pacts/help.md diff --git a/.ruby-version b/.ruby-version index b38ebbf..75a22a2 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -3.0.4 \ No newline at end of file +3.0.3 diff --git a/Gemfile b/Gemfile index c94911e..b443a4f 100644 --- a/Gemfile +++ b/Gemfile @@ -8,7 +8,9 @@ group :development, :test do gem 'pact' - gem 'pact-message' + gem 'pact-message', git: 'https://github.com/Mifrill/pact-message-ruby', branch: 'feature/messages' gem 'rake' -end \ No newline at end of file + + gem 'byebug' +end diff --git a/Gemfile.lock b/Gemfile.lock index b4b51dc..b0c936b 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,18 @@ +GIT + remote: https://github.com/Mifrill/pact-message-ruby + revision: 1b55bec934ed37896f4e8860b63ae1eed0070589 + branch: feature/messages + specs: + pact-message (0.11.1) + pact-mock_service (~> 3.1) + pact-support (~> 1.8) + thor (>= 0.20, < 2.0) + GEM remote: https://rubygems.org/ specs: awesome_print (1.9.2) + byebug (11.1.3) coderay (1.1.3) diff-lcs (1.5.0) expgen (0.1.1) @@ -18,10 +29,6 @@ GEM term-ansicolor (~> 1.0) thor (>= 0.20, < 2.0) webrick (~> 1.3) - pact-message (0.11.1) - pact-mock_service (~> 3.1) - pact-support (~> 1.8) - thor (>= 0.20, < 2.0) pact-mock_service (3.10.0) filelock (~> 1.1) find_a_port (~> 1.0.1) @@ -71,11 +78,12 @@ PLATFORMS x86_64-darwin-21 DEPENDENCIES + byebug pact - pact-message + pact-message! pry rake rspec BUNDLED WITH - 2.2.33 + 2.3.26 diff --git a/app/consumers/test_message_consumer.rb b/app/consumers/test_message_consumer.rb index 0d5eaa3..3c6da1d 100644 --- a/app/consumers/test_message_consumer.rb +++ b/app/consumers/test_message_consumer.rb @@ -1,9 +1,5 @@ class TestMessageConsumer - def consume_message(message) - puts "Message consumed" - puts message.to_json message end - - end \ No newline at end of file +end diff --git a/app/producers/test_message_producer.rb b/app/producers/test_message_producer.rb index 5fa6be6..c61aa3f 100644 --- a/app/producers/test_message_producer.rb +++ b/app/producers/test_message_producer.rb @@ -1,12 +1,13 @@ class TestMessageProducer - -def publish_message() - message = { - "email": "jane@example.com", - "title": "Miss", - "first_name": "Jane", - "surname": "Doe" - } + def publish_message + [ + { + "first_name": "John", + "last_name": "Doe" + }, + { + "hello": "world" + } + ] + end end - -end \ No newline at end of file diff --git a/log/pact.log b/log/pact.log new file mode 100644 index 0000000..bedeaa6 --- /dev/null +++ b/log/pact.log @@ -0,0 +1,9 @@ +# Logfile created on 2023-02-09 19:42:49 +0000 by logger.rb/v1.4.3 +I, [2023-02-09T19:42:49.857092 #48830] INFO -- : Running example 'Verifying a pact between Test Message Consumer and Test Message Producer Given Class1 updated has matching content' +I, [2023-02-09T19:42:49.857543 #48830] INFO -- : Sending POST request to path: "/" with headers: {"CONTENT_TYPE"=>"application/json"}, see debug logs for body +D, [2023-02-09T19:42:49.857570 #48830] DEBUG -- : body :{"description":"updated","providerStates":[{"name":"Class1","params":{}}],"metadata":null} +I, [2023-02-09T19:45:49.705091 #53882] INFO -- : Running example 'Verifying a pact between Test Message Consumer and Test Message Producer Given Class1 created;updated has matching content' +I, [2023-02-09T19:45:49.705755 #53882] INFO -- : Sending POST request to path: "/" with headers: {"CONTENT_TYPE"=>"application/json"}, see debug logs for body +D, [2023-02-09T19:45:49.705791 #53882] DEBUG -- : body :{"description":"created;updated","providerStates":[{"name":"Class1","params":{}}],"metadata":null} +I, [2023-02-09T19:45:49.711473 #53882] INFO -- : Received response with status: 200, headers: {"Content-Type"=>"application/json"}, see debug logs for body +D, [2023-02-09T19:45:49.711504 #53882] DEBUG -- : body: {"contents":[{"first_name":"John","last_name":"Doe"},{"hello":"world"}]} diff --git a/reports/pacts/help.md b/reports/pacts/help.md new file mode 100644 index 0000000..d0c450c --- /dev/null +++ b/reports/pacts/help.md @@ -0,0 +1,25 @@ +# For assistance debugging failures + +* The pact files have been stored locally in the following temp directory: + /Users/alex.strizhak/Downloads/pact-ruby-demo/tmp/pacts + +* The requests and responses are logged in the following log file: + /Users/alex.strizhak/Downloads/pact-ruby-demo/log/pact.log + +* Add BACKTRACE=true to the `rake pact:verify` command to see the full backtrace + +* If the diff output is confusing, try using another diff formatter. + The options are :unix, :embedded and :list + + Pact.configure do | config | + config.diff_formatter = :embedded + end + + See https://github.com/pact-foundation/pact-ruby/blob/master/documentation/configuration.md#diff_formatter for examples and more information. + +* Check out https://github.com/pact-foundation/pact-ruby/wiki/Troubleshooting + +* Ask a question on stackoverflow and tag it `pact-ruby` + + +Tried to retrieve diff with previous pact, but received error Pact::Hal::RelationNotFoundError Could not find relation 'pb:diff-previous-distinct' in resource at spec/pacts/test_message_consumer-test_message_producer.json. \ No newline at end of file diff --git a/spec/consumer_spec.rb b/spec/consumer_spec.rb index ed86fa0..cf95d3b 100644 --- a/spec/consumer_spec.rb +++ b/spec/consumer_spec.rb @@ -1,52 +1,39 @@ require 'pact/message/consumer/rspec' require 'support/pact_spec_helper.rb' -require_relative '../app/consumers/test_message_consumer.rb' - -describe TestMessageConsumer, pact: true do - subject(:consumer) {TestMessageConsumer.new} - - # Notice that the expected message payload has fields which are different to that of the actual producer. - # The TestMessageProducer actually sends a message with an additional 'title' field and a renamed 'surname' field. - # See app/producers/test_message_producer.rb. +require_relative '../app/consumers/test_message_consumer.rb' - expected_payload = { - "email": "jane@example.com", - "first_name": "Jane", - "last_name": "Doe" +describe TestMessageConsumer do + let(:expected_payload_1) { + { + "first_name": "John", + "last_name": "Doe" + } } - - # Notice the new pact: :message decorator/metadata + let(:expected_payload_2) { + { + "hello": "world" + } + } + let(:consumer) { described_class.new } describe "Test Message Consumer", pact: :message do - before do - - # Here we are calling test_message_producer, which is mocking the actual TestMessageProducer defined in app/producers/test_message_producer.rb. - # In pact-message we use mocked providers in consumer side tests. These are defined in a similar way to mocked APIs/service providers in standard HTTP CDCT. - # See spec/support/pact_spec_helper.rb. - - test_message_producer.given("A customer is created") - .is_expected_to_send("a customer created message") - # .with_metadata() - .with_content(expected_payload) - - test_message_producer.send_message_string do | content_string | - @message = consumer.consume_message(content_string) + it "generates contract" do + test_message_producer + .given("Class1") + .is_expected_to_send("created") + .with_content(expected_payload_1) + test_message_producer.send_message_string do |content_string| + expect(consumer.consume_message(content_string)).to eq(expected_payload_1.to_json) end + test_message_producer + .given("Class2") + .is_expected_to_send("updated") + .with_content(expected_payload_2) + test_message_producer.send_message_string do |content_string| + expect(consumer.consume_message(content_string)).to eq(expected_payload_2.to_json) + end end - - # This test is a bit redundant, it's essentially marking our own homework and will always pass. - # However IRL the consumer would probably be doing something more complex which we could assert on. - # See spec/pacts/test_message_consumer-test_message_producer.json for the generated contract file. - # Note that this contract does not match what the producer outputs in app/producers/test_message_producer.rb.. - # If we were to run producer side verification on this contract, it should fail. - # This failure would indicate a mismatch between the consumers expectations of the message format and what the producer actually sends. - - it "Successfully consumes the message and creates a pact contract file" do - expect(@message).to eq(expected_payload.to_json) - end - end - -end \ No newline at end of file +end diff --git a/spec/pacts/test_message_consumer-test_message_producer.json b/spec/pacts/test_message_consumer-test_message_producer.json index 57745b2..f6bcfc0 100644 --- a/spec/pacts/test_message_consumer-test_message_producer.json +++ b/spec/pacts/test_message_consumer-test_message_producer.json @@ -7,19 +7,28 @@ }, "messages": [ { - "description": "a customer created message", + "description": "created;updated", "providerStates": [ { - "name": "A customer is created", + "name": "Class1", "params": { } + }, + { + "name": "Class2", + "params": { + } + } + ], + "contents": [ + { + "first_name": "John", + "last_name": "Doe" + }, + { + "hello": "world" } ], - "contents": { - "email": "jane@example.com", - "first_name": "Jane", - "last_name": "Doe" - }, "matchingRules": { } } diff --git a/spec/service_consumers/pact_helper.rb b/spec/service_consumers/pact_helper.rb index 40ccfc0..621c124 100644 --- a/spec/service_consumers/pact_helper.rb +++ b/spec/service_consumers/pact_helper.rb @@ -9,36 +9,35 @@ require 'pact/message' Pact.message_provider "Test Message Provider" do - - # Explictly creates an instance of the producer. - # This might be different to how we create instances of rails/rack APIs in HTTP Pact. - Producer = TestMessageProducer.new - - # This matches the usage in HTTP Pact. + # This matches the usage in HTTP Pact. honours_pact_with "Test Message Consumer" do pact_uri "spec/pacts/test_message_consumer-test_message_producer.json" end - # Create a hash of all the messages the producer needs to publish to meet the preconditions of the contract files it is referenced in. - # This is the recommended approach in the pact-message-ruby documentation. See https://github.com/pact-foundation/pact-message-ruby - MESSAGES = { - "a customer created message" => lambda { Producer.publish_message } - } - # Builder which populates the hash. # Calls the lambda above, which calls the Producer.publish_message method. # See app/producers/test_message_producer.rb. - # The producer publishes a message (json). - # The message is used in the dynamically generated RSpec tests to verify the contract provided by the consumer. + # The producer publishes a message (json). + # The message is used in the dynamically generated RSpec tests to verify the contract provided by the consumer. builder do |message_description| - MESSAGES[message_description].call + # Explictly creates an instance of the producer. + # This might be different to how we create instances of rails/rack APIs in HTTP Pact. + producer = TestMessageProducer.new + + # Create a hash of all the messages the producer needs to publish to meet the preconditions of the contract files it is referenced in. + # This is the recommended approach in the pact-message-ruby documentation. See https://github.com/pact-foundation/pact-message-ruby + messages = { + "created;updated" => lambda { producer.publish_message }, + } + + messages[message_description].call end - #Execute the following to run the provider side verification on the contract files in spec/pacts: + #Execute the following to run the provider side verification on the contract files in spec/pacts: # bundle exec rake pact:verify # The pact verification tool will do the following: # 1. Read the contract files (pact files) in spec/pacts. # 2. Setup the required pre-requiste test state (provider states) referenced in the contract files. # 3. Dynamically generate and execute RSpec tests based on the content of the contract files. -end \ No newline at end of file +end diff --git a/spec/service_consumers/provider_states_for_test_message_consumer.rb b/spec/service_consumers/provider_states_for_test_message_consumer.rb index 28e464f..8c1aec2 100644 --- a/spec/service_consumers/provider_states_for_test_message_consumer.rb +++ b/spec/service_consumers/provider_states_for_test_message_consumer.rb @@ -1,10 +1,13 @@ Pact.provider_states_for "Test Message Consumer" do - provider_state "A customer is created" do + provider_state "Class1" do set_up do - # no_op - # Pre-requiste test setup code to create this state would go here + # Pre-requests test setup code to create this state would go here end end -end \ No newline at end of file + provider_state "Class2" do + set_up do + end + end +end