pact-message 0.5.0 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: f8d37a134b83c10962b37435bf425ce69c8751a6
4
- data.tar.gz: e1fce249bfee4a95eee7d40dd1f81048f2c68ab9
2
+ SHA256:
3
+ metadata.gz: 8626b3359a7fdae0eb187aa5bf25768fbd673059f1af6e78151441834d735317
4
+ data.tar.gz: 10f3f19b10d7da81a729680847420d0f80d621728a406929bdca73f36e561c07
5
5
  SHA512:
6
- metadata.gz: 382faf25a819b3589a4ffbd2e259e8fcc09341400738d1dea3e03d757d803e02ec83d4ad1ff114fe2f209a09c49734d56c09e633cb1626f9c41ef3ce37ba9496
7
- data.tar.gz: 2fe5306302cacaad96d5f257bcfe25f02b018df33dfef4b91fbd23852a44181df5fcb52645471004505e08d84b5485ee90f421b0509d8ec64021d53be36cfb4b
6
+ metadata.gz: 8b27088fb2e9e6caf5ac067a05c39ed8f17155804411e89532907236f95268343e4c416bac1200dfb58230dc4fd3d79fa45815b32777426ed5b528b3f96fffc5
7
+ data.tar.gz: 81b005e65c3dfba662d0fac20f8afcfe0941f8dd1b6332ba7300d3e34be8565acca9269f4605fb3cc90f8feafaaf439a02f32ef11baf40f4bdc6e3b7a314a5ab
@@ -0,0 +1,45 @@
1
+ ---
2
+ name: "Release gem"
3
+
4
+ on:
5
+ repository_dispatch:
6
+ types:
7
+ - release-triggered
8
+
9
+ jobs:
10
+ release:
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - uses: actions/checkout@v2
14
+ with:
15
+ fetch-depth: 0
16
+ - id: release-gem
17
+ uses: pact-foundation/release-gem@v0.0.11
18
+ env:
19
+ GEM_HOST_API_KEY: "${{ secrets.RUBYGEMS_API_KEY }}"
20
+ GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
21
+ INCREMENT: "${{ github.event.client_payload.increment }}"
22
+ outputs:
23
+ gem_name: "${{ steps.release-gem.outputs.gem_name }}"
24
+ version: "${{ steps.release-gem.outputs.version }}"
25
+ increment: "${{ steps.release-gem.outputs.increment }}"
26
+
27
+ notify-gem-released:
28
+ needs: release
29
+ strategy:
30
+ matrix:
31
+ repository: [pact-foundation/pact-ruby-cli, pact-foundation/pact-ruby-standalone]
32
+ runs-on: ubuntu-latest
33
+ steps:
34
+ - name: Notify ${{ matrix.repository }} of gem release
35
+ uses: peter-evans/repository-dispatch@v1
36
+ with:
37
+ token: ${{ secrets.GHTOKENFORPACTCLIRELEASE }}
38
+ repository: ${{ matrix.repository }}
39
+ event-type: gem-released
40
+ client-payload: |
41
+ {
42
+ "name": "${{ needs.release.outputs.gem_name }}",
43
+ "version": "${{ needs.release.outputs.version }}",
44
+ "increment": "${{ needs.release.outputs.increment }}"
45
+ }
@@ -0,0 +1,23 @@
1
+ name: Test
2
+
3
+ on: push
4
+
5
+ jobs:
6
+ test:
7
+ runs-on: "ubuntu-latest"
8
+ continue-on-error: ${{ matrix.experimental }}
9
+ strategy:
10
+ fail-fast: false
11
+ matrix:
12
+ ruby_version: ["2.2", "2.7"]
13
+ experimental: [false]
14
+ include:
15
+ - ruby_version: "3.0"
16
+ experimental: true
17
+ steps:
18
+ - uses: actions/checkout@v2
19
+ - uses: ruby/setup-ruby@v1
20
+ with:
21
+ ruby-version: ${{ matrix.ruby_version }}
22
+ - run: "bundle install"
23
+ - run: "bundle exec rake"
data/.gitignore CHANGED
@@ -12,3 +12,4 @@ spec/pacts/zoo_consumer-zoo_provider.json
12
12
 
13
13
  # rspec failure tracking
14
14
  .rspec_status
