pact 0.1.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. data/.gitignore +28 -0
  2. data/.rspec +2 -0
  3. data/Gemfile +5 -0
  4. data/Gemfile.lock +83 -0
  5. data/LICENSE.txt +22 -0
  6. data/README.md +238 -0
  7. data/Rakefile +33 -0
  8. data/bin/pact +4 -0
  9. data/lib/pact/app.rb +32 -0
  10. data/lib/pact/configuration.rb +54 -0
  11. data/lib/pact/consumer/app_manager.rb +177 -0
  12. data/lib/pact/consumer/configuration_dsl.rb +71 -0
  13. data/lib/pact/consumer/consumer_contract_builder.rb +79 -0
  14. data/lib/pact/consumer/consumer_contract_builders.rb +10 -0
  15. data/lib/pact/consumer/dsl.rb +98 -0
  16. data/lib/pact/consumer/interaction.rb +70 -0
  17. data/lib/pact/consumer/mock_service.rb +340 -0
  18. data/lib/pact/consumer/rspec.rb +43 -0
  19. data/lib/pact/consumer/run_condor.rb +4 -0
  20. data/lib/pact/consumer/run_mock_contract_service.rb +13 -0
  21. data/lib/pact/consumer/service_consumer.rb +22 -0
  22. data/lib/pact/consumer/service_producer.rb +23 -0
  23. data/lib/pact/consumer.rb +7 -0
  24. data/lib/pact/consumer_contract.rb +110 -0
  25. data/lib/pact/json_warning.rb +23 -0
  26. data/lib/pact/logging.rb +14 -0
  27. data/lib/pact/matchers/matchers.rb +85 -0
  28. data/lib/pact/matchers.rb +1 -0
  29. data/lib/pact/producer/configuration_dsl.rb +62 -0
  30. data/lib/pact/producer/matchers.rb +22 -0
  31. data/lib/pact/producer/pact_spec_runner.rb +57 -0
  32. data/lib/pact/producer/producer_state.rb +81 -0
  33. data/lib/pact/producer/rspec.rb +127 -0
  34. data/lib/pact/producer/test_methods.rb +89 -0
  35. data/lib/pact/producer.rb +1 -0
  36. data/lib/pact/rake_task.rb +64 -0
  37. data/lib/pact/reification.rb +26 -0
  38. data/lib/pact/request.rb +109 -0
  39. data/lib/pact/term.rb +40 -0
  40. data/lib/pact/verification_task.rb +57 -0
  41. data/lib/pact/version.rb +3 -0
  42. data/lib/pact.rb +5 -0
  43. data/lib/tasks/pact.rake +6 -0
  44. data/pact.gemspec +36 -0
  45. data/scratchpad.txt +36 -0
  46. data/spec/features/consumption_spec.rb +146 -0
  47. data/spec/features/producer_states/zebras.rb +28 -0
  48. data/spec/features/production_spec.rb +160 -0
  49. data/spec/integration/pact/configuration_spec.rb +65 -0
  50. data/spec/lib/pact/configuration_spec.rb +35 -0
  51. data/spec/lib/pact/consumer/app_manager_spec.rb +41 -0
  52. data/spec/lib/pact/consumer/consumer_contract_builder_spec.rb +87 -0
  53. data/spec/lib/pact/consumer/dsl_spec.rb +52 -0
  54. data/spec/lib/pact/consumer/interaction_spec.rb +108 -0
  55. data/spec/lib/pact/consumer/mock_service_spec.rb +147 -0
  56. data/spec/lib/pact/consumer/service_consumer_spec.rb +11 -0
  57. data/spec/lib/pact/consumer_contract_spec.rb +125 -0
  58. data/spec/lib/pact/matchers/matchers_spec.rb +354 -0
  59. data/spec/lib/pact/producer/configuration_dsl_spec.rb +101 -0
  60. data/spec/lib/pact/producer/producer_state_spec.rb +103 -0
  61. data/spec/lib/pact/producer/rspec_spec.rb +48 -0
  62. data/spec/lib/pact/reification_spec.rb +43 -0
  63. data/spec/lib/pact/request_spec.rb +316 -0
  64. data/spec/lib/pact/term_spec.rb +36 -0
  65. data/spec/lib/pact/verification_task_spec.rb +64 -0
  66. data/spec/spec_helper.rb +5 -0
  67. data/spec/support/a_consumer-a_producer.json +34 -0
  68. data/spec/support/pact_rake_support.rb +41 -0
  69. data/spec/support/test_app_fail.json +22 -0
  70. data/spec/support/test_app_pass.json +21 -0
  71. data/tasks/pact-test.rake +19 -0
  72. metadata +381 -0
