pact-support 0.0.1

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 (107) hide show
  1. checksums.yaml +15 -0
  2. data/.gitignore +29 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +8 -0
  5. data/CHANGELOG.md +4 -0
  6. data/Gemfile +4 -0
  7. data/Gemfile.lock +80 -0
  8. data/LICENSE.txt +22 -0
  9. data/README.md +4 -0
  10. data/Rakefile +4 -0
  11. data/lib/pact/configuration.rb +164 -0
  12. data/lib/pact/consumer/request.rb +27 -0
  13. data/lib/pact/consumer_contract.rb +1 -0
  14. data/lib/pact/consumer_contract/consumer_contract.rb +114 -0
  15. data/lib/pact/consumer_contract/file_name.rb +19 -0
  16. data/lib/pact/consumer_contract/headers.rb +51 -0
  17. data/lib/pact/consumer_contract/interaction.rb +73 -0
  18. data/lib/pact/consumer_contract/pact_file.rb +24 -0
  19. data/lib/pact/consumer_contract/request.rb +73 -0
  20. data/lib/pact/consumer_contract/response.rb +51 -0
  21. data/lib/pact/consumer_contract/service_consumer.rb +28 -0
  22. data/lib/pact/consumer_contract/service_provider.rb +28 -0
  23. data/lib/pact/logging.rb +14 -0
  24. data/lib/pact/matchers.rb +1 -0
  25. data/lib/pact/matchers/actual_type.rb +16 -0
  26. data/lib/pact/matchers/base_difference.rb +37 -0
  27. data/lib/pact/matchers/differ.rb +153 -0
  28. data/lib/pact/matchers/difference.rb +13 -0
  29. data/lib/pact/matchers/difference_indicator.rb +26 -0
  30. data/lib/pact/matchers/embedded_diff_formatter.rb +62 -0
  31. data/lib/pact/matchers/expected_type.rb +35 -0
  32. data/lib/pact/matchers/index_not_found.rb +15 -0
  33. data/lib/pact/matchers/list_diff_formatter.rb +101 -0
  34. data/lib/pact/matchers/matchers.rb +139 -0
  35. data/lib/pact/matchers/no_diff_indicator.rb +18 -0
  36. data/lib/pact/matchers/regexp_difference.rb +13 -0
  37. data/lib/pact/matchers/type_difference.rb +16 -0
  38. data/lib/pact/matchers/unexpected_index.rb +11 -0
  39. data/lib/pact/matchers/unexpected_key.rb +11 -0
  40. data/lib/pact/matchers/unix_diff_formatter.rb +114 -0
  41. data/lib/pact/reification.rb +28 -0
  42. data/lib/pact/rspec.rb +53 -0
  43. data/lib/pact/shared/active_support_support.rb +51 -0
  44. data/lib/pact/shared/dsl.rb +76 -0
  45. data/lib/pact/shared/jruby_support.rb +18 -0
  46. data/lib/pact/shared/json_differ.rb +15 -0
  47. data/lib/pact/shared/key_not_found.rb +15 -0
  48. data/lib/pact/shared/null_expectation.rb +31 -0
  49. data/lib/pact/shared/request.rb +97 -0
  50. data/lib/pact/shared/text_differ.rb +14 -0
  51. data/lib/pact/something_like.rb +49 -0
  52. data/lib/pact/support.rb +9 -0
  53. data/lib/pact/support/version.rb +5 -0
  54. data/lib/pact/symbolize_keys.rb +12 -0
  55. data/lib/pact/term.rb +85 -0
  56. data/lib/tasks/pact.rake +29 -0
  57. data/pact-support.gemspec +35 -0
  58. data/spec/lib/pact/consumer/request_spec.rb +25 -0
  59. data/spec/lib/pact/consumer_contract/active_support_support_spec.rb +58 -0
  60. data/spec/lib/pact/consumer_contract/consumer_contract_spec.rb +141 -0
  61. data/spec/lib/pact/consumer_contract/headers_spec.rb +107 -0
  62. data/spec/lib/pact/consumer_contract/interaction_spec.rb +151 -0
  63. data/spec/lib/pact/consumer_contract/request_spec.rb +329 -0
  64. data/spec/lib/pact/consumer_contract/response_spec.rb +73 -0
  65. data/spec/lib/pact/matchers/differ_spec.rb +215 -0
  66. data/spec/lib/pact/matchers/difference_spec.rb +22 -0
  67. data/spec/lib/pact/matchers/embedded_diff_formatter_spec.rb +90 -0
  68. data/spec/lib/pact/matchers/index_not_found_spec.rb +21 -0
  69. data/spec/lib/pact/matchers/list_diff_formatter_spec.rb +120 -0
  70. data/spec/lib/pact/matchers/matchers_spec.rb +500 -0
  71. data/spec/lib/pact/matchers/regexp_difference_spec.rb +20 -0
  72. data/spec/lib/pact/matchers/type_difference_spec.rb +34 -0
  73. data/spec/lib/pact/matchers/unexpected_index_spec.rb +20 -0
  74. data/spec/lib/pact/matchers/unexpected_key_spec.rb +20 -0
  75. data/spec/lib/pact/matchers/unix_diff_formatter_spec.rb +216 -0
  76. data/spec/lib/pact/reification_spec.rb +67 -0
  77. data/spec/lib/pact/shared/dsl_spec.rb +86 -0
  78. data/spec/lib/pact/shared/json_differ_spec.rb +36 -0
  79. data/spec/lib/pact/shared/key_not_found_spec.rb +20 -0
  80. data/spec/lib/pact/shared/request_spec.rb +196 -0
  81. data/spec/lib/pact/shared/text_differ_spec.rb +54 -0
  82. data/spec/lib/pact/something_like_spec.rb +21 -0
  83. data/spec/lib/pact/term_spec.rb +89 -0
  84. data/spec/spec_helper.rb +19 -0
  85. data/spec/support/a_consumer-a_producer.json +32 -0
  86. data/spec/support/a_consumer-a_provider.json +32 -0
  87. data/spec/support/active_support_if_configured.rb +6 -0
  88. data/spec/support/case-insensitive-response-header-matching.json +21 -0
  89. data/spec/support/consumer_contract_template.json +24 -0
  90. data/spec/support/dsl_spec_support.rb +7 -0
  91. data/spec/support/factories.rb +82 -0
  92. data/spec/support/generated_index.md +4 -0
  93. data/spec/support/generated_markdown.md +55 -0
  94. data/spec/support/interaction_view_model.json +63 -0
  95. data/spec/support/interaction_view_model_with_terms.json +50 -0
  96. data/spec/support/markdown_pact.json +48 -0
  97. data/spec/support/missing_provider_states_output.txt +25 -0
  98. data/spec/support/options.json +21 -0
  99. data/spec/support/shared_examples_for_request.rb +94 -0
  100. data/spec/support/spec_support.rb +20 -0
  101. data/spec/support/stubbing.json +22 -0
  102. data/spec/support/term.json +48 -0
  103. data/spec/support/test_app_fail.json +61 -0
  104. data/spec/support/test_app_pass.json +38 -0
  105. data/spec/support/test_app_with_right_content_type_differ.json +23 -0
  106. data/tasks/spec.rake +6 -0
  107. metadata +401 -0
