pact-message 0.4.5 → 0.9.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 2db11376814a1b7522831243a0f50d51aa70837f
4
- data.tar.gz: dfe86bc1cdf37c3a7a0db45f65e72befa0f0bc2a
2
+ SHA256:
3
+ metadata.gz: 9e2884875c85c338522d2113d74c7e742309cdc864180a6c7270bf3f3d31a175
4
+ data.tar.gz: 786c95da06a2c7b44a5eb17c18c7f45b1c52fdbe0cab4d6732ab57d8d49b0954
5
5
  SHA512:
6
- metadata.gz: d1dd0d8cdd38e3030a7625ad5e5d129f970e99e29f1156915aa01aa9bd7d76af4322665b23b27cec9c33cf22b0ffa21d0d5a8dd902e342583163e953745ffddf
7
- data.tar.gz: 34f4a05ce4ae291b77ae456cda1a661d1cb28a8d32afeca9596e083d96679c12d9dbac8fd1aeb7535047619ed07c17fe4e079a96c00c00edbfd0ec0c38d64870
6
+ metadata.gz: ad49fca208a2fef4107862750d4349ba890354ecf220c497fc3fa192f1d78cd7dee3abba59770023682734e9d5236932d74faf488fe372637181aad81c569550
7
+ data.tar.gz: 4b96ecd3a271135ede66c3d2f492030e4daa045362a8db507b236e6bad0e4e54aaaff064703dcbdf1970d7e73cb0673b45c0f8f49895726e6aee9bc3f58ae63e
@@ -0,0 +1,58 @@
1
+ name: Release gem
2
+
3
+ on:
4
+ repository_dispatch:
5
+ types:
6
+ - release-triggered
7
+
8
+ jobs:
9
+ test:
10
+ runs-on: ubuntu-latest
11
+ steps:
12
+ - uses: actions/checkout@v2
13
+ - uses: actions/setup-ruby@v1
14
+ with:
15
+ ruby-version: '2.6'
16
+ - run: |
17
+ gem install bundler -v 2.1
18
+ bundle install
19
+ - name: Test
20
+ run: bundle exec rake
21
+
22
+ release:
23
+ needs: test
24
+ runs-on: ubuntu-latest
25
+ outputs:
26
+ gem_name: ${{ steps.release-gem.outputs.gem_name }}
27
+ version: ${{ steps.release-gem.outputs.version }}
28
+ increment: ${{ steps.release-gem.outputs.increment }}
29
+ steps:
30
+ - uses: actions/checkout@v2
31
+ with:
32
+ fetch-depth: 0
33
+ - uses: pact-foundation/release-gem@v0.0.11
34
+ id: release-gem
35
+ env:
36
+ GEM_HOST_API_KEY: '${{ secrets.RUBYGEMS_API_KEY }}'
37
+ GITHUB_TOKEN: '${{ secrets.GITHUB_TOKEN }}'
38
+ INCREMENT: '${{ github.event.client_payload.increment }}'
39
+
40
+ notify-gem-released:
41
+ needs: release
42
+ strategy:
43
+ matrix:
44
+ repository: [pact-foundation/pact-ruby-standalone]
45
+ runs-on: ubuntu-latest
46
+ steps:
47
+ - name: Notify ${{ matrix.repository }} of gem release
48
+ uses: peter-evans/repository-dispatch@v1
49
+ with:
50
+ token: ${{ secrets.GHTOKENFORPACTCLIRELEASE }}
51
+ repository: ${{ matrix.repository }}
52
+ event-type: gem-released
53
+ client-payload: |
54
+ {
55
+ "name": "${{ needs.release.outputs.gem_name }}",
56
+ "version": "${{ needs.release.outputs.version }}",
57
+ "increment": "${{ needs.release.outputs.increment }}"
58
+ }
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,12 +1,33 @@
1
1
  sudo: false
2
2
  language: ruby