15
+ spec/pacts/
@@ -1,3 +1,62 @@
1
+ <a name="v0.10.0"></a>
2
+ ### v0.10.0 (2021-01-22)
3
+
4
+ #### Features
5
+
6
+ * allow pact-message update to receive JSON via the standard input ([5cbb664](/../../commit/5cbb664))
7
+
8
+ <a name="v0.9.0"></a>
9
+ ### v0.9.0 (2020-11-04)
10
+
11
+ #### Features
12
+
13
+ * allow pact dir to be configured ([f2f9626](/../../commit/f2f9626))
14
+ * verify that each message has been yielded ([1d4d92c](/../../commit/1d4d92c))
15
+
16
+ * **consumer**
17
+ * only update pact if test suite passes ([e99276d](/../../commit/e99276d))
18
+
19
+ <a name="v0.8.0"></a>
20
+ ### v0.8.0 (2020-09-28)
21
+
22
+ #### Features
23
+
24
+ * reify message when yielding ([d7c0a4a](/../../commit/d7c0a4a))
25
+
26
+ #### Bug Fixes
27
+
28
+ * fix bug in Message.to_hash ([e354cd2](/../../commit/e354cd2))
29
+
30
+ <a name="v0.7.0"></a>
31
+ ### v0.7.0 (2020-02-10)
32
+
33
+
34
+ #### Features
35
+
36
+ * Add metadata to a message request ([796590f](/../../commit/796590f))
37
+ * support the _id attribute from the Pact Broker, and give each message an index ([3a05501](/../../commit/3a05501))
38
+
39
+
40
+ #### Bug Fixes
41
+
42
+ * add back support for using providerState instead of providerStates when updating a pact ([b494a76](/../../commit/b494a76))
43
+
44
+
45
+ <a name="v0.6.0"></a>
46
+ ### v0.6.0 (2020-02-10)
47
+
48
+
49
+ #### Features
50
+
51
+ * Add metadata to a message request ([796590f](/../../commit/796590f))
52
+ * support the _id attribute from the Pact Broker, and give each message an index ([3a05501](/../../commit/3a05501))
53
+
54
+
55
+ #### Bug Fixes
56
+
57
+ * add back support for using providerState instead of providerStates when updating a pact ([b494a76](/../../commit/b494a76))
58
+
59
+
1
60
  <a name="v0.5.0"></a>
2
61
  ### v0.5.0 (2018-10-04)
3
62
 
@@ -0,0 +1,15 @@
1
+ FROM ruby:2.2.4-alpine
2
+
3
+ # Installation path
4
+ ENV HOME=/app
5
+ WORKDIR $HOME
6
+
7
+ RUN set -ex && \
8
+ adduser -h $HOME -s /bin/false -D -S -G root ruby && \
9
+ chmod g+w $HOME && \
10
+ apk add --update --no-cache make gcc libc-dev git
11
+
12
+ RUN gem install bundler -v 1.17.3
13
+ COPY Gemfile pact-message.gemspec $HOME/
14
+ COPY lib/pact/message/version.rb $HOME/lib/pact/message/version.rb
15
+ RUN bundle install --no-cache
data/README.md CHANGED
@@ -4,13 +4,14 @@
4
4
 
5
5
  Create and verify consumer driven contracts for messages.
6
6
 
7
- This project is still under development and is not ready for production use.
7
+
8
8
 
9
9
  ## Installation
10
10
 
11
11
  Add this line to your application's Gemfile:
12
12
 
13
13
  ```ruby
14
+ gem 'pact'
14
15
  gem 'pact-message'
15
16
  ```
16
17
 
@@ -20,11 +21,76 @@ And then execute:
20
21
 
21
22
  Or install it yourself as:
22
23
 
24
+ $ gem install pact
23
25
  $ gem install pact-message
24
26
 
25
27
  ## Usage
26
28
 
