diff --git a/lib/pact/helpers.rb b/lib/pact/helpers.rb index c6c8228..6a7830d 100644 --- a/lib/pact/helpers.rb +++ b/lib/pact/helpers.rb @@ -1,5 +1,6 @@ require 'pact/something_like' require 'pact/term' +require 'pact/provider_param' require 'pact/array_like' # Protected, exposed through Pact.term and Pact.like, and included in Pact::Consumer::RSpec @@ -21,6 +22,10 @@ def term arg1, arg2 = nil end end + def provider_param arg1, arg2 + Pact::ProviderParam.new(arg1, arg2) + end + def like content Pact::SomethingLike.new(content) end diff --git a/lib/pact/matchers/matchers.rb b/lib/pact/matchers/matchers.rb index fc60c61..29a7eec 100644 --- a/lib/pact/matchers/matchers.rb +++ b/lib/pact/matchers/matchers.rb @@ -1,4 +1,5 @@ require 'pact/term' +require 'pact/provider_param' require 'pact/something_like' require 'pact/array_like' require 'pact/shared/null_expectation' @@ -46,6 +47,7 @@ def calculate_diff expected, actual, opts = {} when Pact::SomethingLike then calculate_diff(expected.contents, actual, options.merge(:type => true)) when Pact::ArrayLike then array_like_diff(expected, actual, options) when Pact::Term then term_diff(expected, actual, options) + when Pact::ProviderParam then provider_param_diff(expected, actual) else object_diff(expected, actual, options) end end @@ -60,6 +62,14 @@ def term_diff term, actual, options end end + def provider_param_diff provider_param, actual + if provider_param.default_string == actual + return NO_DIFF + else + return Difference.new provider_param.default_string, actual, 'Given string did not match default string for provider param.' + end + end + def actual_term_diff term, actual, options if term.matcher.match(actual) NO_DIFF diff --git a/lib/pact/matching_rules/extract.rb b/lib/pact/matching_rules/extract.rb index 27a016e..2223ecc 100644 --- a/lib/pact/matching_rules/extract.rb +++ b/lib/pact/matching_rules/extract.rb @@ -1,6 +1,7 @@ require 'pact/something_like' require 'pact/array_like' require 'pact/term' +require 'pact/provider_param' module Pact module MatchingRules @@ -31,6 +32,7 @@ def recurse object, path, match_type when Pact::SomethingLike then handle_something_like(object, path, match_type) when Pact::ArrayLike then handle_array_like(object, path, match_type) when Pact::Term then record_regex_rule object, path + when Pact::ProviderParam then record_provider_param_rule object, path when Pact::QueryString then recurse(object.query, path, match_type) when Pact::QueryHash then recurse_hash(object.query, path, match_type) end @@ -70,6 +72,12 @@ def record_regex_rule term, path rules[path]['regex'] = term.matcher.inspect[1..-2] end + def record_provider_param_rule provider_param, path + rules[path] ||= {} + rules[path]['match'] = 'provider_param' + rules[path]['fill_string'] = provider_param.fill_string + end + def record_match_type_rule path, match_type unless match_type == :array_like || match_type.nil? rules[path] ||= {} diff --git a/lib/pact/matching_rules/merge.rb b/lib/pact/matching_rules/merge.rb index 384e78c..e2e890f 100644 --- a/lib/pact/matching_rules/merge.rb +++ b/lib/pact/matching_rules/merge.rb @@ -80,6 +80,8 @@ def wrap object, path handle_match_type(object, path, rules) elsif rules['regex'] handle_regex(object, path, rules) + elsif rules['fill_string'] + handle_provider_param(object, path, rules) else log_ignored_rules(path, rules, {}) object @@ -96,6 +98,11 @@ def handle_regex object, path, rules Pact::Term.new(generate: object, matcher: Regexp.new(rules['regex'])) end + def handle_provider_param object, path, rules + log_ignored_rules(path, rules, {'match' => 'provider_param', 'fill_string' => rules['fill_string']}) + Pact::ProviderParam.new(rules['fill_string'], object) + end + def log_ignored_rules path, rules, used_rules dup_rules = rules.dup used_rules.each_pair do | used_key, used_value | diff --git a/lib/pact/provider_param.rb b/lib/pact/provider_param.rb new file mode 100644 index 0000000..16c43fe --- /dev/null +++ b/lib/pact/provider_param.rb @@ -0,0 +1,138 @@ +require 'pact/shared/active_support_support' + +module Pact + class ProviderParam + + include Pact::ActiveSupportSupport + + attr_reader :fill_string, :default_string, :params + + def self.json_create(obj) + new(obj['data']['fill_string'], obj['data']['params']) + end + + def initialize(fill_string, arg2) + @fill_string = fill_string + if arg2.is_a? String + @default_string = arg2 + @params = find_default_values + else + @params = stringify_params(arg2) + @default_string = default_string_from_params @params + end + end + + def replace_params(params) + @params = stringify_params(params) + @default_string = default_string_from_params @params + end + + def to_hash + {json_class: self.class.name, data: {fill_string: @fill_string, params: @params}} + end + + def as_json + to_hash + end + + def to_json(options = {}) + as_json.to_json(options) + end + + def ==(other) + if !other.respond_to?(:fill_string) || other.fill_string != @fill_string + return false + end + if !other.respond_to?(:params) || other.params != @params + return false + end + + return true + end + + def to_s + "Pact::ProviderParam: #{@fill_string} #{@params}" + end + + def empty? + false + end + + private + + def stringify_params(params) + stringified_params = {} + params.each{ |k, v| stringified_params[k.to_s] = v } + stringified_params + end + + def param_name_regex + /:{[a-zA-Z0-9_-]+}/ + end + + def parse_fill_string + matches = @fill_string.scan(param_name_regex) + matches.map do |match| + match[2..(match.length - 2)] + end + end + + def find_strings_between_variables(var_names) + in_between_strings = [] + previous_string_end = 0 + matches = @fill_string.scan(param_name_regex) + + matches.size.times do |index| + # get the locations of the string in between the matched variable names + variable_name_start = @fill_string.index(matches[index]) + variable_name_end = variable_name_start + matches[index].length + string_text = @fill_string[previous_string_end...variable_name_start] + previous_string_end = variable_name_end + in_between_strings << string_text unless string_text.empty? + end + last_part = @fill_string[previous_string_end...@fill_string.length] + in_between_strings << last_part unless last_part.empty? + + in_between_strings + end + + def find_variable_values_in_default_string(in_between_strings) + previous_value_end = 0 + values = [] + + in_between_strings.each do |string| + string_start = @default_string.index(string) + value = @default_string[previous_value_end...string_start] + values << value unless string_start == 0 + previous_value_end = string_start + string.length + end + + last_string = @default_string[previous_value_end..@default_string.length - 1] + values << last_string unless last_string.empty? + + values + end + + def find_default_values + var_names = parse_fill_string + in_between_strings = find_strings_between_variables(var_names) + + values = find_variable_values_in_default_string(in_between_strings) + + param_hash = {} + new_params_arr = var_names.zip(values) + new_params_arr.each do |key, value| + param_hash[key] = value + end + param_hash + end + + def default_string_from_params(params) + default_string = @fill_string + params.each do |key, value| + default_string = default_string.gsub(':{' + key + '}', value) + end + default_string + end + end +end diff --git a/lib/pact/reification.rb b/lib/pact/reification.rb index d8045e7..5a19a5b 100644 --- a/lib/pact/reification.rb +++ b/lib/pact/reification.rb @@ -1,5 +1,6 @@ require 'randexp' require 'pact/term' +require 'pact/provider_param' require 'pact/something_like' require 'pact/array_like' require 'pact/shared/request' @@ -10,10 +11,13 @@ module Pact module Reification include ActiveSupportSupport - def self.from_term(term) + def self.from_term(term, replacement_params = {}) case term when Pact::Term, Regexp, Pact::SomethingLike, Pact::ArrayLike from_term(term.generate) + when Pact::ProviderParam + term.replace_params(replacement_params) unless replacement_params.empty? + from_term(term.default_string) when Hash term.inject({}) do |mem, (key,t)| mem[key] = from_term(t) diff --git a/lib/pact/shared/request.rb b/lib/pact/shared/request.rb index 888a0a0..5fcf1b4 100644 --- a/lib/pact/shared/request.rb +++ b/lib/pact/shared/request.rb @@ -34,8 +34,8 @@ def method_and_path "#{method.upcase} #{full_path}" end - def full_path - display_path + display_query + def full_path(provider_params = {}) + display_path(provider_params) + display_query(provider_params) end def content_type @@ -83,13 +83,13 @@ def to_hash_without_body_or_query hash end - def display_path - reified_path = Pact::Reification.from_term(path) + def display_path(provider_params) + reified_path = Pact::Reification.from_term(path, provider_params) reified_path.empty? ? "/" : reified_path end - def display_query - (query.nil? || query.empty?) ? '' : "?#{Pact::Reification.from_term(query)}" + def display_query(provider_params) + (query.nil? || query.empty?) ? '' : "?#{Pact::Reification.from_term(query, provider_params)}" end end diff --git a/lib/pact/support.rb b/lib/pact/support.rb index 7edf6d9..1593c39 100644 --- a/lib/pact/support.rb +++ b/lib/pact/support.rb @@ -3,6 +3,7 @@ require 'pact/matchers' require 'pact/logging' require 'pact/term' +require 'pact/provider_param' require 'pact/helpers' require 'pact/configuration' require 'pact/reification' diff --git a/spec/lib/pact/helpers_spec.rb b/spec/lib/pact/helpers_spec.rb index 86a6eff..9b40f56 100644 --- a/spec/lib/pact/helpers_spec.rb +++ b/spec/lib/pact/helpers_spec.rb @@ -5,6 +5,22 @@ module Pact include Pact::Helpers + describe "#provider_param" do + pp = Pact::ProviderParam.new('some/:{var}/here', {var: 'url'}) + + context 'with a hash argument' do + it "creates a Pact::ProviderParam" do + expect(provider_param('some/:{var}/here', {var: 'url'})).to eq(pp) + end + end + + context 'with two string argumnets' do + it 'creates a Pact::ProviderParam' do + expect(provider_param('some/:{var}/here', 'some/url/here').params).to eq({'var' => 'url'}) + end + end + end + describe "#term" do context "with a Hash argument" do diff --git a/spec/lib/pact/matchers/matchers_provider_param_spec.rb b/spec/lib/pact/matchers/matchers_provider_param_spec.rb new file mode 100644 index 0000000..da00cca --- /dev/null +++ b/spec/lib/pact/matchers/matchers_provider_param_spec.rb @@ -0,0 +1,19 @@ +require 'pact/provider_param' + +module Pact + describe Matchers do + + let(:expected) do + { + url: Pact::ProviderParam.new('/some/:{url_var}/here', {url_var: 'url'}) + } + end + + it 'should not have a difference' do + actual = { + url: '/some/url/here' + } + expect(Pact::Matchers.diff(expected, actual)).to be_empty + end + end +end diff --git a/spec/lib/pact/provider_param_spec.rb b/spec/lib/pact/provider_param_spec.rb new file mode 100644 index 0000000..52c69b2 --- /dev/null +++ b/spec/lib/pact/provider_param_spec.rb @@ -0,0 +1,45 @@ +require 'spec_helper' + +module Pact + describe ProviderParam do + + describe 'initialize' do + it 'creates a ProviderParam' do + pp = ProviderParam.new('/some/url/with/:{param}', {param: 'a parameter'}) + expect(pp).to be_instance_of(Pact::ProviderParam) + end + end + + describe 'default_string' do + it 'returns the default string' do + pp = ProviderParam.new('/some/url/with/:{param}', {param: 'a parameter'}) + expect(pp.default_string).to eq('/some/url/with/a parameter') + end + end + + describe 'fill_string' do + it 'returns the fill string' do + pp = ProviderParam.new('/some/:{var}/here:{blah}', {var: 'var', blah: 'aoeu'}) + expect(pp.fill_string).to eq('/some/:{var}/here:{blah}') + expect(pp.default_string).to eq('/some/var/hereaoeu') + end + end + + describe 'initialize with fill string' do + it 'finds the param names' do + pp = ProviderParam.new('/some/:{id}/path_:{here}', {id: '5', here: 'something'}) + expect(pp.params).to eq({'id' => '5', 'here' => 'something'}) + end + + it 'finds the param names from a given default string' do + pp = ProviderParam.new('/some/:{id}/path_:{here}', '/some/4/path_blah') + expect(pp.params).to eq({'id' => '4', 'here' => 'blah'}) + end + + it 'finds the param names with a param at the start' do + pp = ProviderParam.new(':{first}and:{second}then:{third}', 'oneandtwothenthree') + expect(pp.params).to eq({'first' => 'one', 'second' => 'two', 'third' => 'three'}) + end + end + end +end