3
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
4
+ - 2.2.4
5
+ before_install:
6
+ - rvm use @global
7
+ - gem uninstall bundler -x || gem uninstall bundler -a || true
8
+ - gem install bundler -v 1.17.3
9
+ - bundler --version
10
+ script:
11
+ - rvm use @global
12
+ - bundle exec rake
13
+ jobs:
14
+ # include:
15
+ # - stage: gem release
16
+ # rvm: 2.2.4
17
+ # script: echo "Deploying to rubygems.org ..."
18
+ # deploy:
19
+ # provider: rubygems
20
+ # api_key:
21
+ # secure: NEA7BYENheSN8qF/6BP52uQjTS5U43MXsyxBeqxbp1JOkJxVSNzQw14xy41aXX0gphT7wEVHinnWS+1slLKXvu4OzGKKzcUsnekYFZoGW7eTyKUx7lh/XtFejQ/Mm4P5t75GBgMoaIi+Pa1rD4fcE7zYGrgCvTwIrOGb/SPIKILj0yT8UXMFod8yDDmxzivSSKYe4rgWYlq8aiidDZr2M5ypBR4WcOptCrkBCF8XxXzhFMY4QtrXLsLFRyCzCrDHmosCfC/bLJQltlJjLXfB5ksgaImWAD7wZ6Q4uC5QqmPShonQiPlLEh53Q5nkEWPIcsV7FVZqzXUjPN3LYHlRv+7D3AvbHmJggSt7fXr8YxbzVUkviBlKqNmc9cqM6CSO++QT3UShNgH5b03YKI8rRjFMWYKn1DrN5F5rFNDoGFcZtQSjFN5g/fEiSYsdkNsIeTp4YFxMkTztAYT8TxgcBvCnfXox6xDaLaPWh13UrUL2VL7O7uDK06xWUCp9Hm3/AXz0wRzya1tK9dCWamE5BOzk2ScOiLOgmpgwNHFVA1U93rkHq7Ixr11wazP3Dcinv0kWcW7hdMcI7VA0DUesxLKw6mkcQpd3NLgSU4mWtpoVFcmdERQUGsNM1d5NjGjBeyVMpC0I9NXM1Wv6cLENSX9b4GR7lkwGG/IHRFNciHk=
22
+ # gem: pact-message
23
+ # on:
24
+ # tags: true
25
+ # repo: pact-foundation/pact-message-ruby
26
+ # notifications:
27
+ # webhooks:
28
+ # urls:
29
+ # - https://webhooks.gitter.im/e/6523128341fad111ed79
30
+ # on_success: change
31
+ # on_failure: always
32
+ # on_start: never
33
+
@@ -1,3 +1,65 @@
1
+ <a name="v0.9.0"></a>
2
+ ### v0.9.0 (2020-11-04)
3
+
4
+ #### Features
5
+
6
+ * allow pact dir to be configured ([f2f9626](/../../commit/f2f9626))
7
+ * verify that each message has been yielded ([1d4d92c](/../../commit/1d4d92c))
8
+
9
+ * **consumer**
10
+ * only update pact if test suite passes ([e99276d](/../../commit/e99276d))
11
+
12
+ <a name="v0.8.0"></a>
13
+ ### v0.8.0 (2020-09-28)
14
+
15
+ #### Features
16
+
17
+ * reify message when yielding ([d7c0a4a](/../../commit/d7c0a4a))
18
+
19
+ #### Bug Fixes
20
+
21
+ * fix bug in Message.to_hash ([e354cd2](/../../commit/e354cd2))
22
+
23
+ <a name="v0.7.0"></a>
24
+ ### v0.7.0 (2020-02-10)
25
+
26
+
27
+ #### Features
28
+
29
+ * Add metadata to a message request ([796590f](/../../commit/796590f))
30
+ * support the _id attribute from the Pact Broker, and give each message an index ([3a05501](/../../commit/3a05501))
31
+
32
+
33
+ #### Bug Fixes
34
+
35
+ * add back support for using providerState instead of providerStates when updating a pact ([b494a76](/../../commit/b494a76))
36
+
37
+
38
+ <a name="v0.6.0"></a>
39
+ ### v0.6.0 (2020-02-10)
40
+
41
+
42
+ #### Features
43
+
44
+ * Add metadata to a message request ([796590f](/../../commit/796590f))
45
+ * support the _id attribute from the Pact Broker, and give each message an index ([3a05501](/../../commit/3a05501))
46
+
47
+
48
+ #### Bug Fixes
49
+
50
+ * add back support for using providerState instead of providerStates when updating a pact ([b494a76](/../../commit/b494a76))
51
+
52
+
53
+ <a name="v0.5.0"></a>
54
+ ### v0.5.0 (2018-10-04)
55
+
56
+
57
+ #### Features
58
+
59
+ * **pact specification v3**
60
+ * add support for multiple provider states and params ([b2c1cc7](/../../commit/b2c1cc7))
61
+
62
+
1
63
  <a name="v0.4.5"></a>