29
+ The key to using Message Pact is to completely separate the business logic that creates the message from the transmission protocol (eg. Kafka, Websockets, Lambda). This allows you to write a contract for the message contents, no matter how it is communicated.
30
+
31
+ ### Consumer
32
+
33
+ Not finished yet as nobody has asked for it. Ping @Beth Skurrie on slack.pact.io if you'd like use this.
34
+
35
+ ### Provider
36
+
37
+ Also called a "producer". Message pact verification follows all the same principles as HTTP pact verification, except that instead of verifying that a provider can make the expected HTTP response, we are verifying that the provider can create the expected message. Please read the HTTP Pact [verification](https://github.com/pact-foundation/pact-ruby/wiki/Verifying-pacts) documentation. The only difference is in the configuration block. Use `message_provider` instead of `service_provider`, and configure a `builder` block that takes a `|description|` argument, instead of a Rack `app` block.
38
+
39
+ Make sure you've required 'pact/message' as well as 'pact'.
40
+
41
+ ```ruby
42
+ require 'pact'
43
+ require 'pact/message'
44
+
45
+ Pact.message_provider "MyMessageProvider" do
46
+ honours_pact_with "MyMessageConsumer" do
47
+ pact_uri "/path/or/url/to/your/pact", {
48
+ username: "optional username",
49
+ password: "optional password",
50
+ token: "optional token"
51
+ }
52
+ end
53
+
54
+ # or
55
+
56
+ honours_pacts_from_pact_broker do
57
+ # See docs at https://github.com/pact-foundation/pact-ruby/wiki/Verifying-pacts
58
+ end
59
+
60
+ builder do |message_description|
61
+ #... code that accepts a message description and returns
62
+ # a message hash that should match what is expected in the pact
63
+ do
64
+ end
65
+
66
+ ```
67
+
68
+ How you map between the message description and the code that creates the message is up to you. The easiest way is something like this:
69
+
70
+ ```ruby
71
+ class MyMessageProvider
72
+ def create_hello_message
73
+ {
74
+ text: "Hello world"
75
+ }
76
+ end
77
+ end
78
+
79
+ CONFIG = {
80
+ "a hello message" => lambda { MyMessageProvider.new.create_hello_message }
81
+ }
82
+
83
+ Pact.message_provider "SomeProvider" do
84
+ builder do |description|
85
+ CONFIG[description].call
86
+ do
87
+ end
88
+
89
+ ```
90
+
91
+ #### Provider states
27
92
 
93
+ Provider states work the same way for Message Pact as they do for HTTP Pact. Please read the [provider state](https://github.com/pact-foundation/pact-ruby#using-provider-states) docs in the HTTP Pact project.
28
94
 
29
95
  ## Development
30
96
 
@@ -13,7 +13,7 @@ module Pact
13
13
  include Pact::ActiveSupportSupport
14
14
  include Pact::SymbolizeKeys
15
15
 
16
- attr_accessor :description, :contents, :provider_state, :provider_states, :metadata
16
+ attr_accessor :description, :contents, :provider_state, :provider_states, :metadata, :_id, :index
17
17
 
18
18
  def initialize attributes = {}
19
19
  @description = attributes[:description]
@@ -21,6 +21,8 @@ module Pact
21
21
  @provider_states = attributes[:provider_states] || []
22
22
  @contents = attributes[:contents]
23
23
  @metadata = attributes[:metadata]
24
+ @_id = attributes[:_id]
25
+ @index = attributes[:index]
24
26
  end
25
27
 
26
28
  def self.from_hash hash, options = {}
@@ -32,11 +34,12 @@ module Pact
32
34
  contents_hash = Pact::MatchingRules.merge(hash['contents'], contents_matching_rules, opts)
33
35
  contents = Pact::ConsumerContract::Message::Contents.from_hash(contents_hash)
34
36
  metadata = hash['metaData'] || hash['metadata']
35
- provider_state = hash['providerStates'] && hash['providerStates'].first && hash['providerStates'].first['name']
36
- provider_states = parse_provider_states(hash['providerStates'])
37
+
38
+ provider_state_name = parse_provider_state_name(hash['providerState'], hash['providerStates'])
39
+ provider_states = parse_provider_states(provider_state_name, hash['providerStates'])
37
40
  new(symbolize_keys(hash).merge(
38
41
  contents: contents,
39
- provider_state: provider_state,
42
+ provider_state: provider_state_name,
40
43
  provider_states: provider_states,
41
44
  metadata: metadata))
42
45
  end
@@ -45,7 +48,7 @@ module Pact
45
48
  {
46
49
  description: description,
47
50
  provider_states: [{ name: provider_state }],
48
- contents: contents.to_hash,
51
+ contents: contents.contents,
49
52
  metadata: metadata
50
53
  }
51
54
  end
@@ -61,7 +64,8 @@ module Pact
61
64
  providerStates: [{
62
65
  name: provider_state,
63
66
  params: {}
64
- }]
67
+ }],
68
+ metadata: metadata
65
69
  }
66
70
  )
67
71
  end
@@ -120,9 +124,19 @@ module Pact
120
124
 
121
125
  private
122
126
 
123
- def self.parse_provider_states provider_states
124
- (provider_states || []).collect do | provider_state_hash |
125
- Pact::ProviderState.new(provider_state_hash['name'], provider_state_hash['params'])
127
+ def self.parse_provider_state_name provider_state, provider_states
128
+ (provider_states && provider_states.first && provider_states.first['name']) || provider_state
129
+ end
130
+
131
+ def self.parse_provider_states provider_state_name, provider_states
132
+ if provider_states && provider_states.any?
133
+ provider_states.collect do | provider_state_hash |
134
+ Pact::ProviderState.new(provider_state_hash['name'], provider_state_hash['params'])
135
+ end
136
+ elsif provider_state_name
137
+ [Pact::ProviderState.new(provider_state_name, {})]
138
+ else
139
+ []
126
140
  end
127
141
  end
128
142
  end
@@ -1,3 +1,4 @@
1
+ require 'pact/reification'
1
2
  module Pact
2
3
  class ConsumerContract
3
4
  class Message
@@ -15,13 +16,25 @@ module Pact
15
16
  end
16
17
 
17
18
  def to_s
18
- if @contents.is_a?(Hash) || @contents.is_a?(Array)
19
- @contents.to_json
19
+ if contents.is_a?(Hash) || contents.is_a?(Array)
20
+ contents.to_json
20
21
  else
21
- @contents.to_s
22
+ contents.to_s
22
23
  end
23
24
  end
24
25
 
26
+ def reified_contents_string
27
+ if contents.is_a?(Hash) || contents.is_a?(Array)
28
+ Pact::Reification.from_term(contents).to_json
29
+ else
30
+ Pact::Reification.from_term(contents).to_s
31
+ end
32
+ end
33
+
34
+ def reified_contents_hash
35
+ Pact::Reification.from_term(contents)
36
+ end
37
+
25
38
  def contents
26
39
  @contents
27
40
  end
@@ -9,13 +9,23 @@ module Pact
9
9
  method_option :pact_dir, required: true, desc: "The Pact directory"
10
10
  method_option :pact_specification_version, required: false, default: "2.0.0", desc: "The Pact Specification version"
