Skip to content

Commit

Permalink
feat: add support for writing v3 matching rules
Browse files Browse the repository at this point in the history
  • Loading branch information
bethesque committed Apr 3, 2018
1 parent e5881e3 commit fc89696
Show file tree
Hide file tree
Showing 3 changed files with 341 additions and 2 deletions.
11 changes: 9 additions & 2 deletions lib/pact/matching_rules.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,18 @@ module MatchingRules

# @api public Used by pact-mock_service
def self.extract object_graph, options = {}
Extract.(object_graph)
pact_specification_version = options[:pact_specification_version] || Pact::SpecificationVersion::NIL_VERSION
case pact_specification_version.major
when nil, 0, 1, 2
Extract.(object_graph, matching_rules)
else
V3::Extract.(object_graph, matching_rules)
end
end

def self.merge object_graph, matching_rules, options = {}
case options[:pact_specification_version].major
pact_specification_version = options[:pact_specification_version] || Pact::SpecificationVersion::NIL_VERSION
case pact_specification_version.major
when nil, 0, 1, 2
Merge.(object_graph, matching_rules)
else
Expand Down
94 changes: 94 additions & 0 deletions lib/pact/matching_rules/v3/extract.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
require 'pact/something_like'
require 'pact/array_like'
require 'pact/term'

module Pact
module MatchingRules::V3
class Extract

def self.call matchable
new(matchable).call
end

def initialize matchable
@matchable = matchable
@rules = Hash.new
end

def call
recurse matchable, "$", nil
rules
end

private

attr_reader :matchable, :rules