2
64
  ### v0.4.5 (2018-07-07)
3
65
 
@@ -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,13 +13,16 @@ module Pact
13
13
  include Pact::ActiveSupportSupport
14
14
  include Pact::SymbolizeKeys
15
15
 
16
- attr_accessor :description, :contents, :provider_state, :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]
20
20
  @provider_state = attributes[:provider_state] || attributes[:providerState]
21
+ @provider_states = attributes[:provider_states] || []
21
22
  @contents = attributes[:contents]
22
23
  @metadata = attributes[:metadata]
24
+ @_id = attributes[:_id]
25
+ @index = attributes[:index]
23
26
  end
24
27
 
25
28
  def self.from_hash hash, options = {}
@@ -31,17 +34,21 @@ module Pact
31
34
  contents_hash = Pact::MatchingRules.merge(hash['contents'], contents_matching_rules, opts)
32
35
  contents = Pact::ConsumerContract::Message::Contents.from_hash(contents_hash)
33
36
  metadata = hash['metaData'] || hash['metadata']
34
- provider_state = hash['providerStates'] && hash['providerStates'].first && hash['providerStates'].first['name']
35
- warn_if_multiple_provider_states(provider_state, hash)
36
- warn_if_params_used_in_provider_states(hash)
37
- new(symbolize_keys(hash).merge(contents: contents, provider_state: provider_state, metadata: metadata))
37
+
38
+ provider_state_name = parse_provider_state_name(hash['providerState'], hash['providerStates'])
39
+ provider_states = parse_provider_states(provider_state_name, hash['providerStates'])
40
+ new(symbolize_keys(hash).merge(
41
+ contents: contents,
42
+ provider_state: provider_state_name,
43
+ provider_states: provider_states,
44
+ metadata: metadata))
38
45
  end
39
46
 
40
47
  def to_hash
41
48
  {
42
49
  description: description,
43
50
  provider_states: [{ name: provider_state }],
44
- contents: contents.to_hash,
51
+ contents: contents.contents,
45
52
  metadata: metadata
46
53
  }
47
54
  end
@@ -57,7 +64,8 @@ module Pact
57
64
  providerStates: [{
58
65
  name: provider_state,
59
66
  params: {}
60
- }]
67
+ }],
68
+ metadata: metadata
61
69
  }
62
70
  )
63
71
  end
@@ -116,19 +124,19 @@ module Pact
116
124
 
117
125
  private
118
126
 
119
- def self.warn_if_multiple_provider_states(provider_state, hash)
120
- if hash['providerStates'] && hash['providerStates'].size > 1
121
- ignored_list = hash['providerStates'].collect{ |provider_state| "\"#{provider_state['name']}\"" }[1..-1].join(", ")
122
- Pact.configuration.error_stream.puts("WARN: Using only the first provider state, \"#{provider_state}\", as support for multiple provider states is not yet implemented. Ignoring provider states: #{ignored_list}")
123
- end
127
+ def self.parse_provider_state_name provider_state, provider_states
128
+ (provider_states && provider_states.first && provider_states.first['name']) || provider_state
124
129
  end