11
11
 
12
- 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.'
13
- def update(message)
12
+ # Update a pact with the given message, or create the pact if it does not exist
13
+ desc 'update MESSAGE_JSON', "Update/create a pact. If MESSAGE_JSON is omitted or '-', it is read from stdin"
14
+ long_desc <<-MSG, wrapping: false
15
+ Update a pact with the given message, or create the pact if it does not exist.
16
+ The MESSAGE_JSON may be in the legacy Ruby JSON format or the v2+ format.
17
+ If MESSAGE_JSON is not provided or is '-', the content will be read from
18
+ standard input.
19
+ MSG
20
+ def update(maybe_json = '-')
14
21
  require 'pact/message'
15
- require 'pact/message/consumer/update_pact'
22
+ require 'pact/message/consumer/write_pact'
23
+
24
+ message_json = JSON.parse(maybe_json == '-' ? $stdin.read : maybe_json)
25
+
16
26
  pact_specification_version = Pact::SpecificationVersion.new(options.pact_specification_version)
17
- 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)
27
+ message_hash = Pact::Message.from_hash(message_json, { pact_specification_version: pact_specification_version })
28
+ Pact::Message::Consumer::WritePact.call(message_hash, options.pact_dir, options.consumer, options.provider, options.pact_specification_version, :update)
19
29
  end
20
30
 
21
31
  desc 'reify', "Take a JSON document with embedded pact matchers and return a concrete example"
@@ -1,6 +1,6 @@
1
1
  require 'pact/message/consumer/consumer_contract_builder'
2
2
  require 'pact/message/consumer/consumer_contract_builders'
3
- # require 'pact/consumer/world'
3
+ require 'pact/message/consumer/world'
4
4
 
5
5
  module Pact
6
6
  module Message
@@ -40,11 +40,11 @@ module Pact
40
40
 
41
41
  def create_consumer_contract_builder
42
42
  consumer_contract_builder_fields = {
43
- :consumer_name => consumer_name,
44
- :provider_name => provider_name,
43
+ consumer_name: consumer_name,
44
+ provider_name: provider_name,
45
+ pact_specification_version: pact_specification_version,
46
+ pact_dir: Pact.configuration.pact_dir
45
47
  }
46
- # :pactfile_write_mode => Pact.configuration.pactfile_write_mode,
47
- # :pact_dir => Pact.configuration.pact_dir
48
48
  Pact::Message::Consumer::ConsumerContractBuilder.new consumer_contract_builder_fields
49
49
  end
50
50
 
@@ -58,7 +58,8 @@ module Pact
58
58
  Pact::Message::Consumer::ConsumerContractBuilders.send(:define_method, @name.to_sym) do
59
59
  consumer_contract_builder
60
60
  end
61
- # Pact.consumer_world.add_consumer_contract_builder consumer_contract_builder
61
+
62
+ Pact::Message.consumer_world.add_consumer_contract_builder consumer_contract_builder
62
63
  end
63
64
  end
64
65
  end
@@ -18,9 +18,13 @@ module Pact
18
18
  end
19
19
 
20
20
  dsl do
21
- def builder builder_name, &block
21
+ def mock_provider(builder_name, &block)
22
22
  self.builder = MessageBuilder.build(builder_name, consumer_name, name, &block)
23
23
  end
24
+
25
+ def builder(builder_name, &block)
26
+ expectation_builder(builder_name, &block)
27
+ end
24
28
  end
25
29
 
26
30
  def finalize
@@ -1,5 +1,6 @@
1
1
  require 'pact/message/consumer/interaction_builder'
2
- require 'pact/message/consumer/update_pact'
2
+ require 'pact/message/consumer/write_pact'
3
+ require 'pact/errors'
3
4
 
4
5
  module Pact
5
6
  module Message
@@ -10,39 +11,79 @@ module Pact
10
11
  @interaction_builder = nil
11
12
  @consumer_name = attributes[:consumer_name]
12
13
  @provider_name = attributes[:provider_name]
14
+ @pact_specification_version = attributes[:pact_specification_version]
15
+ @pact_dir = attributes[:pact_dir]
13
16
  @interactions = []
17
+ @yielded_interaction = false
14
18
  end
15
19
 
16
- def given(provider_state)
17
- interaction_builder.given(provider_state)
20
+ def reset
21
+ @interaction_builder = nil
22
+ @yielded_interaction = false
23
+ end
24
+
25
+ def given(provider_state, params = {})
26
+ interaction_builder.given(provider_state, params)
18
27
  end
19
28
 
20
29
  def is_expected_to_send(description)
21
30
  interaction_builder.is_expected_to_send(provider_state)
22
31
  end
23
32
 
24
- def send_message
25
- # TODO handle matchers
26
- yield @contents_string if block_given?
33
+ def send_message_string
34
+ if interaction_builder?
35
+ if block_given?
36
+ @yielded_interaction = true
37
+ yield interaction_builder.interaction.contents.reified_contents_string
38
+ end
39
+ else
40
+ raise Pact::Error.new("No message expectation has been defined")
41
+ end
42
+ end
43
+
44
+ def send_message_hash
45
+ if interaction_builder?
46
+ if block_given?
47
+ @yielded_interaction = true
48
+ yield interaction_builder.interaction.contents.reified_contents_hash
49
+ end
50
+ else
51
+ raise Pact::Error.new("No message expectation has been defined")
52
+ end
27
53
  end
