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,11 @@
1
+ require 'pact/matchers/difference_indicator'
2
+
3
+ module Pact
4
+ class UnexpectedIndex < Pact::DifferenceIndicator
5
+
6
+ def to_s
7
+ '<index not to exist>'
8
+ end
9
+
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ require 'pact/matchers/difference_indicator'
2
+
3
+ module Pact
4
+ class UnexpectedKey < Pact::DifferenceIndicator
5
+
6
+ def to_s
7
+ '<key not to exist>'
8
+ end
9
+
10
+ end
11
+ end
@@ -0,0 +1,114 @@
1
+ require 'pact/shared/jruby_support'
2
+ require 'pact/matchers/differ'
3
+
4
+ module Pact
5
+ module Matchers
6
+
7
+ class UnixDiffFormatter
8
+
9
+ include JRubySupport
10
+
11
+ def initialize diff, options = {}
12
+ @diff = diff
13
+ @colour = options.fetch(:colour, false)
14
+ @differ = Pact::Matchers::Differ.new(@colour)
15
+ end
16
+
17
+ def self.call diff, options = {colour: Pact.configuration.color_enabled}
18
+ new(diff, options).call
19
+ end
20
+
21
+ def call
22
+ to_s
23
+ end
24
+
25
+ def to_s
26
+ expected = generate_string(diff, :expected)
27
+ actual = generate_string(diff, :actual)
28
+ @differ.diff_as_string(actual, expected).lstrip + "\n" + key
29
+ end
30
+
31
+ private
32
+
33
+ def handle thing, target
34
+ case thing
35
+ when Hash then copy_hash(thing, target)
36
+ when Array then copy_array(thing, target)
37
+ when Difference then copy_diff(thing, target)
38
+ when TypeDifference then copy_diff(thing, target)
39
+ when RegexpDifference then copy_diff(thing, target)
40
+ when NoDiffIndicator then copy_no_diff(thing, target)
41
+ else copy_object(thing, target)
42
+ end
43
+ end
44
+
45
+ def generate_string diff, target
46
+ comparable = handle(diff, target)
47
+ begin
48
+ # Can't think of an elegant way to check if we can pretty generate other than to try it and maybe fail
49
+ fix_blank_lines_in_empty_hashes JSON.pretty_generate(comparable)
50
+ rescue JSON::GeneratorError
51
+ comparable.to_s
52
+ end
53
+ end
54
+
55
+ def copy_hash hash, target
56
+ hash.keys.each_with_object({}) do | key, new_hash |
57
+ value = handle hash[key], target
58
+ new_hash[key] = value unless (KeyNotFound === value || UnexpectedKey === value)
59
+ end
60
+ end
61
+
62
+ def copy_array array, target
63
+ array.each_index.each_with_object([]) do | index, new_array |
64
+ value = handle array[index], target
65
+ new_array[index] = value unless (UnexpectedIndex === value || IndexNotFound === value)
66
+ end
67
+ end
68
+
69
+ def copy_no_diff(thing, target)
70
+ thing
71
+ end
72
+
73
+ def copy_diff difference, target
74
+ if target == :actual
75
+ handle difference.actual, target
76
+ else
77
+ handle difference.expected, target
78
+ end
79
+ end
80
+
81
+ def copy_object object, target
82
+ if Regexp === object
83
+ RegexpDecorator.new(object)
84
+ else
85
+ object
86
+ end
87
+ end
88
+
89
+ def key
90
+ "Key: " + @differ.red("-") + @differ.red(" means \"expected, but was not found\". \n") +
91
+ @differ.green(" +") + @differ.green(" means \"actual, should not be found\". \n") +
92
+ " Values where the expected matches the actual are not shown.\n"
93
+ end
94
+
95
+ class RegexpDecorator
96
+
97
+ def initialize regexp
98
+ @regexp = regexp
99
+ end
100
+
101
+ def to_json options={}
102
+ @regexp.inspect
103
+ end
104
+
105
+ def as_json
106
+ @regexp.inspect
107
+ end
108
+ end
109
+
110
+ attr_reader :diff
111
+ end
112
+
113
+ end
114
+ end
@@ -0,0 +1,28 @@
1
+ require 'randexp'
2
+
3
+ module Pact
4
+ module Reification
5
+
6
+ def self.from_term(term)
7
+ case term
8
+ when Pact::Term, Regexp, Pact::SomethingLike
9
+ term.generate
10
+ when Hash
11
+ term.inject({}) do |mem, (key,term)|
12
+ mem[key] = from_term(term)
13
+ mem
14
+ end
15
+ when Array
16
+ term.inject([]) do |mem, term|
17
+ mem << from_term(term)
18
+ mem
19
+ end
20
+ when Pact::Request::Base
21
+ from_term(term.to_hash)
22
+ else
23
+ term
24
+ end
25
+ end
26
+
27
+ end
28
+ end
@@ -0,0 +1,53 @@
1
+ require 'rspec'
2
+ # This is horrible, must work out a better way of doing this
3
+ module Pact
4
+ module RSpec
5
+
6
+ def self.color_enabled?
7
+ if ::RSpec.configuration.respond_to?(:color_enabled?)
8
+ ::RSpec.configuration.color_enabled?(::RSpec.configuration.output_stream)
9
+ else
10
+ ::RSpec.configuration.color_enabled?
11
+ end
12
+ end
13
+
14
+ def self.formatter_class
15
+ if ::RSpec::Core::Formatters.respond_to?(:register)
16
+ require 'pact/provider/rspec/formatter_rspec_3'
17
+ Pact::Provider::RSpec::Formatter
18
+ else
19
+ require 'pact/provider/rspec/formatter_rspec_2'
20
+ Pact::Provider::RSpec::Formatter2
21
+ end
22
+
23
+ end
24
+
25
+ def self.full_description example
26
+ example.respond_to?(:full_description) ? example.full_description : example.example.full_description
27
+ end
28
+
29
+ def self.runner_defined?
30
+ defined?(::RSpec::Core::Runner)
31
+ end
32
+
33
+ def self.is_rspec_3
34
+ ::RSpec::Core::Formatters.respond_to?(:register)
35
+ end
36
+
37
+ def self.is_rspec_2
38
+ !is_rspec_3
39
+ end
40
+
41
+ def self.with_rspec_3
42
+ if is_rspec_3
43
+ yield
44
+ end
45
+ end
46
+
47
+ def self.with_rspec_2
48
+ if is_rspec_2
49
+ yield
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,51 @@
1
+ module Pact
2
+ module ActiveSupportSupport
3
+
4
+ extend self
5
+
6
+ def fix_all_the_things thing
7
+ if thing.is_a?(Regexp)
8
+ fix_regexp(thing)
9
+ elsif thing.is_a?(Array)
10
+ thing.each{ | it | fix_all_the_things it }
11
+ elsif thing.is_a?(Hash)
12
+ thing.values.each{ | it | fix_all_the_things it }
13
+ elsif thing.class.name.start_with?("Pact")
14
+ thing.instance_variables.collect{ | iv_name | thing.instance_variable_get(iv_name)}.each do | iv |
15
+ fix_all_the_things iv
16
+ end
17
+ end
18
+ thing
19
+ end
20
+
21
+ # ActiveSupport JSON overwrites (i.e. TRAMPLES) the json methods of the Regexp class directly
22
+ # (beneath its destructive hooves of destruction).
23
+ # This does not seem to be able to be undone without affecting the JSON serialisation in the
24
+ # calling project, so the best way I've found to fix this issue is to reattach the
25
+ # original as_json to the Regexp instances in the ConsumerContract before we write them to the
26
+ # pact file. If anyone can find a better way, please submit a pull request ASAP!
27
+ def fix_regexp regexp
28
+ def regexp.as_json options = {}
29
+ {:json_class => 'Regexp', "o" => self.options, "s" => self.source }
30
+ end
31
+ regexp
32
+ end
33
+
34
+ # Having Active Support JSON loaded somehow kills the formatting of pretty_generate for objects.
35
+ # Don't ask me why, but it still seems to work for hashes, so the hacky work around is to
36
+ # reparse the generated JSON into a hash and pretty_generate that... sigh...
37
+ # Oh ActiveSupport, why....
38
+ def fix_json_formatting json
39
+ if json.include?("\n")
40
+ json
41
+ else
42
+ JSON.pretty_generate(JSON.parse(json, create_additions: false))
43
+ end
44
+ end
45
+
46
+ def remove_unicode json
47
+ json.gsub(/\\u([0-9A-Za-z]{4})/) {|s| [$1.to_i(16)].pack("U")}
48
+ end
49
+
50
+ end
51
+ end
@@ -0,0 +1,76 @@
1
+ module Pact
2
+
3
+ class DslDelegator
4
+
5
+ def initialize delegation_target
6
+ @delegation_target = delegation_target
7
+ end
8
+
9
+ def instance_eval_with_previous_context_available(*args, &block)
10
+ with_previous_context_available(block.binding) do
11
+ bind_block_as_instance_method_on_self(&block).call(*args)
12
+ end
13
+ end
14
+
15
+ protected
16
+
17
+ def method_missing(method, *args, &block)
18
+ if delegation_target_responds_to? method
19
+ delegation_target.send(method, *args, &block)
20
+ else
21
+ previous_context.send(method, *args, &block)
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ attr_accessor :delegation_target, :previous_context
28
+
29
+ def bind_block_as_instance_method_on_self(&block)
30
+ create_instance_method_from_block(&block).bind(self)
31
+ end
32
+
33
+
34
+ def create_instance_method_from_block &block
35
+ meth = self.class.class_eval do
36
+ define_method :block_as_instance_method_, &block
37
+ meth = instance_method :block_as_instance_method_
38
+ remove_method :block_as_instance_method_
39
+ meth
40
+ end
41
+ end
42
+
43
+ def with_previous_context_available(binding, &block)
44
+ @previous_context = binding.eval('self')
45
+ result = block.call
46
+ @previous_context = nil
47
+ result
48
+ end
49
+
50
+ def delegation_target_responds_to?(method)
51
+ delegation_target.respond_to? method
52
+ end
53
+
54
+ end
55
+
56
+
57
+ # Ripped from http://blog.joecorcoran.co.uk/2013/09/04/simple-pattern-ruby-dsl
58
+ # and then fixed up by using http://www.skorks.com/2013/03/a-closure-is-not-always-a-closure-in-ruby/
59
+ # to access variables and methods defined in the calling scope.
60
+ module DSL
61
+ def build(*args, &block)
62
+ new_instance_of_delegation_target_class = self.new(*args)
63
+ dsl_delegator_class = self.const_get('DSL_DELEGATOR_CLASS')
64
+ dsl_delegator = dsl_delegator_class.new(new_instance_of_delegation_target_class)
65
+ dsl_delegator.instance_eval_with_previous_context_available(&block)
66
+ new_instance_of_delegation_target_class.finalize
67
+ new_instance_of_delegation_target_class
68
+ end
69
+
70
+ def dsl(&block)
71
+ dsl_delegator_class = Class.new(DslDelegator, &block)
72
+ self.const_set('DSL_DELEGATOR_CLASS', dsl_delegator_class)
73
+ end
74
+
75
+ end
76
+ end
@@ -0,0 +1,18 @@
1
+ module Pact
2
+ module JRubySupport
3
+
4
+ # Under jruby, JSON.pretty_generate inserts a blank new line between the opening and closing
5
+ # brackets of an empty hash, like so:
6
+ # {
7
+ # "empty": {
8
+ #
9
+ # }
10
+ # }
11
+ # This screws up the UnixDiffFormatter, so we need to remove the blank lines.
12
+
13
+ def fix_blank_lines_in_empty_hashes json
14
+ json.gsub(/({\n)\n(\s*})/,'\1\2')
15
+ end
16
+
17
+ end
18
+ end
@@ -0,0 +1,15 @@
1
+ require 'pact/matchers/matchers'
2
+
3
+ module Pact
4
+ class JsonDiffer
5
+
6
+ extend Matchers
7
+
8
+
9
+ def self.call expected, actual, options = {}
10
+ diff expected, actual, options
11
+ end
12
+
13
+
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ require 'pact/matchers/difference_indicator'
2
+
3
+ module Pact
4
+ class KeyNotFound < Pact::DifferenceIndicator
5
+
6
+ def to_s
7
+ "<key not found>"
8
+ end
9
+
10
+ def empty?
11
+ true
12
+ end
13
+ end
14
+
15
+ end
@@ -0,0 +1,31 @@
1
+ module Pact
2
+ class NullExpectation
3
+ def to_s
4
+ "<No expectation>"
5
+ end
6
+
7
+ def ==(other_object)
8
+ other_object.is_a? NullExpectation
9
+ end
10
+
11
+ def ===(other_object)
12
+ other_object.is_a? NullExpectation
13
+ end
14
+
15
+ def eql?(other_object)
16
+ self == other_object
17
+ end
18
+
19
+ def hash
20
+ 2934820948209428748274238642672
21
+ end
22
+
23
+ def empty?
24
+ true
25
+ end
26
+
27
+ def nil?
28
+ true
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,97 @@
1
+ require 'pact/matchers'
2
+ require 'pact/symbolize_keys'
3
+ require 'pact/consumer_contract/headers'
4
+
5
+ module Pact
6
+
7
+ module Request
8
+
9
+ class Base
10
+ include Pact::Matchers
11
+ include Pact::SymbolizeKeys
12
+ extend Pact::Matchers
13
+
14
+ attr_reader :method, :path, :headers, :body, :query, :options
15
+
16
+ def initialize(method, path, headers, body, query)
17
+ @method = method.to_s
18
+ @path = path.chomp('/')
19
+ @headers = Hash === headers ? Headers.new(headers) : headers # Could be a NullExpectation - TODO make this more elegant
20
+ @body = body
21
+ @query = query
22
+ end
23
+
24
+ def to_json(options = {})
25
+ as_json.to_json(options)
26
+ end
27
+
28
+ def as_json options = {}
29
+ to_hash
30
+ end
31
+
32
+ def to_hash
33
+ hash = {
34
+ method: method,
35
+ path: path,
36
+ }
37
+
38
+ hash.merge!(query: query) if specified?(:query)
39
+ hash.merge!(headers: headers) if specified?(:headers)
40
+ hash.merge!(body: body) if specified?(:body)
41
+ hash
42
+ end
43
+
44
+ def method_and_path
45
+ "#{method.upcase} #{full_path}"
46
+ end
47
+
48
+ def full_path
49
+ display_path + display_query
50
+ end
51
+
52
+ def content_type
53
+ return nil if headers.is_a? self.class.key_not_found.class
54
+ headers['Content-Type']
55
+ end
56
+
57
+ def modifies_resource?
58
+ http_method_modifies_resource? && body_specified?
59
+ end
60
+
61
+ protected
62
+
63
+ # Not including DELETE, as we don't care about the resources updated state.
64
+ def http_method_modifies_resource?
65
+ ['PUT','POST','PATCH'].include?(method.to_s.upcase)
66
+ end
67
+
68
+ def self.key_not_found
69
+ raise NotImplementedError
70
+ end
71
+
72
+ def body_specified?
73
+ specified?(:body)
74
+ end
75
+
76
+ def specified? key
77
+ !(self.send(key).is_a? self.class.key_not_found.class)
78
+ end
79
+
80
+ def to_hash_without_body
81
+ keep_keys = [:method, :path, :headers, :query]
82
+ as_json.reject{ |key, value| !keep_keys.include? key }.tap do | hash |
83
+ hash[:method] = method.upcase
84
+ end
85
+ end
86
+
87
+ def display_path
88
+ path.empty? ? "/" : path
89
+ end
90
+
91
+ def display_query
92
+ (query.nil? || query.empty?) ? '' : "?#{Pact::Reification.from_term(query)}"
93
+ end
94
+
95
+ end
96
+ end
97
+ end