@@ -0,0 +1,127 @@
1
+ require 'open-uri'
2
+ require 'pact/consumer_contract'
3
+ require 'pact/json_warning'
4
+ require_relative 'matchers'
5
+ require_relative 'test_methods'
6
+ require_relative 'configuration_dsl'
7
+
8
+ module Pact
9
+ module Producer
10
+ module RSpec
11
+
12
+ module InstanceMethods
13
+ def app
14
+ Pact.configuration.producer.app
15
+ end
16
+ end
17
+
18
+ module ClassMethods
19
+
20
+ include Pact::JsonWarning
21
+
22
+ def honour_pactfile pactfile_uri, options = {}
23
+ describe "Pact in #{pactfile_uri}" do
24
+ consumer_contract = Pact::ConsumerContract.from_json(read_pact_from(pactfile_uri, options))
25
+ honour_consumer_contract consumer_contract, options
26
+ end
27
+ end
28
+
29
+ def honour_consumer_contract consumer_contract, options = {}
30
+ check_for_active_support_json
31
+ describe_consumer_contract consumer_contract, options.merge({:consumer => consumer_contract.consumer.name})
32
+ end
33
+
34
+ private
35
+
36
+ def describe_consumer_contract consumer_contract, options
37
+ consumer_contract.interactions.each do |interaction|
38
+ describe_interaction_with_producer_state interaction, options
39
+ end
40
+ end
41
+
42
+ def describe_interaction_with_producer_state interaction, options
43
+ if interaction.producer_state
44
+ describe "Given #{interaction.producer_state}" do
45
+ describe_interaction interaction, options
46
+ end
47
+ else
48
+ describe_interaction interaction, options
49
+ end
50
+ end
51
+
52
+ def describe_interaction interaction, options
53
+
54
+ describe description_for(interaction) do
55
+
56
+ before do
57
+ set_up_producer_state interaction.producer_state, options[:consumer]
58
+ replay_interaction interaction
59
+ end
60
+
61
+ after do
62
+ tear_down_producer_state interaction.producer_state, options[:consumer]
63
+ end
64
+
65
+ describe_response interaction.response
66
+ end
67
+
68
+ end
69
+
70
+ def describe_response response
71
+ describe "returns a response which" do
72
+ if response['status']
73
+ it "has status code #{response['status']}" do
74
+ expect(last_response.status).to eql response['status']
75
+ end
76
+ end
77
+
78
+ if response['headers']
79
+ describe "includes headers" do
80
+ response['headers'].each do |name, value|
81
+ it "\"#{name}\" with value \"#{value}\"" do
82
+ expect(last_response.headers[name]).to match_term value
83
+ end
84
+ end
85
+ end
86
+ end
87
+
88
+ if response['body']
89
+ it "has a matching body" do
90
+ logger.debug "Response body is #{last_response.body}"
91
+ expect(parse_entity_from_response(last_response)).to match_term response['body']
92
+ end
93
+ end
94
+ end
95
+ end
96
+
97
+ def description_for interaction
98
+ "#{interaction.description} to #{interaction.request.path}"
99
+ end
100
+
101
+ def read_pact_from uri, options = {}
102
+ pact = open(uri) { | file | file.read }
103
+ if options[:save_pactfile_to_tmp]
104
+ save_pactfile_to_tmp pact, File.basename(uri)
105
+ end
106
+ pact
107
+ rescue StandardError => e
108
+ $stderr.puts "Error reading file from #{uri}"
109
+ $stderr.puts "#{e.to_s} #{e.backtrace.join("\n")}"
110
+ raise e
111
+ end
112
+
113
+ def save_pactfile_to_tmp pact, name
114
+ File.open(Pact.configuration.tmp_dir + "/#{name}", "w") { |file| file << pact}
115
+ end
116
+
117
+ end
118
+ end
119
+ end
120
+ end
121
+
122
+ RSpec.configure do |config|
123
+ config.extend Pact::Producer::RSpec::ClassMethods
124
+ config.include Pact::Producer::RSpec::InstanceMethods
125
+ config.include Pact::Producer::TestMethods
126
+ config.include Pact::Producer::TestMethods
127
+ end
@@ -0,0 +1,89 @@
1
+ require 'pact/logging'
2
+ require 'rack/test'
3
+ require 'pact/reification'
4
+ require 'pact/producer/producer_state'
5
+
6
+ module Pact
7
+ module Producer
8
+ module TestMethods
9
+
10
+ include Pact::Logging
11
+ include Rack::Test::Methods
12
+
13
+ def parse_entity_from_response response
14
+ case response.headers['Content-Type']
15
+ when /json/
16
+ JSON.load(response.body)
17
+ else
18
+ response.body
19
+ end
20
+ end
21
+
22
+ def set_up_producer_state producer_state_name, consumer
23
+ if producer_state_name
24
+ get_producer_state(producer_state_name, consumer).set_up
25
+ end
26
+ end
27
+
28
+ def tear_down_producer_state producer_state_name, consumer
29
+ if producer_state_name
30
+ get_producer_state(producer_state_name, consumer).tear_down
31
+ end
32
+ end
33
+
34
+ def replay_interaction interaction
35
+ request = interaction.request
36
+ path = request_path(request)
37
+ args = [path, request_body(request)]
38
+
39
+ if !request.headers.nil?
40
+ args << request_headers(request)
41
+ end
42
+
43
+ logger.debug "Sending #{request.method} with #{args}"
44
+ self.send(request.method, *args)
45
+ end
46
+
47
+ private
48
+
49
+ def request_headers request
50
+ request_headers = {}
51
+ request.headers.each do |key, value|
52
+ key = key.upcase
53
+ if key.match(/CONTENT.TYPE/)
54
+ request_headers['CONTENT_TYPE'] = value
55
+ else
56
+ request_headers['HTTP_' + key.to_s] = value
57
+ end
58
+ end
59
+ request_headers
60
+ end
61
+
62
+ def request_path request
63
+ path = request.path
64
+ query = request.query
65
+ if query && !query.empty?
66
+ path += "?" + request.query
67
+ end
68
+ path
69
+ end
70
+
71
+ def request_body request
72
+ body = request.body
73
+ if body
74
+ body = JSON.dump(Pact::Reification.from_term(body))
75
+ else
76
+ body = ""
77
+ end
78
+ end
79
+
80
+ def get_producer_state producer_state_name, consumer
81
+ unless producer_state = ProducerState.get(producer_state_name, :for => consumer)
82
+ extra = consumer ? " for consumer \"#{consumer}\"" : ""
83
+ raise "Could not find a producer state defined for \"#{producer_state_name}\"#{extra}. Have you required the producer state file in your spec?"
84
+ end
85
+ producer_state
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1 @@
1
+ #TODO delete this file after checking if any projects load it
@@ -0,0 +1,64 @@
1
+ require 'rake/tasklib'
2
+
3
+
4
+ module Pact
5
+
6
+ ##
7
+ # To enable `rake pact`, put something like this in your Rakefile:
8
+ #
9
+ # ```
10
+ # require 'pact/rake_task'
11
+ #
12
+ # Pact::RakeTask.new do |pact|
13
+ # pact.file 'spec/pacts/some-pact.json',
14
+ # from_url: 'http://example.com/some-pact.json'
15
+ # pact.file 'spec/pacts/other-pact.json',
16
+ # from_url: 'http://example.com/other-pact.json'
17
+ # end
18
+ # ```
19
+ class RakeTask < ::Rake::TaskLib
20
+ attr_reader :connections
21
+
22
+ def initialize(name = :pact)
23
+ @connections = []
24
+
25
+ yield self
26
+
27
+ namespace name do
28
+ desc "Update integration pacts from external sources"
29
+ task :pull do
30
+ connections.each do |conn|
31
+ body = fetch conn[:url]
32
+ File.open(conn[:file], 'w') {|f| f.write body }
33
+ puts "Wrote #{conn[:url]} to #{conn[:file]}"
34
+ end
35
+ end
36
+ end
37
+ task name => "#{name}:pull" # default task for the namespace
38
+ end
39
+
40
+ def file(filename, options = {})
41
+ url = options.fetch(:from_url)
42
+ @connections << {file: filename, url: url}
43
+ end
44
+
45
+ private
46
+ # written with plain Net::HTTP to avoid bringing in extra dependencies
47
+ def fetch(url_string, redirection_limit = 5)
48
+ raise 'Too many HTTP redirects' if redirection_limit == 0
49
+ url = URI.parse(url_string)
50
+ response = Net::HTTP.get_response(url)
51
+ case response
52
+ when Net::HTTPSuccess then
53
+ response.body
54
+ when Net::HTTPRedirection then
55
+ location = response['Location']
56
+ fetch(location, redirection_limit - 1)
57
+ else
58
+ response.value # raise the appropriate error
59
+ end
60
+ end
61
+ end
62
+
63
+
64
+ end
@@ -0,0 +1,26 @@
1
+ require 'randexp'
2
+
3
+ module Pact
4
+ module Reification
5
+
6
+ def self.from_term(term)
7
+ case
8
+ when term.respond_to?(:generate)
9
+ term.generate
10
+ when term.is_a?(Hash)
11
+ term.inject({}) do |mem, (key,term)|
12
+ mem[key] = from_term(term)
13
+ mem
14
+ end
15
+ when term.is_a?(Array)
16
+ term.inject([]) do |mem, term|
17
+ mem << from_term(term)
18
+ mem
19
+ end
20
+ else
21
+ term
22
+ end
23
+ end
24
+
25
+ end
26
+ end
@@ -0,0 +1,109 @@
1
+ require 'pact/matchers'
2
+
3
+ module Pact
4
+
5
+ module Request
6
+
7
+ class NullExpectation
8
+ def to_s
9
+ "<No expectation>"
10
+ end
11
+
12
+ def ==(other_object)
13
+ other_object.is_a? NullExpectation
14
+ end
15
+
16
+ def ===(other_object)
17
+ other_object.is_a? NullExpectation
18
+ end
19
+
20
+ def eql?(other_object)
21
+ self == other_object
22
+ end
23
+
24
+ def hash
25
+ 2934820948209428748274238642672
26
+ end
27
+
28
+ def empty?
29
+ true
30
+ end
31
+
32
+ def nil?
33
+ true
34
+ end
35
+ end
36
+
37
+ class Base
38
+ include Pact::Matchers
39
+ extend Pact::Matchers
40
+
41
+ NULL_EXPECTATION = NullExpectation.new
42
+
43
+ attr_reader :method, :path, :headers, :body, :query
44
+
45
+ def self.from_hash(hash)
46
+ sym_hash = hash.inject({}) { |memo, (k,v)| memo[k.to_sym] = v; memo }
47
+ method = sym_hash.fetch(:method)
48
+ path = sym_hash.fetch(:path)
49
+ query = sym_hash.fetch(:query, NULL_EXPECTATION)
50
+ headers = sym_hash.fetch(:headers, NULL_EXPECTATION)
51
+ body = sym_hash.fetch(:body, NULL_EXPECTATION)
52
+ new(method, path, headers, body, query)
53
+ end
54
+
55
+ def initialize(method, path, headers, body, query)
56
+ @method = method.to_s
57
+ @path = path.chomp('/')
58
+ @headers = headers
59
+ @body = body
60
+ @query = query
61
+ end
62
+
63
+ def to_json(options = {})
64
+ as_json.to_json(options)
65
+ end
66
+
67
+ def as_json
68
+ base_json = {
69
+ method: method,
70
+ path: path,
71
+ }
72
+
73
+ base_json.merge!(body: body) unless body.is_a? NullExpectation
74
+ base_json.merge!(headers: headers) unless headers.is_a? NullExpectation
75
+ base_json.merge!(query: query) unless query.is_a? NullExpectation
76
+ base_json
77
+ end
78
+
79
+ end
80
+
81
+ class Expected < Base
82
+
83
+ attr_accessor :description
84
+
85
+ def self.from_hash(hash)
86
+ request = super
87
+ request.description = hash.fetch(:description, nil)
88
+ request
89
+ end
90
+
91
+ def match(actual_request)
92
+ difference(actual_request).empty?
93
+ end
94
+
95
+ def matches_route? actual_request
96
+ diff({:method => method, :path => path}, {:method => actual_request.method, :path => actual_request.path}).empty?
97
+ end
98
+
99
+ def difference(actual_request)
100
+ diff(as_json, actual_request.as_json)
101
+ end
102
+
103
+ end
104
+
105
+ class Actual < Base
106
+ end
107
+
108
+ end
109
+ end
data/lib/pact/term.rb ADDED
@@ -0,0 +1,40 @@
1
+ module Pact
2
+ class Term
3
+
4
+ attr_reader :generate, :matcher
5
+
6
+ def self.json_create(obj)
7
+ new(generate: obj['data']['generate'], matcher: obj['data']['matcher'])
8
+ end
9
+
10
+ def initialize(attributes = {})
11
+ @generate = attributes[:generate]
12
+ @matcher = attributes[:matcher]
13
+ end
14
+
15
+ def to_json(options = {})
16
+ { json_class: self.class.name, data: { generate: generate, matcher: matcher} }.to_json(options)
17
+ end
18
+
19
+ def match(literal)
20
+ literal.respond_to?(:to_s) ? matcher.match(literal.to_s) : nil
21
+ end
22
+
23
+ def ==(other)
24
+ return false unless other.respond_to?(:generate) && other.respond_to?(:matcher)
25
+ generate == other.generate && matcher == other.matcher
26
+ end
27
+
28
+ def to_s
29
+ "Pact::Term matcher: #{matcher.to_s}" + (generate.nil? ? "" : " generate: \"#{generate}\"")
30
+ end
31
+
32
+ def diff_with_actual(actual)
33
+ match(actual) ? nil : {
34
+ expected: self,
35
+ actual: actual
36
+ }
37
+ end
38
+
39
+ end
40
+ end
@@ -0,0 +1,57 @@
1
+ require 'rake/tasklib'
2
+ require 'pact/producer/pact_spec_runner'
3
+
4
+ =begin
5
+ To create a rake pact:verify:<something> task
6
+
7
+ Pact::VerificationTask.new(:head) do | pact |
8
+ pact.uri 'http://master.cd.vpc.realestate.com.au/browse/BIQ-MAS/latestSuccessful/artifact/JOB2/Pacts/mas-contract_transaction_service.json',
9
+ support_file: './spec/consumers/pact_helper'
10
+ pact.uri 'http://master.cd.vpc.realestate.com.au/browse/BIQ-IMAGINARY-CONSUMER/latestSuccessful/artifact/JOB2/Pacts/imaginary_consumer-contract_transaction_service.json',
11
+ support_file: './spec/consumers/pact_helper'
12
+ end
13
+
14
+ The pact.uri may be a local file system path or a remote URL.
15
+ The support_file should include code that makes your rack app available for the rack testing framework.
16
+ Eg.
17
+
18
+ Pact.configure do | config |
19
+ config.producer do
20
+ name "My Producer"
21
+ app { TestApp.new }
22
+ end
23
+ end
24
+
25
+ It should also load all your app's dependencies (eg by calling out to spec_helper)
26
+
27
+ =end
28
+
29
+ module Pact
30
+ class VerificationTask < ::Rake::TaskLib
31
+ attr_reader :pact_spec_config
32
+
33
+ def initialize(name)
34
+ @pact_spec_config = []
35
+
36
+ yield self
37
+
38
+ namespace :pact do
39
+ desc "Verify producer against the consumer pacts for #{name}"
40
+ task "verify:#{name}" do
41
+ exit_status = Producer::PactSpecRunner.run(pact_spec_config)
42
+ fail failure_message if exit_status != 0
43
+ end
44
+
45
+ def failure_message
46
+ "\n* * * * * * * * * * * * * * * * * * *\n" +
47
+ "Producer did not honour pact file.\nSee\n * #{Pact.configuration.log_path}\n * #{Pact.configuration.tmp_dir}\nfor logs and pact files." +
48
+ "\n* * * * * * * * * * * * * * * * * * *\n\n"
49
+ end
50
+ end
51
+ end
52
+
53
+ def uri(uri, options)
54
+ @pact_spec_config << {uri: uri, support_file: options[:support_file], consumer: options[:consumer]}
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,3 @@
1
+ module Pact
2
+ VERSION = "0.1.28"
3
+ end
data/lib/pact.rb ADDED
@@ -0,0 +1,5 @@
1
+ require_relative 'pact/version'
2
+ require_relative 'pact/configuration'
3
+ require_relative 'pact/consumer'
4
+ require_relative 'pact/producer'
5
+ require_relative 'pact/consumer_contract'
@@ -0,0 +1,6 @@
1
+ namespace :pact do
2
+ desc "Runs all the tasks prefixed with pact:verify in this project"
3
+ task :verify do
4
+ Rake::Task.tasks.find_all{ | task| task.name.start_with? "pact:verify:"}.map(&:invoke)
5
+ end
6
+ end
data/pact.gemspec ADDED
@@ -0,0 +1,36 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'pact/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "pact"
8
+ gem.version = Pact::VERSION
9
+ gem.authors = ["James Fraser", "Sergei Matheson", "Brent Snook", "Ronald Holshausen", "Bethany Skurrie"]
10
+ gem.email = ["james.fraser@alumni.swinburne.edu", "sergei.matheson@gmail.com", "brent@fuglylogic.com", "uglyog@gmail.com", "bskurrie@dius.com.au"]
11
+ gem.description = %q{Define a pact between service consumers and providers}
12
+ gem.summary = %q{Define a pact between service consumers and providers}
13
+ gem.homepage = "https://github.com/uglyog/pact.git"
14
+
15
+ gem.files = `git ls-files`.split($/)
16
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
17
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
+ gem.require_paths = ["lib"]
19
+ gem.license = 'MIT'
20
+
21
+ gem.add_runtime_dependency 'randexp', '~> 0.1.7'
22
+ gem.add_runtime_dependency 'hashie', '~> 2.0.5'
23
+ gem.add_runtime_dependency 'rspec', '~> 2.12'
24
+ gem.add_runtime_dependency 'find_a_port', '~> 1.0.1'
25
+ gem.add_runtime_dependency 'rack-test', '~> 0.6.2'
26
+ gem.add_runtime_dependency 'awesome_print', '~> 1.1.0'
27
+ gem.add_runtime_dependency 'capybara', '~> 2.1.0'
28
+ gem.add_runtime_dependency 'thor'
29
+ gem.add_runtime_dependency 'thin'
30
+ gem.add_runtime_dependency 'json' #Not locking down a version because buncher gem requires 1.6, while other projects use 1.7.
31
+
32
+ gem.add_development_dependency 'geminabox-client'
33
+ gem.add_development_dependency 'rake', '~> 10.0.3'
34
+ gem.add_development_dependency 'webmock', '~> 1.9.3'
35
+ gem.add_development_dependency 'pry'
36
+ end
data/scratchpad.txt ADDED
@@ -0,0 +1,36 @@
1
+ Ideas for expectation DSL
2
+
3
+ Unfortunately RSpec has already stolen the method "example".
4
+ Could use:
5
+ eg
6
+ ex
7
+
8
+ my_producer.
9
+ given("a thing exists").
10
+ upon_receiving("a request for a thing").with({:method => 'get', :path => '/thing'})
11
+ will_respond_with({:body => {
12
+ name: ex("Fred"),
13
+ age: eg 29,
14
+ mobile: eg("0415 134 234", /\d{4} \d{3} \d{3}/),
15
+ dob: eg("1983-02-28", match: /\d\d\d\d-\d\d-\d\d/),
16
+ driver_licence_number: eg(12345678, size: 8),
17
+ children: eg([{name: 'Mary'}])
18
+ }})
19
+
20
+ eg([ {name: 'Mary'} ]) should match any array where every element has a name String
21
+ eg([ {name: example('Mary', size: 4) } ]) should match any array where every element has a 4 letter name
22
+
23
+ eg("Fred") could return Pact::Term.new(:matcher => /.+/, :generate => 'Fred')
24
+ eg(29) could return Pact::Term.new(:matcher => /\d+/, :generate => 29)
25
+
26
+ Need a way to specify a literal empty hash, rather than a hash that matches anything as {} currently does.
27
+
28
+ {:something => literal({}) }
29
+ {:something => actual({}) }
30
+ {:something => empty_hash }
31
+
32
+ Slightly unintuitive behaviour: {} matches any hash, but [] only matches an empty array (or does now we've changed the code). Should [] match any array? How do we then specify an empty array?
33
+
34
+ {:something => literal([]) }
35
+ {:something => actual([]) }
36
+ {:something => empty_array }