28
54
 
29
55
  def handle_interaction_fully_defined(interaction)
56
+ @contents = interaction.contents
30
57
  @contents_string = interaction.contents.to_s
31
- @interactions << interaction
32
- @interaction_builder = nil
33
- # TODO pull these from pact config
34
- Pact::Message::Consumer::UpdatePact.call(interaction, "./spec/pacts", consumer_name, provider_name, "2.0.0")
35
58
  end
36
59
 
37
60
  def verify example_description
38
- #
39
- # TODO check that message was actually yielded
61
+ # There may be multiple message providers defined, and not every one of them
62
+ # has to define a message for every test.
63
+ if interaction_builder?
64
+ if yielded_interaction?
65
+ interactions << interaction_builder.interaction
66
+ else
67
+ raise Pact::Error.new("`send_message_string` was not called for message \"#{interaction_builder.interaction.description}\"")
68
+ end
69
+ end
70
+ end
71
+
72
+ def write_pact
73
+ Pact::Message::Consumer::WritePact.call(interactions, pact_dir, consumer_name, provider_name, pact_specification_version, :overwrite)
40
74
  end
41
75
 
42
76
  private
43
77
 
44
- attr_writer :interaction_builder
45
- attr_accessor :consumer_name, :provider_name, :consumer_contract_details
78
+ attr_accessor :consumer_name, :provider_name, :consumer_contract_details, :contents, :interactions, :pact_specification_version, :pact_dir
79
+
80
+ def interaction_builder?
81
+ !!@interaction_builder
82
+ end
83
+
84
+ def yielded_interaction?
85
+ @yielded_interaction
86
+ end
46
87
 
47
88
  def interaction_builder
48
89
  @interaction_builder ||=
@@ -17,13 +17,17 @@ module Pact
17
17
  self
18
18
  end
19
19
 
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
22
24
  self
23
25
  end
24
26
 
27
+ alias_method :and, :given
28
+
25
29
  def with_metadata(object)
26
- # TODO implement this
30
+ interaction.metadata = object
27
31
  self
28
32
  end
29
33
 
@@ -17,7 +17,9 @@ module Pact
17
17
  hash[:providerStates] = provider_states
18
18
  hash[:contents] = extract_contents
19
19
  hash[:matchingRules] = extract_matching_rules
20
- hash[:metaData] = message.metadata || {}
20
+ if message.metadata
21
+ hash[:metaData] = message.metadata
22
+ end
21
23
  fix_all_the_things hash
22
24
  end
23
25
 
@@ -42,9 +44,14 @@ module Pact
42
44
  end
43
45
 
44
46
  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
48
55
  end
49
56
 
50
57
  def pact_specification_version
@@ -20,7 +20,15 @@ hooks = Pact::Message::Consumer::SpecHooks.new
20
20
  RSpec.configure do |config|
21
21
  config.include Pact::Message::Consumer::RSpec, :pact => :message
22
22
 
23
- config.after :each, :pact => true do | example |
23
+ config.before :each, :pact => :message do | example |
24
+ hooks.before_each Pact::RSpec.full_description(example)
25
+ end
26
+
27
+ config.after :each, :pact => :message do | example |
24
28
  hooks.after_each Pact::RSpec.full_description(example)
25
29
  end
30
+
31
+ config.after :all do
32
+ hooks.after_suite
33
+ end
26
34
  end
@@ -1,12 +1,25 @@
1
+ require 'pact/message/consumer/world'
2
+
1
3
  module Pact
2
4
  module Message
3
5
  module Consumer
4
6
  class SpecHooks
7
+ def before_each example_description
8
+ Pact::Message.consumer_world.register_pact_example_ran
9
+ Pact::Message.consumer_world.consumer_contract_builders.each(&:reset)
10
+ end
11
+
5
12
  def after_each example_description
6
13
  Pact.configuration.message_provider_verifications.each do | message_provider_verification |
7
14
  message_provider_verification.call example_description
8
15
  end
9
16
  end
17
+
18
+ def after_suite
19
+ if Pact::Message.consumer_world.any_pact_examples_ran?
20
+ Pact::Message.consumer_world.consumer_contract_builders.each(&:write_pact)
21
+ end
22
+ end
10
23
  end
11
24
  end
12
25
  end
@@ -0,0 +1,36 @@
1
+ module Pact
2
+ module Message
3
+ def self.consumer_world
4
+ @consumer_world ||= Consumer::World.new
5
+ end
6
+
7
+ # internal api, for testing only
8
+ def self.clear_consumer_world
9
+ @consumer_world = nil
10
+ end
11
+
12
+ module Consumer
13
+ class World
14
+ def initialize
15
+ @any_pact_examples_ran = false
16
+ end
17
+
18
+ def consumer_contract_builders
19
+ @consumer_contract_builders ||= []
20
+ end
21
+
22
+ def add_consumer_contract_builder consumer_contract_builder
23
+ consumer_contract_builders << consumer_contract_builder
24
+ end
25
+
26
+ def register_pact_example_ran
27
+ @any_pact_examples_ran = true
28
+ end
29
+
30
+ def any_pact_examples_ran?
31
+ @any_pact_examples_ran
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -4,26 +4,27 @@ require 'pact/message/consumer/consumer_contract_decorator'
4
4
  module Pact
