pact-message 0.6.0 → 0.11.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: 26c0cea21dd41b6f907a255478162aaa8cd51cca
4
- data.tar.gz: b62a0f0fb8e0d13abbd996ca86b9ae826ae032b2
2
+ SHA256:
3
+ metadata.gz: f26e9b2d322f3bb375df2197aa2180f8de0b4d62e45decf77e4c4b3802505ffa
4
+ data.tar.gz: 130523097c7c65ddc0bf9d9dbf4464d7d17dfb7de97c7ef97471535be265e382
5
5
  SHA512:
6
- metadata.gz: f816074ffebe0ea7d9a89c21bb971ddee873e67b9a8f872e2077e64ac9e610831c4578680a057e62b719b8de64016c6c76932a377d09e0221615bb3588d069f4
7
- data.tar.gz: d64e741d000004653b13b760ad3be4a2cb49a3bc64b45b45d50cf04e9f28b158d4c06cd808206b9729f183870097ef4eba45e7c2f4d049394791eeb9c92f3957
6
+ metadata.gz: 184da6d526714237d5ec94c7a27b5c8e8562a91023cd6ede596e6a7aa8adf34ba1249650a01f4f50bcdcbd78fa529799cfd04922a00a51f90f832477f7cd35ff
7
+ data.tar.gz: 8b24b7de4e4d24c41e4520d5ba0008d3ca719f7be4c17fa7c0d70d7bf4ea0fcb32e1910fcb37b543b4040d16e02f9ac672e478dfe06f6961bdbff86a67b7378f
@@ -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, pull_request]
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/
data/CHANGELOG.md CHANGED
@@ -1,3 +1,69 @@
1
+ <a name="v0.11.0"></a>
2
+ ### v0.11.0 (2021-03-22)
3
+
4
+ #### Features
5
+
6
+ * update thor dependancy ([87a5f64](/../../commit/87a5f64))
7
+
8
+ <a name="v0.10.0"></a>
9
+ ### v0.10.0 (2021-01-22)
10
+
11
+ #### Features
12
+
13
+ * allow pact-message update to receive JSON via the standard input ([5cbb664](/../../commit/5cbb664))
14
+
15
+ <a name="v0.9.0"></a>
16
+ ### v0.9.0 (2020-11-04)
17
+
18
+ #### Features
19
+
20
+ * allow pact dir to be configured ([f2f9626](/../../commit/f2f9626))
21
+ * verify that each message has been yielded ([1d4d92c](/../../commit/1d4d92c))
22
+
23
+ * **consumer**
24
+ * only update pact if test suite passes ([e99276d](/../../commit/e99276d))
25
+
26
+ <a name="v0.8.0"></a>
27
+ ### v0.8.0 (2020-09-28)
28
+
29
+ #### Features
30
+
31
+ * reify message when yielding ([d7c0a4a](/../../commit/d7c0a4a))
32
+
33
+ #### Bug Fixes
34
+
35
+ * fix bug in Message.to_hash ([e354cd2](/../../commit/e354cd2))
36
+
37
+ <a name="v0.7.0"></a>
38
+ ### v0.7.0 (2020-02-10)
39
+
40
+
41
+ #### Features
42
+
43
+ * Add metadata to a message request ([796590f](/../../commit/796590f))
44
+ * support the _id attribute from the Pact Broker, and give each message an index ([3a05501](/../../commit/3a05501))
45
+
46
+
47
+ #### Bug Fixes
48
+
49
+ * add back support for using providerState instead of providerStates when updating a pact ([b494a76](/../../commit/b494a76))
50
+
51
+
52
+ <a name="v0.6.0"></a>
53
+ ### v0.6.0 (2020-02-10)
54
+
55
+
56
+ #### Features
57
+
58
+ * Add metadata to a message request ([796590f](/../../commit/796590f))
59
+ * support the _id attribute from the Pact Broker, and give each message an index ([3a05501](/../../commit/3a05501))
60
+
61
+
62
+ #### Bug Fixes
63
+
64
+ * add back support for using providerState instead of providerStates when updating a pact ([b494a76](/../../commit/b494a76))
65
+
66
+
1
67
  <a name="v0.5.0"></a>
2
68
  ### v0.5.0 (2018-10-04)
3
69
 
@@ -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,15 +21,129 @@ 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.
27
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
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
 
31
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
97
+ ### Setup
98
+
99
+ After checking out the repo, run the following to install dependencies.
100
+
101
+ ```bash
102
+ $ bundle exec bin/setup
103
+
104
+ bundle install
105
+ \+ bundle install
106
+ ...
107
+ Bundle complete! 6 Gemfile dependencies, 29 gems now installed.
108
+ Use `bundle info [gemname]` to see where a bundled gem is installed.
109
+
110
+ Do any other automated setup that you need to do here
111
+ ```
112
+
113
+ ### Tests
114
+
115
+ Run the following command to run the tests.
116
+
117
+ ```bash
118
+ $ bundle exec rake spec
119
+
120
+ the CLI
121
+ creates a pact file with the given message
122
+ creates a pact file with a message from the standard input
123
+ ...
124
+ Finished in 0.50883 seconds (files took 0.15053 seconds to load)
125
+ 26 examples, 0 failures, 2 pending
126
+ ```
127
+
128
+ ### Interactive Prompt
129
+
130
+ You can run the following command for an for an interactive prompt that will allow you to experiment.
131
+
132
+ ```bash
133
+ $ bundle exec bin/console
134
+ 2.6.6 :001 >
135
+ ```
136
+
137
+ To execute commands on the CLI run the following command followed by command line arguments as you would with the published version.
138
+
139
+ ```bash
140
+ $ bundle exec bin/pact-message
141
+ Commands:
142
+ pact-message help [COMMAND] # Describe available commands or one specific command
143
+ pact-message reify # Take a JSON document with embedded pact matchers and return...
144
+ pact-message update MESSAGE_JSON --consumer=CONSUMER --pact-dir=PACT_DIR --provider=PROVIDER # Update/create a pact. If MESSAGE_JSON is omitted or '-', it...
145
+ pact-message version # Show the pact-message gem version
146
+ ```
32
147
 