@@ -0,0 +1,14 @@
1
+ require 'pact/matchers/matchers'
2
+ require 'pact/matchers/difference'
3
+
4
+ module Pact
5
+ class TextDiffer
6
+
7
+ extend Matchers
8
+
9
+ def self.call expected, actual, options = {}
10
+ diff expected, actual, options
11
+ end
12
+
13
+ end
14
+ end
@@ -0,0 +1,49 @@
1
+ require 'pact/symbolize_keys'
2
+ module Pact
3
+
4
+ # Specifies that the actual object should be considered a match if
5
+ # it includes the same keys, and the values of the keys are of the same class.
6
+
7
+ class SomethingLike
8
+ include SymbolizeKeys
9
+
10
+ attr_reader :contents
11
+
12
+ def initialize contents
13
+ @contents = contents
14
+ end
15
+
16
+ def to_hash
17
+ {
18
+ :json_class => self.class.name,
19
+ :contents => contents
20
+ }
21
+ end
22
+
23
+ def as_json opts = {}
24
+ to_hash
25
+ end
26
+
27
+ def to_json opts = {}
28
+ as_json.to_json opts
29
+ end
30
+
31
+ def self.json_create hash
32
+ new(symbolize_keys(hash)[:contents])
33
+ end
34
+
35
+ def eq other
36
+ self == other
37
+ end
38
+
39
+ def == other
40
+ other.is_a?(SomethingLike) && other.contents == self.contents
41
+ end
42
+
43
+ def generate
44
+ contents
45
+ end
46
+ end
47
+ end
48
+
49
+
@@ -0,0 +1,9 @@
1
+ require 'pact/support/version'
2
+ require 'pact/consumer_contract'
3
+ require 'pact/matchers'
4
+ require 'pact/logging'
5
+ require 'pact/term'
6
+ require 'pact/something_like'
7
+ require 'pact/configuration'
8
+ require 'pact/reification'
9
+ require 'pact/rspec'
@@ -0,0 +1,5 @@
1
+ module Pact
2
+ module Support
3
+ VERSION = "0.0.1"
4
+ end
5
+ end
@@ -0,0 +1,12 @@
1
+ module Pact
2
+ module SymbolizeKeys
3
+
4
+ def self.included(base)
5
+ base.extend(self)
6
+ end
7
+
8
+ def symbolize_keys hash
9
+ hash.inject({}) { |memo, (k,v)| memo[k.to_sym] = v; memo }
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,85 @@
1
+ require 'pact/shared/active_support_support'
2
+ require 'json/add/regexp'
3
+
4
+ module Pact
5
+ class Term
6
+
7
+ include Pact::ActiveSupportSupport
8
+
9
+ attr_reader :generate, :matcher
10
+
11
+ def self.json_create(obj)
12
+ new(generate: obj['data']['generate'], matcher: obj['data']['matcher'])
13
+ end
14
+
15
+ def self.unpack_regexps source
16
+ case source
17
+ when Pact::Term then source.matcher
18
+ when Array then unpack_regexps_from_array source
19
+ when Hash then unpack_regexps_from_hash source
20
+ else
21
+ source
22
+ end
23
+ end
24
+
25
+ def initialize(attributes = {})
26
+ @generate = attributes[:generate]
27
+ @matcher = attributes[:matcher]
28
+ raise "Please specify a matcher for the Term" unless @matcher != nil
29
+ raise "Please specify a value to generate for the Term" unless @generate != nil
30
+ raise "Value to generate \"#{@generate}\" does not match regular expression #{@matcher}" unless @generate =~ @matcher
31
+ end
32
+
33
+ def to_hash
34
+ { json_class: self.class.name, data: { generate: generate, matcher: fix_regexp(matcher)} }
35
+ end
36
+
37
+ def as_json(options = {})
38
+ to_hash
39
+ end
40
+
41
+
42
+ def to_json(options = {})
43
+ as_json.to_json(options)
44
+ end
45
+
46
+ def match(literal)
47
+ literal.respond_to?(:to_s) ? matcher.match(literal.to_s) : nil
48
+ end
49
+
50
+ def ==(other)
51
+ return false unless other.respond_to?(:generate) && other.respond_to?(:matcher)
52
+ generate == other.generate && matcher == other.matcher
53
+ end
54
+
55
+ def to_s
56
+ "Pact::Term matcher: #{matcher.inspect}" + (generate.nil? ? "" : " generate: \"#{generate}\"")
57
+ end
58
+
59
+ def diff_with_actual(actual)
60
+ match(actual) ? nil : {
61
+ expected: self,
62
+ actual: actual
63
+ }
64
+ end
65
+
66
+ def empty?
67
+ false
68
+ end
69
+
70
+ private
71
+
72
+ def self.unpack_regexps_from_array source
73
+ source.each_with_object([]) do | item, destination |
74
+ destination << unpack_regexps(item)
75
+ end
76
+ end
77
+
78
+ def self.unpack_regexps_from_hash source
79
+ source.keys.each_with_object({}) do | key, destination |
80
+ destination[key] = unpack_regexps source[key]
81
+ end
82
+ end
83
+
84
+ end
85
+ end
@@ -0,0 +1,29 @@
1
+
2
+ namespace :pact do
3
+
4
+ desc "Verifies the pact files configured in the pact_helper.rb against this service provider."
5
+ task :verify do
6
+
7
+ require 'pact/tasks/task_helper'
8
+
9
+ include Pact::TaskHelper
10
+
11
+ handle_verification_failure do
12
+ execute_pact_verify
13
+ end
14
+ end
15
+
16
+ desc "Verifies the pact at the given URI against this service provider."
17
+ task 'verify:at', :pact_uri do | t, args |
18
+ require 'term/ansicolor'
19
+ require 'pact/tasks/task_helper'
20
+
21
+ include Pact::TaskHelper
22
+
23
+ abort(::Term::ANSIColor.red("Please provide a pact URI. eg. rake pact:verify:at[../my-consumer/spec/pacts/my_consumer-my_provider.json]")) unless args[:pact_uri]
24
+ handle_verification_failure do
25
+ execute_pact_verify args[:pact_uri]
26
+ end
27
+ end
28
+
29
+ end
@@ -0,0 +1,35 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'pact/support/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "pact-support"
8
+ gem.version = Pact::Support::VERSION
9
+ gem.authors = ["James Fraser", "Sergei Matheson", "Brent Snook", "Ronald Holshausen", "Beth 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.summary = %q{Shared code for Pact gems}
12
+ gem.homepage = "https://github.com/bethesque/pact-support"
13
+
14
+ gem.files = `git ls-files`.split($/)
15
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
16
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
17
+ gem.require_paths = ["lib"]
18
+ gem.license = 'MIT'
19
+
20
+ gem.add_runtime_dependency 'randexp', '~> 0.1.7'
21
+ gem.add_runtime_dependency 'rspec', '>=2.14'
22
+ gem.add_runtime_dependency 'rack-test', '~> 0.6.2'
23
+ gem.add_runtime_dependency 'awesome_print', '~> 1.1'
24
+ gem.add_runtime_dependency 'json'
25
+ gem.add_runtime_dependency 'term-ansicolor', '~> 1.0'
26
+ gem.add_runtime_dependency 'find_a_port', '~> 1.0.1'
27
+ gem.add_runtime_dependency 'thor'
28
+
29
+ gem.add_development_dependency 'rake', '~> 10.0.3'
30
+ gem.add_development_dependency 'webmock', '~> 1.18.0'
31
+ gem.add_development_dependency 'pry'
32
+ gem.add_development_dependency 'fakefs', '~> 0.4'
33
+ gem.add_development_dependency 'hashie', '~> 2.0'
34
+ gem.add_development_dependency 'activesupport'
35
+ end
@@ -0,0 +1,25 @@
1
+ require 'spec_helper'
2
+ require 'support/shared_examples_for_request'
3
+ require 'pact/consumer/request'
4
+
5
+ module Pact
6
+ describe Consumer::Request::Actual do
7
+ it_behaves_like "a request"
8
+
9
+ let(:raw_request) do
10
+ {
11
+ 'method' => 'get',
12
+ 'path' => '/mallory'
13
+ }
14
+ end
15
+
16
+ describe "from_hash" do
17
+ context "when field are not defined" do
18
+ subject { described_class.from_hash(raw_request) }
19
+ it "raises an error" do
20
+ expect{subject}.to raise_error KeyError
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,58 @@
1
+ require 'spec_helper'
2
+ require 'pact/shared/active_support_support'
3
+
4
+ module Pact
5
+ describe ActiveSupportSupport do
6
+
7
+ include ActiveSupportSupport
8
+
9
+
10
+ describe "fix_regexp" do
11
+ let(:regexp) { /moose/ }
12
+
13
+ subject { fix_regexp regexp }
14
+
15
+ it "returns the original regexp" do
16
+ expect(subject).to be(regexp)
17
+ end
18
+
19
+ it "fixes the as_json method for Regexp that ActiveSupport tramples beneath its destructive hooves of destruction" do
20
+ expect(subject.to_json).to eq("{\"json_class\":\"Regexp\",\"o\":0,\"s\":\"moose\"}")
21
+ end
22
+ end
23
+
24
+ describe "fix_all_the_things" do
25
+ let(:hash) do
26
+ { 'body' => Pact::Term.new(matcher: /a*b/, generate: 'abba'), array: [/blah/], thing: /alligator/ }
27
+ end
28
+
29
+ subject { fix_all_the_things(hash) }
30
+
31
+ it "returns the original object" do
32
+ expect(subject).to be(hash)
33
+ end
34
+
35
+ it "finds all the Regexp objects in hashes or Pact class attributes and fixes the as_json method" do
36
+ json = subject.to_json
37
+ expect(json).to include("{\"json_class\":\"Regexp\",\"o\":0,\"s\":\"a*b\"}")
38
+ expect(json).to include("{\"json_class\":\"Regexp\",\"o\":0,\"s\":\"blah\"}")
39
+ expect(json).to include("{\"json_class\":\"Regexp\",\"o\":0,\"s\":\"alligator\"}")
40
+ end
41
+ end
42
+
43
+ describe "fix_json_formatting" do
44
+ let(:active_support_affected_pretty_generated_json) { "{\"json_class\":\"Regexp\",\"o\":0,\"s\":\"a*b\"}" }
45
+ let(:pretty_generated_json) do
46
+ '{
47
+ "json_class": "Regexp",
48
+ "o": 0,
49
+ "s": "a*b"
50
+ }'
51
+ end
52
+
53
+ it "pretty formats the json that has been not pretty formatted because of ActiveSupport" do
54
+ expect(fix_json_formatting(active_support_affected_pretty_generated_json)).to eq (pretty_generated_json.strip)
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,141 @@
1
+ require 'spec_helper'
2
+ require 'pact/consumer_contract'
3
+
4
+ module Pact
5
+ describe ConsumerContract do
6
+ describe "as_json" do
7
+
8
+ class MockInteraction
9
+ def as_json(options ={})
10
+ {:mock => "interaction"}
11
+ end
12
+ end
13
+
14
+ before do
15
+ allow(DateTime).to receive(:now).and_return(DateTime.strptime("2013-08-15T13:27:13+10:00"))
16
+ end
17
+
18
+ let(:service_consumer) { double('ServiceConsumer', :as_json => {:a => 'consumer'}) }
19
+ let(:service_provider) { double('ServiceProvider', :as_json => {:a => 'provider'}) }
20
+ let(:pact) { ConsumerContract.new({:interactions => [MockInteraction.new], :consumer => service_consumer, :provider => service_provider }) }
21
+ let(:expected_as_json) { {:provider=>{:a=>"provider"}, :consumer=>{:a=>"consumer"}, :interactions=>[{:mock=>"interaction"}], :metadata=>{:pactSpecificationVersion=> "1.0.0" }} }
22
+
23
+ it "should return a hash representation of the Pact" do
24
+ expect(pact.as_json).to eq expected_as_json
25
+ end
26
+
27
+ end
28
+
29
+ describe ".from_json" do
30
+ let(:loaded_pact) { ConsumerContract.from_json(string) }
31
+ context "when the top level object is a ConsumerContract" do
32
+ let(:string) { '{"interactions":[{"request": {"path":"/path", "method" : "get"}, "response": {"status" : 200}}], "consumer": {"name" : "Bob"} , "provider": {"name" : "Mary"} }' }
33
+
34
+ it "should create a Pact" do
35
+ expect(loaded_pact).to be_instance_of ConsumerContract
36
+ end
37
+
38
+ it "should have interactions" do
39
+ expect(loaded_pact.interactions).to be_instance_of Array
40
+ end
41
+
42
+ it "should have a consumer" do
43
+ expect(loaded_pact.consumer).to be_instance_of Pact::ServiceConsumer
44
+ end
45
+
46
+ it "should have a provider" do
47
+ expect(loaded_pact.provider).to be_instance_of Pact::ServiceProvider
48
+ end
49
+ end
50
+
51
+ context "with old 'producer' key" do
52
+ let(:string) { File.read('./spec/support/a_consumer-a_producer.json')}
53
+ it "should create a Pact" do
54
+ expect(loaded_pact).to be_instance_of ConsumerContract
55
+ end
56
+
57
+ it "should have interactions" do
58
+ expect(loaded_pact.interactions).to be_instance_of Array
59
+ end
60
+
61
+ it "should have a consumer" do
62
+ expect(loaded_pact.consumer).to be_instance_of Pact::ServiceConsumer
63
+ end
64
+
65
+ it "should have a provider" do
66
+ expect(loaded_pact.provider).to be_instance_of Pact::ServiceProvider
67
+ expect(loaded_pact.provider.name).to eq "an old producer"
68
+ end
69
+
70
+ it "should have a provider_state" do
71
+ expect(loaded_pact.interactions.first.provider_state).to eq 'state one'
72
+ end
73
+ end
74
+ end
75
+
76
+ describe "find_interactions" do
77
+ let(:consumer) { double('Pact::ServiceConsumer', :name => 'Consumer')}
78
+ let(:provider) { double('Pact::ServiceProvider', :name => 'Provider')}
79
+ let(:interaction) { double('Pact::Interaction') }
80
+ subject { ConsumerContract.new(:interactions => [interaction], :consumer => consumer, :provider => provider) }
81
+ let(:criteria) { {:description => /blah/} }
82
+ before do
83
+ expect(interaction).to receive(:matches_criteria?).with(criteria).and_return(matches)
84
+ end
85
+ context "by description" do
86
+ context "when no interactions are found" do
87
+ let(:matches) { false }
88
+ it "returns an empty array" do
89
+ expect(subject.find_interactions(criteria)).to eql []
90
+ end
91
+ end
92
+ context "when interactions are found" do
93
+ let(:matches) { true }
94
+ it "returns an array of the matching interactions" do
95
+ expect(subject.find_interactions(criteria)).to eql [interaction]
96
+ end
97
+ end
98
+ end
99
+ end
100
+
101
+ describe "find_interaction" do
102
+ let(:consumer) { double('Pact::ServiceConsumer', :name => 'Consumer')}
103
+ let(:provider) { double('Pact::ServiceProvider', :name => 'Provider')}
104
+ let(:interaction1) { double('Pact::Interaction') }
105
+ let(:interaction2) { double('Pact::Interaction') }
106
+ let(:criteria) { {:description => /blah/} }
107
+
108
+ before do
109
+ expect(interaction1).to receive(:matches_criteria?).with(criteria).and_return(matches1)
110
+ expect(interaction2).to receive(:matches_criteria?).with(criteria).and_return(matches2)
111
+ end
112
+
113
+ subject { ConsumerContract.new(:interactions => [interaction1, interaction2], :consumer => consumer, :provider => provider) }
114
+ context "by description" do
115
+ context "when a match is found" do
116
+ let(:matches1) { true }
117
+ let(:matches2) { false }
118
+
119
+ it "returns the interaction" do
120
+ expect(subject.find_interaction criteria).to eql interaction1
121
+ end
122
+ end
123
+ context "when more than one match is found" do
124
+ let(:matches1) { true }
125
+ let(:matches2) { true }
126
+ it "raises an error" do
127
+ expect{ subject.find_interaction(criteria) }.to raise_error "Found more than 1 interaction matching {:description=>/blah/} in pact file between Consumer and Provider."
128
+ end
129
+ end
130
+ context "when a match is not found" do
131
+ let(:matches1) { false }
132
+ let(:matches2) { false }
133
+ it "raises an error" do
134
+ expect{ subject.find_interaction(criteria) }.to raise_error "Could not find interaction matching {:description=>/blah/} in pact file between Consumer and Provider."
135
+ end
136
+ end
137
+ end
138
+ end
139
+
140
+ end
141
+ end