5
5
  module Message
6
6
  module Consumer
7
- class UpdatePact
7
+ class WritePact
8
8
 
9
- def initialize message, 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
10
10
  @pact_dir = pact_dir
11
- @message = message
11
+ @messages = messages
12
12
  @consumer_name = consumer_name
13
13
  @provider_name = provider_name
14
14
  @pact_specification_version = pact_specification_version
15
+ @pactfile_write_mode = pactfile_write_mode
15
16
  end
16
17
 
17
- def self.call(message, pact_dir, consumer_name, provider_name, pact_specification_version)
18
- new(message, 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
19
20
  end
20
21
 
21
22
  def call
22
23
  details = {
23
- consumer: {name: consumer_name},
24
- provider: {name: provider_name},
25
- interactions: [message],
26
- pactfile_write_mode: :update,
24
+ consumer: { name: consumer_name },
25
+ provider: { name: provider_name },
26
+ interactions: [*messages],
27
+ pactfile_write_mode: pactfile_write_mode,
27
28
  pact_dir: pact_dir,
28
29
  pact_specification_version: pact_specification_version,
29
30
  error_stream: StringIO.new,
@@ -36,7 +37,7 @@ module Pact
36
37
 
37
38
  private
38
39
 
39
- attr_reader :message, :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
40
41
  end
41
42
  end
42
43
  end
@@ -11,7 +11,9 @@ module Pact
11
11
  def call(hash)
12
12
  hash = symbolize_keys(hash)
13
13
  options = { pact_specification_version: pact_specification_version(hash) }
14
- interactions = hash[:messages].collect { |hash| Pact::ConsumerContract::Message.from_hash(hash, options)}
14
+ interactions = hash[:messages].each_with_index.collect do |hash, index|
15
+ Pact::ConsumerContract::Message.from_hash({ index: index }.merge(hash), options)
16
+ end
15
17
  ConsumerContract.new(
16
18
  :consumer => ServiceConsumer.from_hash(hash[:consumer]),
17
19
  :provider => ServiceProvider.from_hash(hash[:provider]),
@@ -1,5 +1,5 @@
1
1
  module Pact
2
2
  module Message
3
- VERSION = "0.5.0"
3
+ VERSION = "0.10.0"
4
4
  end
5
5
  end
@@ -0,0 +1 @@
1
+ require 'pact/message'
@@ -33,11 +33,10 @@ Gem::Specification.new do |spec|
33
33
  # pact-mock_service dependencies are Pact::ConsumerContractDecorator
34
34
  # and Pact::ConsumerContractWriter. Potentially we should extract
35
35
  # or duplicate these classes to remove the pact-mock_service dependency.
36
- spec.add_runtime_dependency "pact-mock_service", "~> 2.6"
36
+ spec.add_runtime_dependency "pact-mock_service", "~> 3.1"
37
37
  spec.add_runtime_dependency "thor", "~> 0.20"
38
38
 
39
- spec.add_development_dependency "bundler", "~> 1.15"
40
- spec.add_development_dependency "rake", "~> 10.0"
39
+ spec.add_development_dependency "rake", "~> 12.3", ">= 12.3.3"
41
40
  spec.add_development_dependency "rspec", "~> 3.0"
42
41
  spec.add_development_dependency "pry-byebug"
43
42
  spec.add_development_dependency 'conventional-changelog', '~>1.2'
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env bash
2
+
3
+ export IMAGE=pact_message_ruby_bundle_base
4
+
5
+ function docker_build_bundle_base() {
6
+ docker build . -f Dockerfile-bundle-base -t $IMAGE
7
+ }
8
+
9
+ function bundle_update_on_docker() {
10
+ rm -rf tmp/Gemfile.lock
11
+ docker run --rm -v ${PWD}/tmp:/tmp/bundle_update $IMAGE:latest sh -c "bundle update && cp Gemfile.lock /tmp/bundle_update"
12
+ mv tmp/Gemfile.lock .
13
+ }
14
+
15
+ function on_docker() {
16
+ docker run --rm -v ${PWD}:/app $IMAGE:latest sh -c "$@"
17
+ }
18
+
19
+ gem_version() {
20
+ on_docker "bundle exec ruby -e \"require 'bump'; puts Bump::Bump.current\""
21
+ }
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env bash
2
+
3
+ gem_version() {
4
+ on_docker "bundle exec ruby -e \"require 'bump'; puts Bump::Bump.current\""
5
+ }
@@ -1,13 +1,26 @@
1
- #!/bin/bash
1
+ #!/bin/sh
2
2
  set -e
3
3
 
4
+ source script/docker-functions
5
+
4
6
  # avoid accidentally double incrementing when previous run fails
5
7
  git reset HEAD CHANGELOG.md lib/pact/message/version.rb
6
8
  git checkout -- lib/pact/message/version.rb
7
9
  git checkout -- CHANGELOG.md
8
- bundle exec bump ${1:-minor} --no-commit
9
- bundle exec rake generate_changelog
10
+
11
+ docker_build_bundle_base
12
+
13
+ script/release/bump-version.sh $1
14
+ script/release/generate-changelog.sh
15
+
10
16
  git add CHANGELOG.md lib/pact/message/version.rb
11
- git commit -m "chore(release): version $(ruby -r ./lib/pact/message/version.rb -e "puts Pact::Message::VERSION")"
12
- # bundle exec rake tag_for_release # TODO move release to travis
13
- bundle exec rake release
17
+ git commit -m "chore(release): version ${VERSION}
18
+
19
+ [ci-skip]"
20
+
21
+ VERSION=$(gem_version)
22
+ TAG="v${VERSION}"
23
+ git tag -a "${TAG}" -m "Releasing version ${TAG}"
24
+ git push origin "${TAG}"
25
+ git push origin master
26
+ echo "Releasing from https://travis-ci.org/pact-foundation/pact-message-ruby/builds"
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env sh
2
+
3
+ source script/docker-functions
4
+
5
+ on_docker "bundle exec bump ${1:-minor} --no-commit"
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env sh
2
+
3
+ set -e
4
+
5
+ source script/docker-functions
6
+ on_docker "bundle exec rake generate_changelog"
@@ -0,0 +1,30 @@
1
+ #!/bin/sh
2
+
3
+ # Script to trigger release of gem via the pact-foundation/release-gem action
4
+ # Requires a Github API token with repo scope stored in the
5
+ # environment variable GITHUB_ACCESS_TOKEN_FOR_PF_RELEASES
6
+
7
+ : "${GITHUB_ACCESS_TOKEN_FOR_PF_RELEASES:?Please set environment variable GITHUB_ACCESS_TOKEN_FOR_PF_RELEASES}"
8
+
9
+ if [ -n "$1" ]; then
10
+ increment="\"${1}\""
11
+ else
12
+ increment="null"
13
+ fi
14
+
15
+ repository_slug=$(git remote get-url origin | cut -d':' -f2 | sed 's/\.git//')
16
+
17
+ output=$(curl -v https://api.github.com/repos/${repository_slug}/dispatches \
18
+ -H 'Accept: application/vnd.github.everest-preview+json' \
19
+ -H "Authorization: Bearer $GITHUB_ACCESS_TOKEN_FOR_PF_RELEASES" \
20
+ -d "{\"event_type\": \"release-triggered\", \"client_payload\": {\"increment\": ${increment}}}" 2>&1)
21
+
22
+ if ! echo "${output}" | grep "HTTP\/.* 204" > /dev/null; then
23
+ echo "$output" | sed "s/${GITHUB_ACCESS_TOKEN_FOR_PF_RELEASES}/********/g"
24
+ echo "Failed to trigger release"
25
+ exit 1
26
+ else
27
+ echo "Release workflow triggered"
28
+ fi
29
+
30
+ echo "See https://github.com/${repository_slug}/actions?query=workflow%3A%22Release+gem%22"
@@ -0,0 +1,60 @@
1
+ require "rspec/core/rake_task"
2
+
3
+ ZOO_PACT_FILE_PATH = "spec/pacts/zoo_consumer-zoo_provider.json"
4
+
5
+ RSpec::Core::RakeTask.new(:pass) do | task |
6
+ task.pattern = "spec/features/create_message_pact_spec.rb"
7
+ end
8
+
9
+ RSpec::Core::RakeTask.new(:fail) do | task |
10
+ task.pattern = "spec/features/create_message_pact_with_failure_test.rb"
11
+ end
12
+
13
+ task :pass_writes_pact_file do
14
+ require 'json'
15
+ puts "Ensuring that pact file is written for successful test suites"
16
+ FileUtils.rm_rf(ZOO_PACT_FILE_PATH)
17
+ Rake::Task['pass'].execute
18
+ if !File.exist?(ZOO_PACT_FILE_PATH)
19
+ raise "Expected pact file to be written at #{ZOO_PACT_FILE_PATH}"
20
+ end
21
+
22
+ pact_hash = JSON.parse(File.read(ZOO_PACT_FILE_PATH))
23
+ if pact_hash['messages'].size < 2
24
+ raise "Expected pact file to contain more than 1 message"
25
+ end
26
+ end
27
+
28
+ task :fail_does_not_write_pact_file do
29
+ puts "Ensuring that pact file is NOT written for failed test suites"
30
+ FileUtils.rm_rf(ZOO_PACT_FILE_PATH)
31
+ expect_to_fail('bundle exec rake fail')
32
+ if File.exist?(ZOO_PACT_FILE_PATH)
33
+ raise "Expected pact file NOT to be written at #{ZOO_PACT_FILE_PATH}"
34
+ end
35
+ end
36
+
37
+ task :default => [:pass_writes_pact_file, :fail_does_not_write_pact_file]
38
+
39
+ def expect_to_fail command, options = {}
40
+ success = execute_command command, options
41
+ fail "Expected '#{command}' to fail" if success
42
+ end
43
+
44
+ def execute_command command, options
45
+ require 'open3'
46
+ result = nil
47
+ Open3.popen3(command) {|stdin, stdout, stderr, wait_thr|
48
+ result = wait_thr.value
49
+ ensure_patterns_present(command, options, stdout, stderr) if options[:with]
50
+ }
51
+ result.success?
52
+ end
53
+
54
+ def ensure_patterns_present command, options, stdout, stderr
55
+ require 'term/ansicolor'
56
+ output = stdout.read + stderr.read
57
+ options[:with].each do | pattern |
58
+ raise (::Term::ANSIColor.red("Could not find #{pattern.inspect} in output of #{command}") + "\n\n#{output}") unless output =~ pattern
59
+ end
60
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pact-message
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.10.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Beth Skurrie
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-10-04 00:00:00.000000000 Z
11
+ date: 2021-01-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: pact-support
@@ -30,14 +30,14 @@ dependencies:
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '2.6'
33
+ version: '3.1'
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: '2.6'
40
+ version: '3.1'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: thor
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -52,34 +52,26 @@ dependencies:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0.20'
55
- - !ruby/object:Gem::Dependency
56
- name: bundler
57
- requirement: !ruby/object:Gem::Requirement
58
- requirements:
59
- - - "~>"
60
- - !ruby/object:Gem::Version
61
- version: '1.15'
62
- type: :development
63
- prerelease: false
64
- version_requirements: !ruby/object:Gem::Requirement
65
- requirements:
66
- - - "~>"
67
- - !ruby/object:Gem::Version
68
- version: '1.15'
69
55
  - !ruby/object:Gem::Dependency
70
56
  name: rake
71
57
  requirement: !ruby/object:Gem::Requirement
72
58
  requirements:
73
59
  - - "~>"
74
60
  - !ruby/object:Gem::Version
75
- version: '10.0'
61
+ version: '12.3'
62
+ - - ">="
63
+ - !ruby/object:Gem::Version
64
+ version: 12.3.3
76
65
  type: :development
77
66
  prerelease: false
78
67
  version_requirements: !ruby/object:Gem::Requirement
79
68
  requirements:
80
69
  - - "~>"
81
70
  - !ruby/object:Gem::Version
82
- version: '10.0'
71
+ version: '12.3'
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: 12.3.3
83
75
  - !ruby/object:Gem::Dependency
84
76
  name: rspec
85
77
  requirement: !ruby/object:Gem::Requirement
@@ -146,12 +138,14 @@ executables:
146
138
  extensions: []
147
139
  extra_rdoc_files: []
148
140
  files:
141
+ - ".github/workflows/release_gem.yml"
142
+ - ".github/workflows/test.yml"
149
143
  - ".gitignore"
150
144
  - ".rspec"
151
- - ".travis.yml"
152
145
  - CHANGELOG.md
153
146
  - CONTRIBUTING.md
154
147
  - DEVELOPER_DOCUMENTATION.md
148
+ - Dockerfile-bundle-base
155
149
  - Gemfile
156
150
  - LICENSE.txt
157
151
  - QUESTIONS.md
@@ -177,13 +171,21 @@ files:
177
171
  - lib/pact/message/consumer/interaction_decorator.rb
178
172
  - lib/pact/message/consumer/rspec.rb
179
173
  - lib/pact/message/consumer/spec_hooks.rb
180
- - lib/pact/message/consumer/update_pact.rb
174
+ - lib/pact/message/consumer/world.rb
175
+ - lib/pact/message/consumer/write_pact.rb
181
176
  - lib/pact/message/consumer_contract_parser.rb
182
177
  - lib/pact/message/version.rb
178
+ - lib/pact/pact-message.rb
183
179
  - pact-message.gemspec
180
+ - script/docker-functions
181
+ - script/functions
184
182
  - script/release.sh
183
+ - script/release/bump-version.sh
184
+ - script/release/generate-changelog.sh
185
+ - script/trigger-release.sh
185
186
  - script/update-pact.sh
186
187
  - tasks/release.rake
188
+ - tasks/test.rake
187
189
  homepage: http://pact.io
188
190
  licenses:
189
191
  - MIT
@@ -204,8 +206,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
204
206
  - !ruby/object:Gem::Version
205
207
  version: '0'
206
208
  requirements: []
207
- rubyforge_project:
208
- rubygems_version: 2.6.11
209
+ rubygems_version: 3.2.6
209
210
  signing_key:
210
211
  specification_version: 4
211
212
  summary: Consumer contract library for messages
@@ -1,12 +0,0 @@
1
- sudo: false
2
- language: ruby
3
- rvm:
4
- - 2.4.1
5
- before_install: gem install bundler -v 1.15.4
6
- notifications:
7
- webhooks:
8
- urls:
9
- - https://webhooks.gitter.im/e/6523128341fad111ed79
10
- on_success: change
11
- on_failure: always
12
- on_start: never