125
130
 
126
- def self.warn_if_params_used_in_provider_states(hash)
127
- return unless hash['providerStates']
128
- provider_states_with_params = hash['providerStates'].select{ | provider_state | provider_state.fetch('params', {}).any? }
129
- if provider_states_with_params.any?
130
- ignored_list = provider_states_with_params.collect{ |provider_state| "\"#{provider_state['name']}\"" }.join(", ")
131
- Pact.configuration.error_stream.puts("WARN: Ignoring params for the following provider states as params support is not yet implemented: #{ignored_list}")
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
+ []
132
140
  end
133
141
  end
134
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
@@ -12,10 +12,10 @@ module Pact
12
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
13
  def update(message)
14
14
  require 'pact/message'
15
- require 'pact/message/consumer/update_pact'
15
+ require 'pact/message/consumer/write_pact'
16
16
  pact_specification_version = Pact::SpecificationVersion.new(options.pact_specification_version)
17
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)
18
+ Pact::Message::Consumer::WritePact.call(message, options.pact_dir, options.consumer, options.provider, options.pact_specification_version, :update)
19
19
  end
20
20
 
21
21
  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
 
@@ -14,10 +14,12 @@ module Pact
14
14
 
15
15
  def as_json options = {}
16
16
  hash = { :description => message.description }
17
- hash[:providerStates] = provider_states if message.provider_state
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
 
@@ -38,13 +40,18 @@ module Pact
38
40
  end
39
41
 
40
42
  def provider_states
41
- [{ name: message.provider_state }]
43
+ message.provider_states.collect(&:as_json)
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
@@ -1,6 +1,7 @@
1
1
  require 'pact/consumer_contract'
2
2
  require 'pact/consumer_contract/message'
3
3
  require 'pact/consumer_contract/message/contents'
4
+ require 'pact/consumer_contract/provider_state'
4
5
 
5
6
  module Pact
6
7
  module Message
@@ -10,7 +11,9 @@ module Pact
10
11
  def call(hash)
11
12
  hash = symbolize_keys(hash)
12
13
  options = { pact_specification_version: pact_specification_version(hash) }
13
- 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
14
17
  ConsumerContract.new(
15
18
  :consumer => ServiceConsumer.from_hash(hash[:consumer]),
16
19
  :provider => ServiceProvider.from_hash(hash[:provider]),
@@ -1,5 +1,5 @@
1
1
  module Pact
2
2
  module Message
3
- VERSION = "0.4.5"
3
+ VERSION = "0.9.0"
4
4
  end
5
5
  end
@@ -0,0 +1 @@
1
+ require 'pact/message'
@@ -29,15 +29,14 @@ Gem::Specification.new do |spec|
29
29
  spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
30
30
  spec.require_paths = ["lib"]
31
31
 
32
- spec.add_runtime_dependency "pact-support", "~> 1.6"
32
+ spec.add_runtime_dependency "pact-support", "~> 1.8"
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 $(git remote show) | cut -d':' -f2 | sed 's/\.git//')
16
+
17
+ output=$(curl -v -X POST 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\/1.1 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.4.5
4
+ version: 0.9.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-07-06 00:00:00.000000000 Z
11
+ date: 2020-11-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: pact-support
@@ -16,28 +16,28 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '1.6'
19
+ version: '1.8'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '1.6'
26
+ version: '1.8'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: pact-mock_service
29
29
  requirement: !ruby/object:Gem::Requirement
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"
149
142
  - ".gitignore"
150
143
  - ".rspec"
151
144
  - ".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.1.4
209
210
  signing_key:
210
211
  specification_version: 4
211
212
  summary: Consumer contract library for messages