Skip to content

Commit 1d4d92c

Browse files
committed
feat: verify that each message has been yielded
1 parent 19e9549 commit 1d4d92c

File tree

11 files changed

+219
-49
lines changed

11 files changed

+219
-49
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,4 @@ spec/pacts/zoo_consumer-zoo_provider.json
1212

1313
# rspec failure tracking
1414
.rspec_status
15+
spec/pacts/

lib/pact/consumer_contract/message/contents.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@ def reified_contents_string
3131
end
3232
end
3333

34+
def reified_contents_hash
35+
Pact::Reification.from_term(contents)
36+
end
37+
3438
def contents
3539
@contents
3640
end

lib/pact/message/cli.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@ class CLI < Thor
1212
desc 'update MESSAGE_JSON', 'Update a pact with the given message, or create the pact if it does not exist. The MESSAGE_JSON may be in the legacy Ruby JSON format or the v2+ format.'
1313
def update(message)
1414
require 'pact/message'
15-
require 'pact/message/consumer/update_pact'
15+
require 'pact/message/consumer/write_pact'
1616
pact_specification_version = Pact::SpecificationVersion.new(options.pact_specification_version)
1717
message = Pact::Message.from_hash(JSON.load(message), { pact_specification_version: pact_specification_version })
18-
Pact::Message::Consumer::UpdatePact.call(message, options.pact_dir, options.consumer, options.provider, options.pact_specification_version)
18+
Pact::Message::Consumer::WritePact.call(message, options.pact_dir, options.consumer, options.provider, options.pact_specification_version, :update)
1919
end
2020

2121
desc 'reify', "Take a JSON document with embedded pact matchers and return a concrete example"

lib/pact/message/consumer/consumer_contract_builder.rb

Lines changed: 46 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
require 'pact/message/consumer/interaction_builder'
2-
require 'pact/message/consumer/update_pact'
2+
require 'pact/message/consumer/write_pact'
3+
require 'pact/errors'
34

45
module Pact
56
module Message
@@ -11,44 +12,77 @@ def initialize(attributes)
1112
@consumer_name = attributes[:consumer_name]
1213
@provider_name = attributes[:provider_name]
1314
@interactions = []
15+
@yielded_interaction = false
1416
end
1517

16-
def given(provider_state)
17-
interaction_builder.given(provider_state)
18+
def reset
19+
@interaction_builder = nil
20+
@yielded_interaction = false
21+
end
22+
23+
def given(provider_state, params = {})
24+
interaction_builder.given(provider_state, params)
1825
end
1926

2027
def is_expected_to_send(description)
2128
interaction_builder.is_expected_to_send(provider_state)
2229
end
2330

2431
def send_message_string
25-
if block_given?
26-
yield contents.reified_contents_string
32+
if interaction_builder?
33+
if block_given?
34+
@yielded_interaction = true
35+
yield interaction_builder.interaction.contents.reified_contents_string
36+
end
37+
else
38+
raise Pact::Error.new("No message expectation has been defined")
39+
end
40+
end
41+
42+
def send_message_hash
43+
if interaction_builder?
44+
if block_given?
45+
@yielded_interaction = true
46+
yield interaction_builder.interaction.contents.reified_contents_hash
47+
end
48+
else
49+
raise Pact::Error.new("No message expectation has been defined")
2750
end
2851
end
2952

3053
def handle_interaction_fully_defined(interaction)
3154
@contents = interaction.contents
3255
@contents_string = interaction.contents.to_s
33-
@interactions << interaction
34-
@interaction_builder = nil
35-
3656
end
3757

3858
def verify example_description
39-
#
40-
# TODO check that message was actually yielded
59+
# There may be multiple message providers defined, and not every one of them
60+
# has to define a message for every test.
61+
if interaction_builder?
62+
if yielded_interaction?
63+
interactions << interaction_builder.interaction
64+
else
65+
raise Pact::Error.new("`send_message_string` was not called for message \"#{interaction_builder.interaction.description}\"")
66+
end
67+
end
4168
end
4269

4370
def write_pact
44-
Pact::Message::Consumer::UpdatePact.call(interactions, "./spec/pacts", consumer_name, provider_name, "2.0.0")
71+
Pact::Message::Consumer::WritePact.call(interactions, "./spec/pacts", consumer_name, provider_name, "2.0.0", :overwrite)
4572
end
4673

4774
private
4875

49-
attr_writer :interaction_builder
5076
attr_accessor :consumer_name, :provider_name, :consumer_contract_details, :contents, :interactions
5177

