diff --git a/lib/pact/consumer.rb b/lib/pact/consumer.rb index 37bd7934..e478c919 100644 --- a/lib/pact/consumer.rb +++ b/lib/pact/consumer.rb @@ -4,4 +4,5 @@ require 'pact/consumer/consumer_contract_builders' require 'pact/consumer/interaction_builder' require 'pact/term' +require 'pact/provider_param' require 'pact/something_like' diff --git a/lib/pact/provider/matchers/messages.rb b/lib/pact/provider/matchers/messages.rb index c5ad9fb4..1d01aaa3 100644 --- a/lib/pact/provider/matchers/messages.rb +++ b/lib/pact/provider/matchers/messages.rb @@ -1,5 +1,6 @@ require 'term/ansicolor' require 'pact/term' +require 'pact/provider_param' module Pact module Matchers diff --git a/lib/pact/provider/request.rb b/lib/pact/provider/request.rb index 3aaf0662..fef1d611 100644 --- a/lib/pact/provider/request.rb +++ b/lib/pact/provider/request.rb @@ -10,8 +10,9 @@ class Replayable # See https://github.com/rack/rack/blob/e7d741c6282ca4cf4e01506f5681e6e6b14c0b32/SPEC#L87-89 NO_HTTP_PREFIX = ["CONTENT-TYPE", "CONTENT-LENGTH"] - def initialize expected_request + def initialize expected_request, provider_params={} @expected_request = expected_request + @provider_params = provider_params end def method @@ -19,7 +20,7 @@ def method end def path - expected_request.full_path + expected_request.full_path(@provider_params) end def body @@ -35,7 +36,7 @@ def headers request_headers = {} return request_headers if expected_request.headers.is_a?(Pact::NullExpectation) expected_request.headers.each do |key, value| - request_headers[rack_request_header_for(key)] = Pact::Reification.from_term(value) + request_headers[rack_request_header_for(key)] = Pact::Reification.from_term(value, @provider_params) end request_headers end diff --git a/lib/pact/provider/rspec.rb b/lib/pact/provider/rspec.rb index e6ed3c2d..b440ecc5 100644 --- a/lib/pact/provider/rspec.rb +++ b/lib/pact/provider/rspec.rb @@ -82,8 +82,8 @@ def describe_interaction interaction, options before do | example | interaction_context.run_once :before do Pact.configuration.logger.info "Running example '#{Pact::RSpec.full_description(example)}'" - set_up_provider_state interaction.provider_state, options[:consumer] - replay_interaction interaction + call_params = set_up_provider_state interaction.provider_state, options[:consumer] + replay_interaction interaction, call_params interaction_context.last_response = last_response end end diff --git a/lib/pact/provider/state/provider_state.rb b/lib/pact/provider/state/provider_state.rb index 435510b0..b7fd46b8 100644 --- a/lib/pact/provider/state/provider_state.rb +++ b/lib/pact/provider/state/provider_state.rb @@ -68,6 +68,7 @@ class ProviderState attr_accessor :name attr_accessor :namespace + attr_accessor :provider_params extend Pact::DSL @@ -77,6 +78,11 @@ def initialize name, namespace, &block @set_up_defined = false @tear_down_defined = false @no_op_defined = false + @provider_params = {} + end + + def provider_param var_name, value + @provider_params[var_name] = value end dsl do diff --git a/lib/pact/provider/state/provider_state_manager.rb b/lib/pact/provider/state/provider_state_manager.rb index 72457caf..a5b0290c 100644 --- a/lib/pact/provider/state/provider_state_manager.rb +++ b/lib/pact/provider/state/provider_state_manager.rb @@ -13,8 +13,11 @@ def set_up_provider_state get_global_base_provider_state.set_up get_consumer_base_provider_state.set_up if provider_state_name - get_provider_state.set_up + provider_state = get_provider_state + provider_state.set_up + return provider_state.provider_params end + return {} end def tear_down_provider_state diff --git a/lib/pact/provider/test_methods.rb b/lib/pact/provider/test_methods.rb index 76486685..b8a405c9 100644 --- a/lib/pact/provider/test_methods.rb +++ b/lib/pact/provider/test_methods.rb @@ -14,8 +14,8 @@ module TestMethods include Pact::Logging include Rack::Test::Methods - def replay_interaction interaction - request = Request::Replayable.new(interaction.request) + def replay_interaction interaction, provider_params={} + request = Request::Replayable.new(interaction.request, provider_params) args = [request.path, request.body, request.headers] logger.info "Sending #{request.method.upcase} request to path: \"#{request.path}\" with headers: #{request.headers}, see debug logs for body" diff --git a/spec/features/foo_bar_spec.rb b/spec/features/foo_bar_spec.rb index 9a37bbe5..791c08e3 100644 --- a/spec/features/foo_bar_spec.rb +++ b/spec/features/foo_bar_spec.rb @@ -8,37 +8,65 @@ describe "Bar", :pact => true do - it "can retrieve a thing" do - - Pact.clear_configuration - Pact.clear_consumer_world + before :all do + Pact.clear_configuration + Pact.clear_consumer_world - Pact.service_consumer "Foo" do - has_pact_with "Bar" do - mock_service :bar_service do - pact_specification_version "2" - port 4638 - end + Pact.service_consumer "Foo" do + has_pact_with "Bar" do + mock_service :bar_service do + pact_specification_version "2" + port 4638 end end + end + end - bar_service. - upon_receiving("a retrieve thing request").with({ + it "can retrieve a thing" do + bar_service. + upon_receiving("a retrieve thing request").with({ method: :get, path: '/thing' }). - will_respond_with({ + will_respond_with({ status: 200, headers: { 'Content-Type' => 'application/json' }, body: Pact.each_like({status: Pact.term(/\d+/, "4")}) }) - bar_response = Net::HTTP.get_response(URI('http://localhost:4638/thing')) + bar_response = Net::HTTP.get_response(URI('http://localhost:4638/thing')) - expect(bar_response.code).to eql '200' - expect(JSON.parse(bar_response.body)).to eq [{"status" => "4"}] + expect(bar_response.code).to eql '200' + expect(JSON.parse(bar_response.body)).to eq [{"status" => "4"}] - puts bar_service.write_pact + puts bar_service.write_pact end -end \ No newline at end of file + it 'can retrieve a specific thing with a provider param' do + bar_service. + upon_receiving("a retrieve specific thing request").with({ + method: :get, + path: Pact.provider_param('/thing/:{id}', {id: '99'}), + headers: { + Authorization: Pact.provider_param('Bearer :{auth_token}', {auth_token: 'faketoken'}) + } + }). + will_respond_with({ + status: 200, + headers: { 'Content-Type' => 'application/json' }, + body: Pact.each_like({status: Pact.term(/\d+/, "4")}) + }) + + uri = URI('http://localhost:4638/thing/99') + req = Net::HTTP::Get.new(uri) + req['Authorization'] = 'Bearer faketoken' + bar_response = Net::HTTP.start(uri.hostname, uri.port) { |http| + http.request(req) + } + + expect(bar_response.code).to eql '200' + expect(JSON.parse(bar_response.body)).to eq [{"status" => "4"}] + + puts bar_service.write_pact + end +end diff --git a/spec/features/production_spec.rb b/spec/features/production_spec.rb index ca4ccef8..662d7854 100644 --- a/spec/features/production_spec.rb +++ b/spec/features/production_spec.rb @@ -30,6 +30,11 @@ def find_zebra_names end def call(env) + if (env['HTTP_AUTHORIZATION']) + if env['HTTP_AUTHORIZATION'] != 'password' + return [401, {'Content-Type' => 'application/json'}, {error: "The password is 'password'"}.to_json] + end + end case env['PATH_INFO'] when "/zebra_names" [200, {'Content-Type' => 'application/json'}, { names: find_zebra_names }.to_json] diff --git a/spec/features/provider_states/zebras.rb b/spec/features/provider_states/zebras.rb index 916bcf6d..a7e6d47c 100644 --- a/spec/features/provider_states/zebras.rb +++ b/spec/features/provider_states/zebras.rb @@ -26,3 +26,18 @@ FileUtils.rm_rf("tmp/a_mock_database.json") end end + +Pact.provider_state "zebra with provider param" do + set_up do + some_data = { + id: '99', + name: 'Zee' + } + File.open("tmp/a_mock_database.json", "w") { |file| file << some_data.to_json } + provider_param :id, '99' + end + + tear_down do + FileUtils.rm_rf("tmp/a_mock_database.json") + end +end diff --git a/spec/support/pact_helper.rb b/spec/support/pact_helper.rb index a994556d..4e77aa1d 100644 --- a/spec/support/pact_helper.rb +++ b/spec/support/pact_helper.rb @@ -13,6 +13,8 @@ def call env [200, {'Content-Type' => 'text/plain'}, ['some text']] elsif env['PATH_INFO'] == '/content_type_is_important' [200, {'Content-Type' => 'application/json'}, [{message: "A message", note: "This will cause verify to fail if it using the wrong content type differ."}.to_json]] + elsif env['PATH_INFO'] == '/food_item/7' + [200, {'Content-Type' => 'application/json'}, [{message: 'One apple'}.to_json]] else raise "unexpected path #{env['PATH_INFO']}!!!" end @@ -46,6 +48,13 @@ def call env WEATHER[:current_state] = 'sunny' end + + provider_state 'create one apple' do + set_up do + provider_param :item, 'apple' + provider_param :id, '7' + end + end end end diff --git a/spec/support/provider_param.json b/spec/support/provider_param.json new file mode 100644 index 00000000..112b6f42 --- /dev/null +++ b/spec/support/provider_param.json @@ -0,0 +1,46 @@ +{ + "consumer": { + "name": "some-test-consumer" + }, + "provider": { + "name": "an unknown provider" + }, + "interactions": [ + { + "description": "a test request", + "request": { + "method": "get", + "path": "/food_item/5", + "query": "", + "headers": { + "Authorization": "Bearer faketoken" + }, + "matchingRules": { + "$.path": { + "match": "provider_param", + "fill_string": "/food_item/:{id}" + }, + "$.headers.Authorization": { + "match": "provider_param", + "fill_string": "Bearer :{auth_token}" + } + } + }, + "response": { + "matchingRules": { + "$.headers.Content-Type" : { + "match": "regex", "regex": "json" + } + }, + "status": 200, + "headers" : { + "Content-Type": "foo/json" + }, + "body": { + "message" : "One apple" + } + }, + "provider_state": "create one apple" + } + ] +} diff --git a/tasks/pact-test.rake b/tasks/pact-test.rake index 9d49fd2a..7a174e5d 100644 --- a/tasks/pact-test.rake +++ b/tasks/pact-test.rake @@ -29,6 +29,10 @@ Pact::VerificationTask.new(:term_v2) do | pact | pact.uri './spec/support/term-v2.json' end +Pact::VerificationTask.new(:provider_param) do |pact| + pact.uri './spec/support/provider_param.json' +end + Pact::VerificationTask.new(:case_insensitive_response_header_matching) do | pact | pact.uri './spec/support/case-insensitive-response-header-matching.json', :pact_helper => './spec/support/case-insensitive-response-header-matching.rb' end @@ -68,6 +72,7 @@ namespace :pact do Rake::Task['pact:verify:test_app:content_type'].execute Rake::Task['pact:verify:case_insensitive_response_header_matching'].execute Rake::Task['pact:verify:term_v2'].execute + Rake::Task['pact:verify:provider_param'].execute end desc "All the verification tests with active support loaded"