33
148
  To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
34
149
 
@@ -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.6.0"
3
+ VERSION = "0.11.0"
4
4
  end
5
5
  end
@@ -0,0 +1 @@
1
+ require 'pact/message'
data/pact-message.gemspec CHANGED
@@ -34,10 +34,9 @@ Gem::Specification.new do |spec|
34
34
  # and Pact::ConsumerContractWriter. Potentially we should extract
35
35
  # or duplicate these classes to remove the pact-mock_service dependency.
36
36
  spec.add_runtime_dependency "pact-mock_service", "~> 3.1"
37
- spec.add_runtime_dependency "thor", "~> 0.20"
37
+ spec.add_runtime_dependency "thor", '>= 0.20', '< 2.0'
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
+ }
data/script/functions ADDED
@@ -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
+ }
data/script/release.sh CHANGED
@@ -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"
data/tasks/test.rake ADDED
@@ -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.6.0
4
+ version: 0.11.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: 2019-05-22 00:00:00.000000000 Z
11
+ date: 2021-03-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: pact-support
@@ -42,44 +42,42 @@ dependencies:
42
42
  name: thor
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - "~>"
45
+ - - ">="
46
46
  - !ruby/object:Gem::Version
47
47
  version: '0.20'
48
+ - - "<"
49
+ - !ruby/object:Gem::Version
50
+ version: '2.0'
48
51
  type: :runtime
49
52
  prerelease: false
50
53
  version_requirements: !ruby/object:Gem::Requirement
51
54
  requirements:
52
- - - "~>"
55
+ - - ">="
53
56
  - !ruby/object:Gem::Version
54
57
  version: '0.20'
55
- - !ruby/object:Gem::Dependency
56
- name: bundler
57
- requirement: !ruby/object:Gem::Requirement
58
- requirements:
59
- - - "~>"
58
+ - - "<"
60
59
  - !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'
60
+ version: '2.0'
69
61
  - !ruby/object:Gem::Dependency
70
62
  name: rake
71
63
  requirement: !ruby/object:Gem::Requirement
72
64
  requirements:
73
65
  - - "~>"
74
66
  - !ruby/object:Gem::Version
75
- version: '10.0'
67
+ version: '12.3'
68
+ - - ">="
69
+ - !ruby/object:Gem::Version
70
+ version: 12.3.3
76
71
  type: :development
77
72
  prerelease: false
78
73
  version_requirements: !ruby/object:Gem::Requirement
79
74
  requirements:
80
75
  - - "~>"
81
76
  - !ruby/object:Gem::Version
82
- version: '10.0'
77
+ version: '12.3'
78
+ - - ">="
79
+ - !ruby/object:Gem::Version
80
+ version: 12.3.3
83
81
  - !ruby/object:Gem::Dependency
84
82
  name: rspec
85
83
  requirement: !ruby/object:Gem::Requirement
@@ -146,12 +144,14 @@ executables:
146
144
  extensions: []
147
145
  extra_rdoc_files: []
148
146
  files:
147
+ - ".github/workflows/release_gem.yml"
148
+ - ".github/workflows/test.yml"
149
149
  - ".gitignore"
150
150
  - ".rspec"
151
- - ".travis.yml"
152
151
  - CHANGELOG.md
153
152
  - CONTRIBUTING.md
154
153
  - DEVELOPER_DOCUMENTATION.md
154
+ - Dockerfile-bundle-base
155
155
  - Gemfile
156
156
  - LICENSE.txt
157
157
  - QUESTIONS.md
@@ -177,13 +177,21 @@ files:
177
177
  - lib/pact/message/consumer/interaction_decorator.rb
178
178
  - lib/pact/message/consumer/rspec.rb
179
179
  - lib/pact/message/consumer/spec_hooks.rb
180
- - lib/pact/message/consumer/update_pact.rb
180
+ - lib/pact/message/consumer/world.rb
181
+ - lib/pact/message/consumer/write_pact.rb
181
182
  - lib/pact/message/consumer_contract_parser.rb
182
183
  - lib/pact/message/version.rb
184
+ - lib/pact/pact-message.rb
183
185
  - pact-message.gemspec
186
+ - script/docker-functions
187
+ - script/functions
184
188
  - script/release.sh
189
+ - script/release/bump-version.sh
190
+ - script/release/generate-changelog.sh
191
+ - script/trigger-release.sh
185
192
  - script/update-pact.sh
186
193
  - tasks/release.rake
194
+ - tasks/test.rake
187
195
  homepage: http://pact.io
188
196
  licenses:
189
197
  - MIT
@@ -204,8 +212,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
204
212
  - !ruby/object:Gem::Version
205
213
  version: '0'
206
214
  requirements: []
207
- rubyforge_project:
208
- rubygems_version: 2.6.11
215
+ rubygems_version: 3.2.15
209
216
  signing_key:
210
217
  specification_version: 4
211
218
  summary: Consumer contract library for messages
data/.travis.yml DELETED
@@ -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