78+
def interaction_builder?
79+
!!@interaction_builder
80+
end
81+
82+
def yielded_interaction?
83+
@yielded_interaction
84+
end
85+
5286
def interaction_builder
5387
@interaction_builder ||=
5488
begin

lib/pact/message/consumer/interaction_builder.rb

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,17 @@ def is_expected_to_send description
1717
self
1818
end
1919

20-
def given provider_state
21-
@interaction.provider_state = provider_state.nil? ? nil : provider_state.to_s
20+
def given name, params = {}
21+
if name
22+
@interaction.provider_states << Pact::ProviderState.new(name, params)
23+
end
2224
self
2325
end
2426

27+
alias_method :and, :given
28+
2529
def with_metadata(object)
26-
# TODO implement this
30+
interaction.metadata = object
2731
self
2832
end
2933

lib/pact/message/consumer/interaction_decorator.rb

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@ def as_json options = {}
1717
hash[:providerStates] = provider_states
1818
hash[:contents] = extract_contents
1919
hash[:matchingRules] = extract_matching_rules
20-
hash[:metaData] = message.metadata || {}
20+
if message.metadata
21+
hash[:metaData] = message.metadata
22+
end
2123
fix_all_the_things hash
2224
end
2325

@@ -42,9 +44,14 @@ def provider_states
4244
end
4345

4446
def extract_matching_rules
45-
{
46-
body: Pact::MatchingRules.extract(message.contents.contents, pact_specification_version: pact_specification_version)
47-
}
47+
body_matching_rules = Pact::MatchingRules.extract(message.contents.contents, pact_specification_version: pact_specification_version)
48+
if body_matching_rules.any?
49+
{
50+
body: body_matching_rules
51+
}
52+
else
53+
{}
54+
end
4855
end
4956

5057
def pact_specification_version

lib/pact/message/consumer/spec_hooks.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ module Consumer
66
class SpecHooks
77
def before_each example_description
88
Pact::Message.consumer_world.register_pact_example_ran
9+
Pact::Message.consumer_world.consumer_contract_builders.each(&:reset)
910
end
1011

1112
def after_each example_description
Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,26 +4,27 @@
44
module Pact
55
module Message
66
module Consumer
7-
class UpdatePact
7+
class WritePact
88

9-
def initialize messages, pact_dir, consumer_name, provider_name, pact_specification_version
9+
def initialize messages, pact_dir, consumer_name, provider_name, pact_specification_version, pactfile_write_mode
1010
@pact_dir = pact_dir
1111
@messages = messages
1212
@consumer_name = consumer_name
1313
@provider_name = provider_name
1414
@pact_specification_version = pact_specification_version
15+
@pactfile_write_mode = pactfile_write_mode
1516
end
1617

17-
def self.call(messages, pact_dir, consumer_name, provider_name, pact_specification_version)
18-
new(messages, pact_dir, consumer_name, provider_name, pact_specification_version).call
18+
def self.call(messages, pact_dir, consumer_name, provider_name, pact_specification_version, pactfile_write_mode)
19+
new(messages, pact_dir, consumer_name, provider_name, pact_specification_version, pactfile_write_mode).call
1920
end
2021

