pact-support 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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