def recurse object, path, match_type
case object
when Hash then recurse_hash(object, path, match_type)
when Array then recurse_array(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::QueryString then recurse(object.query, path, match_type)
when Pact::QueryHash then recurse_hash(object.query, path, match_type)
end
end

def recurse_hash hash, path, match_type
hash.each do | (key, value) |
recurse value, "#{path}#{next_path_part(key)}", match_type
end
end

def recurse_array new_array, path, match_type
new_array.each_with_index do | value, index |
recurse value, "#{path}[#{index}]", match_type
end
end

def handle_something_like something_like, path, match_type
record_match_type_rule path, "type"
recurse something_like.contents, path, "type"
end

def handle_array_like array_like, path, match_type
record_rule "#{path}", 'min' => array_like.min
record_match_type_rule "#{path}[*].*", 'type'
recurse array_like.contents, "#{path}[*]", :array_like
end

def record_rule path, rule
rules[path] ||= {}
rules[path]['matchers'] ||= []
rules[path]['matchers'] << rule
end

def record_regex_rule term, path
rules[path] ||= {}
rules[path]['matchers'] ||= []
rule = { 'match' => 'regex', 'regex' => term.matcher.inspect[1..-2]}
rules[path]['matchers'] << rule
end

def record_match_type_rule path, match_type
unless match_type == :array_like || match_type.nil?
rules[path] ||= {}
rules[path]['matchers'] ||= []
rules[path]['matchers'] << { 'match' => match_type }
end
end

# Beth: there's a potential bug if the key contains a dot and a single quote.
# Not sure what to do then.
def next_path_part key
if key.to_s.include?('.')
"['#{key}']"
else
".#{key}"
end
end
end
end
end
238 changes: 238 additions & 0 deletions spec/lib/pact/matching_rules/v3/extract_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,238 @@
require 'pact/matching_rules/v3/extract'
require 'pact/support'

module Pact
module MatchingRules::V3
describe Extract do

describe ".call" do

subject { Extract.call(matchable) }

context "with a Pact::SomethingLike" do
let(:matchable) do
{
body: Pact::SomethingLike.new(foo: 'bar', alligator: { name: 'Mary' })
}
end

let(:rules) do
{
"$.body" => {
"matchers" => [ {"match" => "type"} ]
}
}
end

it "creates a rule that matches by type" do
expect(subject).to eq rules
end
end

context "with a Pact::Term" do
let(:matchable) do
{
body: {
alligator: {
name: Pact::Term.new(generate: 'Mary', matcher: /.*a/)
}
}
}
end

let(:rules) do
{
"$.body.alligator.name" => {
"matchers" => [ {"match" => "regex", "regex" => ".*a"} ]
}
}
end

it "creates a rule that matches by regex" do
expect(subject).to eq rules
end
end

context "with a Pact::SomethingLike containing a Term" do
let(:matchable) do
{
body: Pact::SomethingLike.new(
foo: 'bar',
alligator: { name: Pact::Term.new(generate: 'Mary', matcher: /.*a/) }
)
}
end

let(:rules) do
{
"$.body" => {
"matchers" => [ {"match" => "type"} ]
},
"$.body.alligator.name" => {
"matchers" => [ {"match" => "regex", "regex"=>".*a"} ]
},
}
end

it "the match:regex overrides the match:type" do
expect(subject).to eq rules
end
end

context "with a Pact::SomethingLike containing an array" do
let(:matchable) do
{
body: Pact::SomethingLike.new(
alligators: [
{name: 'Mary'},
{name: 'Betty'}
]
)
}
end

let(:rules) do
{
"$.body" => {
"matchers" => [ {"match" => "type"} ]
}
}
end

it "lists a rule for each item" do
expect(subject).to eq rules
end
end

context "with an ArrayLike" do
let(:matchable) do
{
body: {
alligators: Pact::ArrayLike.new(
name: 'Fred'
)
}
}
end

let(:rules) do
{
"$.body.alligators" => {
"matchers" => [ {"min" => 1} ]
},
"$.body.alligators[*].*" => {
"matchers" => [ {"match" => "type"} ]
}
}
end

it "lists a rule for all items" do
expect(subject).to eq rules
end
end

context "with an ArrayLike with a Pact::Term inside" do
let(:matchable) do
{
body: {
alligators: Pact::ArrayLike.new(
name: 'Fred',
phoneNumber: Pact::Term.new(generate: '1234567', matcher: /\d+/)
)
}
}
end

let(:rules) do
{
"$.body.alligators" => {
"matchers" => [ {"min" => 1} ]
},
"$.body.alligators[*].*" => {
"matchers" => [ {"match" => "type"} ]
},
"$.body.alligators[*].phoneNumber" => {
"matchers" => [ {"match" => "regex", "regex" => "\\d+"} ]
}
}
end

it "lists a rule that specifies that the regular expression must match" do
expect(subject).to eq rules
end
end

context "with a Pact::QueryString containing a Pact::Term" do
let(:matchable) do
{
query: Pact::QueryString.new(Pact::Term.new(generate: 'foobar', matcher: /foo/))
}
end

let(:rules) do
{
"$.query" => {
"matchers" => [ {"match" => "regex", "regex" => "foo"} ]
}
}
end

it "lists a rule that specifies that the regular expression must match" do
expect(subject).to eq rules
end
end

context "with a Pact::QueryHash containing a Pact::Term" do
let(:matchable) do
{
query: Pact::QueryHash.new(bar: Pact::Term.new(generate: 'foobar', matcher: /foo/))
}
end

let(:rules) do
{
"$.query.bar[0]" => {
"matchers" => [ {"match" => "regex", "regex" => "foo"} ]
}
}
end

it "lists a rule that specifies that the regular expression must match" do
expect(subject).to eq rules
end
end

context "with no special matching" do
let(:matchable) do
{
body: { alligator: { name: 'Mary' } }
}
end

let(:rules) do
{}
end


it "does not create any rules" do
expect(subject).to eq rules
end
end

context "with a key containing a dot" do
let(:matchable) do
{
"key" => {
"key.with.dots" => Pact::SomethingLike.new("foo")
}
}
end

it "uses square brackets notation for the key with dots" do
expect(subject.keys).to include "$.key['key.with.dots']"
end
end
end
end
end
end

0 comments on commit fc89696

Please sign in to comment.