pact 1.0.26 → 1.0.27
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.
- data/CHANGELOG.md +14 -0
- data/Gemfile.lock +1 -1
- data/README.md +5 -3
- data/lib/pact/consumer/mock_service/interaction_mismatch.rb +16 -3
- data/lib/pact/consumer/mock_service/interaction_replay.rb +3 -8
- data/lib/pact/consumer_contract/interaction.rb +4 -0
- data/lib/pact/matchers/diff_decorator.rb +84 -0
- data/lib/pact/matchers/matchers.rb +50 -7
- data/lib/pact/provider.rb +1 -0
- data/lib/pact/provider/pact_spec_runner.rb +2 -0
- data/lib/pact/provider/print_missing_provider_states.rb +30 -0
- data/lib/pact/provider/provider_state_proxy.rb +35 -0
- data/lib/pact/provider/rspec.rb +2 -2
- data/lib/pact/provider/test_methods.rb +3 -18
- data/lib/pact/provider/world.rb +19 -0
- data/lib/pact/templates/provider_state.erb +15 -0
- data/lib/pact/version.rb +1 -1
- data/{scratchpad.txt → scratchpad.rb} +17 -1
- data/spec/integration/consumer_spec.rb +2 -2
- data/spec/lib/pact/matchers/diff_decorator_spec.rb +80 -0
- data/spec/lib/pact/matchers/matchers_spec.rb +313 -308
- data/spec/lib/pact/provider/print_missing_provider_states_spec.rb +19 -0
- data/spec/lib/pact/provider/provider_state_proxy_spec.rb +60 -0
- data/spec/lib/pact/provider/world_spec.rb +33 -0
- data/spec/support/missing_provider_states_output.txt +25 -0
- metadata +20 -7
- data/spec/lib/pact/provider/test_methods_spec.rb +0 -20
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,20 @@ Do this to generate your change history
|
|
|
2
2
|
|
|
3
3
|
git log --date=relative --pretty=format:' * %h - %s (%an, %ad)'
|
|
4
4
|
|
|
5
|
+
### 1.0.26 (10 December 2013)
|
|
6
|
+
|
|
7
|
+
* 388fc7b - Changing provider set up and tear down to run before :all rather than before :each (Beth, 13 minutes ago)
|
|
8
|
+
* 06b5626 - Updating TODO list in the README. (Beth, 25 hours ago)
|
|
9
|
+
* 823f306 - Update README.md (bethesque, 32 hours ago)
|
|
10
|
+
* 7d96017 - Improving layout of text diff message (Beth Skurrie, 2 days ago)
|
|
11
|
+
* 9c88c3a - Working on a new way to display the diff between an expected and actual request/response (Beth Skurrie, 2 days ago)
|
|
12
|
+
* ff2c448 - Added a Difference class instead of a hash with :expected and :actual (Beth Skurrie, 2 days ago)
|
|
13
|
+
* b34457c - Moved all missing provider state templates into the one message at the end of the test so it's easier to digest and can be copied directly into a file. (Beth Skurrie, 2
|
|
14
|
+
* 1729887 - Moving ProviderStateProxy on to Pact World (Beth Skurrie, 3 days ago)
|
|
15
|
+
* c53cb4d - Starting to add Pact::World (Beth, 4 days ago)
|
|
16
|
+
* f7af9e2 - Recording missing provider states (Beth, 4 days ago)
|
|
17
|
+
* 4caa171 - Starting work on ProviderStateProxy - intent is for it to record missing and unused states to report at the end of the pact:verify (Beth, 4 days ago)
|
|
18
|
+
|
|
5
19
|
### 1.0.26 (5 December 2013)
|
|
6
20
|
|
|
7
21
|
* e4be654 - BEST COMMIT TO PACT EVER since the introduction of pact:verify. Got rid of the horrific backtraces. (Beth, 5 hours ago)
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
|
@@ -161,10 +161,8 @@ require 'pact/provider/rspec'
|
|
|
161
161
|
require './spec_helper'
|
|
162
162
|
|
|
163
163
|
Pact.service_provider "My Service Provider" do
|
|
164
|
-
# If you have a config.ru file, the app will be loaded from it automatically and you can skip the app config.
|
|
165
|
-
# Otherwise, set your rack app here as you would when using Rack::Test::Methods
|
|
166
164
|
|
|
167
|
-
app { MyApp.new }
|
|
165
|
+
app { MyApp.new } # Optional, loads app from config.ru by default
|
|
168
166
|
|
|
169
167
|
honours_pact_with 'My Service Consumer' do
|
|
170
168
|
|
|
@@ -391,8 +389,12 @@ Short term:
|
|
|
391
389
|
|
|
392
390
|
Long term:
|
|
393
391
|
- Provide more flexible matching (eg the keys should match, and the classes of the values should match, but the values of each key do not need to be equal). This is to make the pact verification less brittle.
|
|
392
|
+
- Add support for verifying pact against running server
|
|
393
|
+
- Add XML support
|
|
394
|
+
- Improve display of interaction diffs
|
|
394
395
|
- Decouple Rspec from Pact and make rspec-pact gem for easy integration
|
|
395
396
|
|
|
397
|
+
|
|
396
398
|
## Contributing
|
|
397
399
|
|
|
398
400
|
1. Fork it
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
require 'pact/matchers/diff_decorator'
|
|
2
|
+
|
|
1
3
|
module Pact
|
|
2
4
|
module Consumer
|
|
3
5
|
class InteractionMismatch
|
|
@@ -12,8 +14,12 @@ module Pact
|
|
|
12
14
|
@candiate_diffs = candidate_interactions.collect{ | candidate_interaction| CandidateDiff.new(candidate_interaction, actual_request)}
|
|
13
15
|
end
|
|
14
16
|
|
|
15
|
-
def
|
|
16
|
-
candiate_diffs.collect(&:
|
|
17
|
+
def to_hash
|
|
18
|
+
candiate_diffs.collect(&:to_hash)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def to_s
|
|
22
|
+
candiate_diffs.collect(&:to_s).join("\n")
|
|
17
23
|
end
|
|
18
24
|
|
|
19
25
|
def short_summary
|
|
@@ -36,12 +42,19 @@ module Pact
|
|
|
36
42
|
diff.keys
|
|
37
43
|
end
|
|
38
44
|
|
|
39
|
-
def
|
|
45
|
+
def to_hash
|
|
40
46
|
summary = {:description => candidate_interaction.description}
|
|
41
47
|
summary[:provider_state] = candidate_interaction.provider_state if candidate_interaction.provider_state
|
|
42
48
|
summary.merge(diff)
|
|
43
49
|
end
|
|
44
50
|
|
|
51
|
+
def to_s
|
|
52
|
+
[
|
|
53
|
+
"Diff with interaction: #{candidate_interaction.description_with_provider_state_quoted}",
|
|
54
|
+
Pact::Matchers::DiffDecorator.new(diff).to_s
|
|
55
|
+
].join("\n")
|
|
56
|
+
end
|
|
57
|
+
|
|
45
58
|
def diff
|
|
46
59
|
@diff ||= candidate_interaction.request.difference(actual_request)
|
|
47
60
|
end
|
|
@@ -75,12 +75,6 @@ module Pact
|
|
|
75
75
|
InteractionMismatch.new(candidate_interactions, actual_request)
|
|
76
76
|
end
|
|
77
77
|
|
|
78
|
-
def diff_summary_for interaction, diff
|
|
79
|
-
summary = {:description => interaction.description}
|
|
80
|
-
summary[:provider_state] = interaction.provider_state if interaction.provider_state
|
|
81
|
-
summary.merge(diff)
|
|
82
|
-
end
|
|
83
|
-
|
|
84
78
|
def request_summary_for interaction
|
|
85
79
|
summary = {:description => interaction.description}
|
|
86
80
|
summary[:provider_state] if interaction.provider_state
|
|
@@ -91,7 +85,7 @@ module Pact
|
|
|
91
85
|
def unrecognised_request_response interaction_mismatch
|
|
92
86
|
response = {
|
|
93
87
|
message: "No interaction found for #{interaction_mismatch.actual_request.method_and_path}",
|
|
94
|
-
interaction_diffs: interaction_mismatch.
|
|
88
|
+
interaction_diffs: interaction_mismatch.to_hash
|
|
95
89
|
}
|
|
96
90
|
[500, {'Content-Type' => 'application/json'}, [response.to_json]]
|
|
97
91
|
end
|
|
@@ -99,7 +93,8 @@ module Pact
|
|
|
99
93
|
def log_unrecognised_request_and_interaction_diff interaction_mismatch
|
|
100
94
|
logger.error "No interaction found on #{name} for #{interaction_mismatch.actual_request.method_and_path}"
|
|
101
95
|
logger.error 'Interaction diffs for that route:'
|
|
102
|
-
logger.ap(interaction_mismatch.
|
|
96
|
+
logger.ap(interaction_mismatch.to_hash, :error)
|
|
97
|
+
logger.error("Interaction diffs for that route in text format:\n#{interaction_mismatch.to_s}")
|
|
103
98
|
end
|
|
104
99
|
|
|
105
100
|
def handle_unrecognised_request actual_request, candidate_interactions
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
module Pact
|
|
2
|
+
module Matchers
|
|
3
|
+
class DiffDecorator
|
|
4
|
+
|
|
5
|
+
attr_reader :diff
|
|
6
|
+
|
|
7
|
+
def initialize diff
|
|
8
|
+
@diff = diff
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def to_hash
|
|
12
|
+
diff
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def to_s
|
|
16
|
+
diff_descriptions(diff).join("\n")
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def diff_descriptions obj, path = [], descriptions = []
|
|
20
|
+
case obj
|
|
21
|
+
when Hash then handle_hash obj, path, descriptions
|
|
22
|
+
when Array then handle_array obj, path, descriptions
|
|
23
|
+
when Difference then handle_difference obj, path, descriptions
|
|
24
|
+
when NoDiffIndicator then nil
|
|
25
|
+
else
|
|
26
|
+
raise "Invalid diff, expected Hash, Array, NoDiffIndicator or Difference, found #{obj}"
|
|
27
|
+
end
|
|
28
|
+
descriptions
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def handle_hash hash, path, descriptions
|
|
32
|
+
hash.each_pair do | key, value |
|
|
33
|
+
diff_descriptions value, path + [key.inspect], descriptions
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def handle_array array, path, descriptions
|
|
38
|
+
array.each_with_index do | obj, index |
|
|
39
|
+
diff_descriptions obj, path + [index], descriptions
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def handle_difference difference, path, descriptions
|
|
44
|
+
|
|
45
|
+
case difference.actual
|
|
46
|
+
when Pact::KeyNotFound then handle_key_not_found(difference, path, descriptions)
|
|
47
|
+
when Pact::IndexNotFound then handle_index_not_found(difference, path, descriptions)
|
|
48
|
+
else
|
|
49
|
+
case difference.expected
|
|
50
|
+
when Pact::UnexpectedKey then handle_unexpected_key(difference, path, descriptions)
|
|
51
|
+
when Pact::UnexpectedIndex then handle_unexpected_index(difference, path, descriptions)
|
|
52
|
+
else
|
|
53
|
+
handle_mismatched_value(difference, path, descriptions)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def handle_unexpected_index difference, path, descriptions
|
|
59
|
+
descriptions << "\tAt:\n\t\t#{path_to_s(path)}\n\tArray contained unexpected item:\n\t\t#{difference.actual.ai}"
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def handle_mismatched_value difference, path, descriptions
|
|
63
|
+
descriptions << "\tAt:\n\t\t#{path_to_s(path)}\n\tExpected:\n\t\t#{difference.expected.ai}\n\tActual:\n\t\t#{difference.actual.ai}"
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def handle_index_not_found difference, path, descriptions
|
|
67
|
+
descriptions << "\tAt:\n\t\t#{path_to_s(path)}\n\tMissing index with value:\n\t\t#{difference.expected.ai}"
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def handle_key_not_found difference, path, descriptions
|
|
71
|
+
descriptions << "\tAt:\n\t\t#{path_to_s(path)}\n\tMissing key with value:\n\t\t#{difference.expected.ai}"
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def handle_unexpected_key difference, path, descriptions
|
|
75
|
+
descriptions << "\tAt:\n\t\t#{path_to_s(path)}\n\tHash contained unexpected key with value:\n\t\t#{difference.actual.ai}"
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def path_to_s path
|
|
79
|
+
"[" + path.join("][") + "]"
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
@@ -10,7 +10,50 @@ require 'pact/matchers/index_not_found'
|
|
|
10
10
|
module Pact
|
|
11
11
|
module Matchers
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
class Difference
|
|
14
|
+
attr_reader :expected, :actual
|
|
15
|
+
def initialize expected, actual
|
|
16
|
+
@expected = expected
|
|
17
|
+
@actual = actual
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def any?
|
|
21
|
+
true
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def to_hash
|
|
25
|
+
{:EXPECTED => expected, :ACTUAL => actual}
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def to_json options = {}
|
|
29
|
+
to_hash.to_json(options)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def to_s
|
|
33
|
+
to_hash.to_s
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def == other
|
|
37
|
+
other.is_a?(Difference) && other.expected == expected && other.actual == actual
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
class NoDiffIndicator
|
|
42
|
+
|
|
43
|
+
def to_json
|
|
44
|
+
to_s
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def to_s
|
|
48
|
+
'no difference here!'
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def == other
|
|
52
|
+
other.is_a? NoDiffIndicator
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
NO_DIFF_INDICATOR = NoDiffIndicator.new
|
|
14
57
|
#UnexpectedKey.new = '<key not to be present>'
|
|
15
58
|
DEFAULT_OPTIONS = {allow_unexpected_keys: true, structure: false}.freeze
|
|
16
59
|
|
|
@@ -34,7 +77,7 @@ module Pact
|
|
|
34
77
|
if actual.is_a?(String) && regexp.match(actual)
|
|
35
78
|
{}
|
|
36
79
|
else
|
|
37
|
-
|
|
80
|
+
Difference.new regexp, actual
|
|
38
81
|
end
|
|
39
82
|
end
|
|
40
83
|
|
|
@@ -42,7 +85,7 @@ module Pact
|
|
|
42
85
|
if actual.is_a? Array
|
|
43
86
|
actual_array_diff expected, actual, options
|
|
44
87
|
else
|
|
45
|
-
|
|
88
|
+
Difference.new expected, actual
|
|
46
89
|
end
|
|
47
90
|
end
|
|
48
91
|
|
|
@@ -78,7 +121,7 @@ module Pact
|
|
|
78
121
|
{}
|
|
79
122
|
else
|
|
80
123
|
(actual.keys - expected.keys).inject({}) do | diff, key |
|
|
81
|
-
diff[key] =
|
|
124
|
+
diff[key] = Difference.new(UnexpectedKey.new, actual[key])
|
|
82
125
|
diff
|
|
83
126
|
end
|
|
84
127
|
end
|
|
@@ -88,7 +131,7 @@ module Pact
|
|
|
88
131
|
if actual.is_a? Hash
|
|
89
132
|
actual_hash_diff expected, actual, options
|
|
90
133
|
else
|
|
91
|
-
|
|
134
|
+
Difference.new expected, actual
|
|
92
135
|
end
|
|
93
136
|
end
|
|
94
137
|
|
|
@@ -96,7 +139,7 @@ module Pact
|
|
|
96
139
|
if classes_match? expected, actual
|
|
97
140
|
{}
|
|
98
141
|
else
|
|
99
|
-
|
|
142
|
+
Difference.new structure_diff_expected_display(expected), structure_diff_actual_display(actual)
|
|
100
143
|
end
|
|
101
144
|
end
|
|
102
145
|
|
|
@@ -118,7 +161,7 @@ module Pact
|
|
|
118
161
|
def object_diff expected, actual, options
|
|
119
162
|
return class_diff(expected, actual) if options[:structure]
|
|
120
163
|
if expected != actual
|
|
121
|
-
|
|
164
|
+
Difference.new expected, actual
|
|
122
165
|
else
|
|
123
166
|
{}
|
|
124
167
|
end
|
data/lib/pact/provider.rb
CHANGED
|
@@ -4,6 +4,7 @@ require 'rspec/core'
|
|
|
4
4
|
require 'rspec/core/formatters/documentation_formatter'
|
|
5
5
|
require 'rspec/core/formatters/json_formatter'
|
|
6
6
|
require 'pact/provider/pact_helper_locator'
|
|
7
|
+
require 'pact/provider/print_missing_provider_states'
|
|
7
8
|
require_relative 'rspec'
|
|
8
9
|
|
|
9
10
|
|
|
@@ -101,6 +102,7 @@ module Pact
|
|
|
101
102
|
config.run_hook(:after, :suite)
|
|
102
103
|
end
|
|
103
104
|
end
|
|
105
|
+
PrintMissingProviderStates.call Pact.world.provider_states.missing_provider_states
|
|
104
106
|
@output = @json_formatter.output_hash
|
|
105
107
|
exit_code
|
|
106
108
|
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
module Pact
|
|
2
|
+
module Provider
|
|
3
|
+
class PrintMissingProviderStates
|
|
4
|
+
|
|
5
|
+
# Hash of consumer names to array of names of missing provider states
|
|
6
|
+
def self.call missing_provider_states
|
|
7
|
+
if missing_provider_states.any?
|
|
8
|
+
puts orangeify(text(missing_provider_states))
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def self.orangeify string
|
|
13
|
+
"\e[33m#{string}\e[m"
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def self.text missing_provider_states
|
|
17
|
+
create_provider_states_for(missing_provider_states)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def self.create_provider_states_for consumers
|
|
21
|
+
ERB.new(template_string).result(binding)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def self.template_string
|
|
25
|
+
File.read(File.expand_path( '../../templates/provider_state.erb', __FILE__))
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
module Pact
|
|
2
|
+
module Provider
|
|
3
|
+
class ProviderStateProxy
|
|
4
|
+
|
|
5
|
+
attr_reader :missing_provider_states
|
|
6
|
+
|
|
7
|
+
def initialize
|
|
8
|
+
@missing_provider_states = {}
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def get name, options = {}
|
|
12
|
+
unless provider_state = ProviderState.get(name, options)
|
|
13
|
+
register_missing_provider_state name, options[:for]
|
|
14
|
+
raise error_message name, options[:for]
|
|
15
|
+
end
|
|
16
|
+
provider_state
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
def error_message name, consumer
|
|
22
|
+
"Could not find provider state \"#{name}\" for consumer #{consumer}"
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def register_missing_provider_state name, consumer
|
|
26
|
+
missing_states_for(consumer) << name unless missing_states_for(consumer).include?(name)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def missing_states_for consumer
|
|
30
|
+
@missing_provider_states[consumer] ||= []
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
data/lib/pact/provider/rspec.rb
CHANGED
|
@@ -62,12 +62,12 @@ module Pact
|
|
|
62
62
|
|
|
63
63
|
describe description_for(interaction), :pact => :verify do
|
|
64
64
|
|
|
65
|
-
before do
|
|
65
|
+
before :all do
|
|
66
66
|
set_up_provider_state interaction.provider_state, options[:consumer]
|
|
67
67
|
replay_interaction interaction
|
|
68
68
|
end
|
|
69
69
|
|
|
70
|
-
after do
|
|
70
|
+
after :all do
|
|
71
71
|
tear_down_provider_state interaction.provider_state, options[:consumer]
|
|
72
72
|
end
|
|
73
73
|
|
|
@@ -2,7 +2,9 @@ require 'pact/logging'
|
|
|
2
2
|
require 'rack/test'
|
|
3
3
|
require 'pact/consumer_contract/interaction'
|
|
4
4
|
require 'pact/provider/provider_state'
|
|
5
|
+
require 'pact/provider/provider_state_proxy'
|
|
5
6
|
require 'pact/provider/request'
|
|
7
|
+
require 'pact/provider/world'
|
|
6
8
|
|
|
7
9
|
module Pact
|
|
8
10
|
module Provider
|
|
@@ -44,24 +46,7 @@ module Pact
|
|
|
44
46
|
end
|
|
45
47
|
|
|
46
48
|
def get_provider_state provider_state_name, consumer
|
|
47
|
-
|
|
48
|
-
extra = consumer ? " for consumer \"#{consumer}\"" : ""
|
|
49
|
-
error_msg = <<-eos
|
|
50
|
-
Could not find a provider state named \"#{provider_state_name}\"#{extra}.
|
|
51
|
-
Have you required the provider states file for this consumer in your pact_helper.rb?
|
|
52
|
-
If you have not yet defined a provider state for \"#{provider_state_name}\", here is a template:
|
|
53
|
-
|
|
54
|
-
Pact.provider_states_for \"#{consumer}\" do
|
|
55
|
-
provider_state \"#{provider_state_name}\" do
|
|
56
|
-
set_up do
|
|
57
|
-
# Your set up code goes here
|
|
58
|
-
end
|
|
59
|
-
end
|
|
60
|
-
end
|
|
61
|
-
eos
|
|
62
|
-
raise error_msg
|
|
63
|
-
end
|
|
64
|
-
provider_state
|
|
49
|
+
Pact.world.provider_states.get(provider_state_name, :for => consumer)
|
|
65
50
|
end
|
|
66
51
|
end
|
|
67
52
|
end
|