2122
def call
2223
details = {
23-
consumer: {name: consumer_name},
24-
provider: {name: provider_name},
24+
consumer: { name: consumer_name },
25+
provider: { name: provider_name },
2526
interactions: [*messages],
26-
pactfile_write_mode: :update,
27+
pactfile_write_mode: pactfile_write_mode,
2728
pact_dir: pact_dir,
2829
pact_specification_version: pact_specification_version,
2930
error_stream: StringIO.new,
@@ -36,7 +37,7 @@ def call
3637

3738
private
3839

39-
attr_reader :messages, :pact_dir, :consumer_name, :provider_name, :pact_specification_version
40+
attr_reader :messages, :pact_dir, :consumer_name, :provider_name, :pact_specification_version, :pactfile_write_mode
4041
end
4142
end
4243
end

spec/features/create_message_pact_spec.rb

Lines changed: 102 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,18 @@
1515
pact_specification_version '2'
1616
end
1717
end
18+
19+
has_pact_with "Wiffle Provider" do
20+
builder :wiffle_producer do
21+
pact_specification_version '2'
22+
end
23+
end
1824
end
1925

2026
FileUtils.rm_rf ZOO_PACT_FILE_PATH
2127
end
2228

23-
class MessageHandler
24-
29+
class StringMessageHandler
2530
attr_reader :output_stream
2631

2732
def initialize
@@ -34,32 +39,109 @@ def call(content_string)
3439
end
3540
end
3641

37-
let(:message_handler) { MessageHandler.new }
42+
class HashSymbolMessageHandler
43+
attr_reader :output_stream
3844

39-
it "allows a consumer to test that it can handle a message example correctly", pact: :message do
40-
alice_producer
41-
.given("there is an alligator named Mary")
42-
.is_expected_to_send("an alligator message")
43-
.with_metadata(type: 'animal')
44-
.with_content(name: "Mary")
45+
def initialize
46+
@output_stream = StringIO.new
47+
end
4548

46-
alice_producer.send_message_string do | content_string |
47-
message_handler.call(content_string)
49+
def call(content_hash)
50+
output_stream.print "Hello #{content_hash[:name]}"
4851
end
52+
end
4953

50-
expect(message_handler.output_stream.string).to eq ("Hello Mary")
54+
class HashStringMessageHandler
55+
attr_reader :output_stream
56+
57+
def initialize
58+
@output_stream = StringIO.new
59+
end
60+
61+
def call(content_hash)
62+
output_stream.print "Hello #{content_hash['name']}"
63+
end
5164
end
5265

53-
it "allows a consumer to test that it can handle a second message example correctly", pact: :message do
54-
alice_producer
55-
.given("there is an alligator named John")
56-
.is_expected_to_send("an alligator message")
57-
.with_content(name: like("John"))
66+
class ArrayMessageHandler
67+
attr_reader :output_stream
68+
69+
def initialize
70+
@output_stream = StringIO.new
71+
end
5872

59-
alice_producer.send_message_string do | content_string |
60-
message_handler.call(content_string)
73+
def call(array)
74+
output_stream.print "Hello #{array.join(", ")}"
6175
end
76+
end
77+
78+
context "with a string message" do
79+
let(:message_handler) { StringMessageHandler.new }
80+
81+
it "allows a consumer to test that it can handle the expected message", pact: :message do
82+
alice_producer
83+
.given("there is an alligator named Mary")
84+
.is_expected_to_send("an alligator message")
85+
.with_metadata(type: 'animal')
86+
.with_content(name: "Mary")
6287

63-
expect(message_handler.output_stream.string).to eq ("Hello John")
88+
alice_producer.send_message_string do | content_string |
89+
message_handler.call(content_string)
90+
end
91+
92+
expect(message_handler.output_stream.string).to eq ("Hello Mary")
93+
end
94+
end
95+
96+
context "with a hash message with symbol keys" do
97+
let(:message_handler) { HashSymbolMessageHandler.new }
98+
99+
it "allows a consumer to test that it can handle the expected message", pact: :message do
100+
alice_producer
101+
.given("there is an alligator named John")
102+
.is_expected_to_send("an alligator message")
103+
.with_content(name: like("John"))
104+
105+
alice_producer.send_message_hash do | content_hash |
106+
message_handler.call(content_hash)
107+
end
108+
109+
expect(message_handler.output_stream.string).to eq ("Hello John")
110+
end
111+
end
112+
113+
context "with a hash message with string keys" do
114+
let(:message_handler) { HashStringMessageHandler.new }
115+
116+
it "allows a consumer to test that it can handle the expected message", pact: :message do
117+
alice_producer
118+
.given("there is an alligator named Sue")
119+
.is_expected_to_send("an alligator message")
120+
.with_content("name" => like("Sue"))
121+
122+
alice_producer.send_message_hash do | content_hash |
123+
message_handler.call(content_hash)
124+
end
125+
126+
expect(message_handler.output_stream.string).to eq ("Hello Sue")
127+
end
128+
end
129+
130+
context "with an array message" do
131+
let(:message_handler) { ArrayMessageHandler.new }
132+
133+
it "allows a consumer to test that it can handle the expected message", pact: :message do
134+
alice_producer
135+
.given("there is an alligator named John", { some: "params" })
136+
.and("there is an alligator named Mary")
137+
.is_expected_to_send("an alligator message")
138+
.with_content([like("John"), like("Mary")])
139+
140+
alice_producer.send_message_hash do | content_hash |
141+
message_handler.call(content_hash)
142+
end
143+
144+
expect(message_handler.output_stream.string).to eq ("Hello John, Mary")
145+
end
64146
end
65147
end

0 commit comments

Comments
 (0)