pact 1.26.0 → 1.27.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
- SHA256:
3
- metadata.gz: cb658bf653b3a96004e3e9907a879b94ac723796ce3dc0d3eb49bca8cf9d9dae
4
- data.tar.gz: f700d052cfbe66ed2a53ef4be267b96994ce940ea4683a7da22fd46a24133384
2
+ SHA1:
3
+ metadata.gz: 863f88ef054fb71549db6134a969beaf7ded3f35
4
+ data.tar.gz: 444f03ebae284db7197842e7be35008a976d5779
5
5
  SHA512:
6
- metadata.gz: 0f01bb647d4065caff16740a52ae2b9141c4aa0c0220a7afcac4bba29958e48ebe1e282ed2fe3ff8027d7a66a4d503fdf88e54b4c955f169454e7090ea5935fe
7
- data.tar.gz: cc372af0a41fea7417382d2f3d5671093b3c6aced8afc00d8c52e55ea02206241233b8b157f9beecffa4fe2318ab793cf3ac5a3e8f8a2e149f44b484946b6402
6
+ metadata.gz: 248b0ed165715ea29e49f74ed98990020908b5f56b08b22bb2b9744c4e0e18066fabb064629883d95bc462e4e2239032a17528a2172e0502efe54ec788cc3f3d
7
+ data.tar.gz: 848fb4bb48b346947d787843a0b84613063bfb1d2bfeac6f575a3d35a14b9db8ac5bf398f16bf73c478f4764df11feb3bf7451f9fe4b1e25ece2f18b708d4c1a
@@ -1,3 +1,19 @@
1
+ <a name="v1.27.0"></a>
2
+ ### v1.27.0 (2018-06-22)
3
+
4
+
5
+ #### Features
6
+
7
+ * log tagging to stdout when publishing verification results ([2387424](/../../commit/2387424))
8
+ * Dynamically retrieve pacts for a given provider ([5aca966](/../../commit/5aca966))
9
+
10
+
11
+ #### Bug Fixes
12
+
13
+ * correct request url in http client ([1fb22b6](/../../commit/1fb22b6))
14
+ * correctly escape expanded URL in the HAL client ([821238d](/../../commit/821238d))
15
+
16
+
1
17
  <a name="v1.26.0"></a>
2
18
  ### v1.26.0 (2018-05-08)
3
19
 
@@ -0,0 +1,76 @@
1
+ require 'uri'
2
+ require 'delegate'
3
+ require 'pact/hal/link'
4
+
5
+ module Pact
6
+ module Hal
7
+ class Entity
8
+ def initialize(data, http_client, response = nil)
9
+ @data = data
10
+ @links = (@data || {}).fetch("_links", {})
11
+ @client = http_client
12
+ @response = response
13
+ end
14
+
15
+ def get(key, *args)
16
+ _link(key).get(*args)
17
+ end
18
+
19
+ def post(key, *args)
20
+ _link(key).post(*args)
21
+ end
22
+
23
+ def put(key, *args)
24
+ _link(key).put(*args)
25
+ end
26
+
27
+ def can?(key)
28
+ @links.key? key.to_s
29
+ end
30
+
31
+ def follow(key, http_method, *args)
32
+ Link.new(@links[key].merge(method: http_method), @client).run(*args)
33
+ end
34
+
35
+ def _link(key)
36
+ if @links[key]
37
+ Link.new(@links[key], @client)
38
+ else
39
+ nil
40
+ end
41
+ end
42
+
43
+ def success?
44
+ true
45
+ end
46
+
47
+ def response
48
+ @response
49
+ end
50
+
51
+ def fetch(key)
52
+ @links[key]
53
+ end
54
+
55
+ def method_missing(method_name, *args, &block)
56
+ if @data.key?(method_name.to_s)
57
+ @data[method_name.to_s]
58
+ elsif @links.key?(method_name)
59
+ Link.new(@links[method_name], @client).run(*args)
60
+ else
61
+ super
62
+ end
63
+ end
64
+
65
+ def respond_to_missing?(method_name, include_private = false)
66
+ @data.key?(method_name) || @links.key?(method_name)
67
+ end
68
+ end
69
+
70
+ class ErrorEntity < Entity
71
+ def success?
72
+ false
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,69 @@
1
+ require 'pact/retry'
2
+
3
+ module Pact
4
+ module Hal
5
+ class HttpClient
6
+ attr_accessor :username, :password
7
+
8
+ def initialize options
9
+ @username = options[:username]
10
+ @password = options[:password]
11
+ end
12
+
13
+ def get href, params = {}, headers = {}
14
+ query = params.collect{ |(key, value)| "#{CGI::escape(key)}=#{CGI::escape(value)}" }.join("&")
15
+ uri = URI(href)
16
+ uri.query = query
17
+ perform_request(create_request(uri, 'Get', nil, headers), uri)
18
+ end
19
+
20
+ def put href, body = nil, headers = {}
21
+ uri = URI(href)
22
+ perform_request(create_request(uri, 'Put', body, headers), uri)
23
+ end
24
+
25
+ def post href, body = nil, headers = {}
26
+ uri = URI(href)
27
+ perform_request(create_request(uri, 'Post', body, headers), uri)
28
+ end
29
+
30
+ def create_request uri, http_method, body = nil, headers = {}
31
+ request = Net::HTTP.const_get(http_method).new(uri.request_uri)
32
+ request['Content-Type'] = "application/json" if ['Post', 'Put', 'Patch'].include?(http_method)
33
+ request['Accept'] = "application/hal+json"
34
+ headers.each do | key, value |
35
+ request[key] = value
36
+ end
37
+
38
+ request.body = body if body
39
+ request.basic_auth username, password if username
40
+ request
41
+ end
42
+
43
+ def perform_request request, uri
44
+ options = {:use_ssl => uri.scheme == 'https'}
45
+ response = Retry.until_true do
46
+ Net::HTTP.start(uri.host, uri.port, :ENV, options) do |http|
47
+ http.request request
48
+ end
49
+ end
50
+ Response.new(response)
51
+ end
52
+
53
+ class Response < SimpleDelegator
54
+ def body
55
+ bod = __getobj__().body
56
+ if bod && bod != ''
57
+ JSON.parse(bod)
58
+ else
59
+ nil
60
+ end
61
+ end
62
+
63
+ def success?
64
+ __getobj__().code.start_with?("2")
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,65 @@
1
+ require 'uri'
2
+ require 'delegate'
3
+
4
+ module Pact
5
+ module Hal
6
+ class Link
7
+ attr_reader :request_method, :href
8
+
9
+ def initialize(attrs, http_client)
10
+ @attrs = attrs
11
+ @request_method = attrs.fetch(:method, :get).to_sym
12
+ @href = attrs.fetch('href')
13
+ @http_client = http_client
14
+ end
15
+
16
+ def run(payload = nil)
17
+ response = case request_method
18
+ when :get
19
+ get(payload)
20
+ when :put
21
+ put(payload)
22
+ when :post
23
+ post(payload)
24
+ end
25
+ end
26
+
27
+ def get(payload = {}, headers = {})
28
+ wrap_response(@http_client.get(href, payload, headers))
29
+ end
30
+
31
+ def put(payload = nil, headers = {})
32
+ wrap_response(@http_client.put(href, payload ? JSON.dump(payload) : nil, headers))
33
+ end
34
+
35
+ def post(payload = nil, headers = {})
36
+ wrap_response(@http_client.post(href, payload ? JSON.dump(payload) : nil, headers))
37
+ end
38
+
39
+ def expand(params)
40
+ expanded_url = expand_url(params, href)
41
+ new_attrs = @attrs.merge('href' => expanded_url)
42
+ Link.new(new_attrs, @http_client)
43
+ end
44
+
45
+ private
46
+
47
+ def wrap_response(http_response)
48
+ require 'pact/hal/entity' # avoid circular reference
49
+ if http_response.success?
50
+ Entity.new(http_response.body, @http_client, http_response)
51
+ else
52
+ ErrorEntity.new(http_response.body, @http_client, http_response)
53
+ end
54
+ end
55
+
56
+ def expand_url(params, url)
57
+ new_url = url
58
+ params.each do | key, value |
59
+ new_url = new_url.gsub('{' + key.to_s + '}', URI.escape(value))
60
+ end
61
+ new_url
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,89 @@
1
+ require 'pact/hal/entity'
2
+ require 'pact/hal/http_client'
3
+
4
+ module Pact
5
+ module PactBroker
6
+ class FetchPacts
7
+ attr_reader :provider, :tags, :broker_base_url, :basic_auth_options, :http_client, :index_entity, :all_pacts
8
+
9
+ ALL_PROVIDER_TAG_RELATION = 'pb:provider-pacts-with-tag'.freeze
10
+ LATEST_PROVIDER_TAG_RELATION = 'pb:latest-provider-pacts-with-tag'.freeze
11
+ ALL_PROVIDER_RELATION = 'pb:provider-pacts'.freeze
12
+ LATEST_PROVIDER_RELATION = 'pb:latest-provider-pacts'.freeze
13
+ PACTS = 'pacts'.freeze
14
+ HREF = 'href'.freeze
15
+
16
+ def initialize(provider, tags, broker_base_url, basic_auth_options, all_pacts)
17
+ @provider = provider
18
+ @tags = tags
19
+ @broker_base_url = broker_base_url
20
+ @http_client = Pact::Hal::HttpClient.new(basic_auth_options)
21
+ @all_pacts = all_pacts
22
+ end
23
+
24
+ def self.call(provider, tags, broker_base_url, basic_auth_options, all_pacts)
25
+ new(provider, tags, broker_base_url, basic_auth_options, all_pacts).call
26
+ end
27
+
28
+ def call
29
+ get_index
30
+ if tags && tags.any?
31
+ get_tagged_pacts_for_provider
32
+ else
33
+ get_pacts_for_provider
34
+ end
35
+ end
36
+
37
+ private
38
+
39
+ def get_pacts_for_provider
40
+ if all_pacts
41
+ get_all_pacts_for_provider
42
+ else
43
+ get_latest_pacts_for_provider
44
+ end
45
+ end
46
+
47
+ def get_tagged_pacts_for_provider
48
+ if all_pacts
49
+ get_all_tagged_pacts_for_provider
50
+ else
51
+ get_latest_tagged_pacts_for_provider
52
+ end
53
+ end
54
+
55
+ def get_index
56
+ response = http_client.get(broker_base_url)
57
+ @index_entity = Pact::Hal::Entity.new(response.body, http_client)
58
+ end
59
+
60
+ def get_latest_tagged_pacts_for_provider
61
+ link = index_entity._link(LATEST_PROVIDER_TAG_RELATION)
62
+ tags.collect do | tag |
63
+ get_pact_urls(link.expand(provider: provider, tag: tag).get)
64
+ end.flatten
65
+ end
66
+
67
+ def get_all_tagged_pacts_for_provider
68
+ link = index_entity._link(ALL_PROVIDER_TAG_RELATION)
69
+ tags.collect do |tag|
70
+ get_pact_urls(link.expand(provider: provider, tag: tag).get)
71
+ end.flatten
72
+ end
73
+
74
+ def get_latest_pacts_for_provider
75
+ link = index_entity._link(LATEST_PROVIDER_RELATION)
76
+ get_pact_urls(link.expand(provider: provider).get)
77
+ end
78
+
79
+ def get_all_pacts_for_provider
80
+ link = index_entity._link(ALL_PROVIDER_RELATION)
81
+ get_pact_urls(link.expand(provider: provider).get)
82
+ end
83
+
84
+ def get_pact_urls(link_by_provider)
85
+ link_by_provider.fetch(PACTS).collect{ |pact | pact[HREF] }
86
+ end
87
+ end
88
+ end
89
+ end
@@ -1,5 +1,6 @@
1
1
  require 'pact/provider/configuration/pact_verification'
2
2
  require 'pact/provider/configuration/service_provider_config'
3
+ require 'pact/errors'
3
4
 
4
5
  module Pact
5
6
 
@@ -1,6 +1,8 @@
1
1
  require 'json'
2
2
  require 'pact/errors'
3
3
  require 'pact/retry'
4
+ require 'pact/hal/entity'
5
+ require 'pact/hal/http_client'
4
6
 
5
7
  # TODO move this to the pact broker client
6
8
 
@@ -10,6 +12,11 @@ module Pact
10
12
  class PublicationError < Pact::Error; end
11
13
 
12
14
  class Publish
15
+
16
+ PUBLISH_RELATION = 'pb:publish-verification-results'.freeze
17
+ PROVIDER_RELATION = 'pb:provider'.freeze
18
+ VERSION_TAG_RELATION = 'pb:version-tag'.freeze
19
+
13
20
  def self.call pact_source, verification_result
14
21
  new(pact_source, verification_result).call
15
22
  end
@@ -17,6 +24,15 @@ module Pact
17
24
  def initialize pact_source, verification_result
18
25
  @pact_source = pact_source
19
26
  @verification_result = verification_result
27
+
28
+ http_client_options = {}
29
+ if pact_source.uri.basic_auth?
30
+ http_client_options[:username] = pact_source.uri.username
31
+ http_client_options[:password] = pact_source.uri.password
32
+ end
33
+
34
+ @http_client = Pact::Hal::HttpClient.new(http_client_options)
35
+ @pact_entity = Pact::Hal::Entity.new(pact_source.pact_hash, http_client)
20
36
  end
21
37
 
22
38
  def call
@@ -27,11 +43,12 @@ module Pact
27
43
  end
28
44
 
29
45
  private
46
+ attr_reader :pact_source, :verification_result, :pact_entity, :http_client
30
47
 
31
48
  def can_publish_verification_results?
32
49
  return false unless Pact.configuration.provider.publish_verification_results?
33
50
 
34
- if publication_url.nil?
51
+ if !pact_entity.can?(PUBLISH_RELATION)
35
52
  Pact.configuration.error_stream.puts "WARN: Cannot publish verification for #{consumer_name} as there is no link named pb:publish-verification-results in the pact JSON. If you are using a pact broker, please upgrade to version 2.0.0 or later."
36
53
  return false
37
54
  end
@@ -43,94 +60,60 @@ module Pact
43
60
  true
44
61
  end
45
62
 
46
- def publication_url
47
- @publication_url ||= pact_source.pact_hash.fetch('_links', {}).fetch('pb:publish-verification-results', {})['href']
48
- end
49
-
50
- def tag_url tag
51
- # This is so dodgey, need to use proper HAL
52
- if publication_url
53
- u = URI(publication_url)
54
- if match = publication_url.match(%r{/provider/([^/]+)})
55
- provider_name = match[1]
56
- base_url = "#{u.scheme}://#{u.host}:#{u.host == u.default_port ? '' : u.port}"
57
- provider_application_version = Pact.configuration.provider.application_version
58
- "#{base_url}/pacticipants/#{provider_name}/versions/#{provider_application_version}/tags/#{tag}"
59
- end
60
- end
63
+ def hacky_tag_url provider_entity
64
+ hacky_tag_url = provider_entity._link('self').href + "/versions/{version}/tags/{tag}"
65
+ Pact::Hal::Link.new('href' => hacky_tag_url)
61
66
  end
62
67
 
63
68
  def tag_versions_if_configured
64
69
  if Pact.configuration.provider.tags.any?
65
- tag_versions if tag_url('')
70
+ if pact_entity.can?(PROVIDER_RELATION)
71
+ tag_versions
72
+ else
73
+ Pact.configuration.error_stream.puts "WARN: Could not tag provider version as the pb:provider link cannot be found"
74
+ end
66
75
  end
67
76
  end
68
77
 
69
78
  def tag_versions
70
- Pact.configuration.provider.tags.each do | tag |
71
- uri = URI(tag_url(tag))
72
- request = build_request('Put', uri, nil, "Tagging provider version at")
73
- response = nil
74
- begin
75
- options = {:use_ssl => uri.scheme == 'https'}
76
- Retry.until_true do
77
- response = Net::HTTP.start(uri.host, uri.port, options) do |http|
78
- http.request request
79
- end
80
- end
81
- rescue StandardError => e
82
- error_message = "Failed to tag provider version due to: #{e.class} #{e.message}"
83
- raise PublicationError.new(error_message)
84
- end
79
+ provider_entity = pact_entity.get(PROVIDER_RELATION)
80
+ tag_link = provider_entity._link(VERSION_TAG_RELATION) || hacky_tag_url(provider_entity)
81
+ provider_application_version = Pact.configuration.provider.application_version
85
82
 
86
- unless response.code.start_with?("2")
87
- raise PublicationError.new("Error returned from tagging request #{response.code} #{response.body}")
83
+ Pact.configuration.provider.tags.each do | tag |
84
+ Pact.configuration.output_stream.puts "INFO: Tagging version #{provider_application_version} of #{provider_name} as #{tag.inspect}"
85
+ tag_entity = tag_link.expand(version: provider_application_version, tag: tag).put
86
+ unless tag_entity.success?
87
+ raise PublicationError.new("Error returned from tagging request #{tag_entity.response.code} #{tag_entity.response.body}")
88
88
  end
89
89
  end
90
90
  end
91
91
 
92
92
  def publish_verification_results
93
- uri = URI(publication_url)
94
- request = build_request('Post', uri, verification_result.to_json, "Publishing verification result #{verification_result} to")
95
- response = nil
93
+ verification_entity = nil
96
94
  begin
97
- options = {:use_ssl => uri.scheme == 'https'}
98
- Retry.until_true do
99
- response = Net::HTTP.start(uri.host, uri.port, options) do |http|
100
- http.request request
101
- end
102
- end
95
+ # The verifications resource didn't have the content_types_provided set correctly, so publishing fails if we don't have */*
96
+ verification_entity = pact_entity.post(PUBLISH_RELATION, verification_result, { "Accept" => "application/hal+json, */*" })
103
97
  rescue StandardError => e
104
- error_message = "Failed to publish verification results due to: #{e.class} #{e.message}"
98
+ error_message = "Failed to publish verification results due to: #{e.class} #{e.message} #{e.backtrace.join("\n")}"
105
99
  raise PublicationError.new(error_message)
106
100
  end
107
101
 
108
- if response.code.start_with?("2")
109
- new_resource_url = JSON.parse(response.body)['_links']['self']['href']
102
+ if verification_entity.success?
103
+ new_resource_url = verification_entity._link('self').href
110
104
  Pact.configuration.output_stream.puts "INFO: Verification results published to #{new_resource_url}"
111
105
  else
112
- raise PublicationError.new("Error returned from verification results publication #{response.code} #{response.body}")
106
+ raise PublicationError.new("Error returned from verification results publication #{verification_entity.response.code} #{verification_entity.response.body}")
113
107
  end
114
108
  end
115
109
 
116
- def build_request meth, uri, body, action
117
- request = Net::HTTP.const_get(meth).new(uri.path)
118
- request['Content-Type'] = "application/json"
119
- request.body = body if body
120
- debug_uri = uri
121
- if pact_source.uri.basic_auth?
122
- request.basic_auth pact_source.uri.username, pact_source.uri.password
123
- debug_uri = URI(uri.to_s).tap { |x| x.userinfo="#{pact_source.uri.username}:*****"}
124
- end
125
- Pact.configuration.output_stream.puts "INFO: #{action} #{debug_uri}"
126
- request
127
- end
128
-
129
110
  def consumer_name
130
111
  pact_source.pact_hash['consumer']['name']
131
112
  end
132
113
 
133
- attr_reader :pact_source, :verification_result
114
+ def provider_name
115
+ pact_source.pact_hash['provider']['name']
116
+ end
134
117
  end
135
118
  end
136
119
  end
@@ -20,12 +20,12 @@ module Pact
20
20
  !!provider_application_version
21
21
  end
22
22
 
23
- def to_json
23
+ def to_json(options = {})
24
24
  {
25
25
  success: success,
26
26
  providerApplicationVersion: provider_application_version,
27
27
  #testResults: test_results_hash # not yet
28
- }.to_json
28
+ }.to_json(options)
29
29
  end
30
30
 
31
31
  def to_s
@@ -1,4 +1,4 @@
1
1
  # Remember to bump pact-provider-proxy when this changes major version
2
2
  module Pact
3
- VERSION = "1.26.0"
3
+ VERSION = "1.27.0"
4
4
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pact
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.26.0
4
+ version: 1.27.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - James Fraser
@@ -12,7 +12,7 @@ authors:
12
12
  autorequire:
13
13
  bindir: bin
14
14
  cert_chain: []
15
- date: 2018-05-08 00:00:00.000000000 Z
15
+ date: 2018-06-21 00:00:00.000000000 Z
16
16
  dependencies:
17
17
  - !ruby/object:Gem::Dependency
18
18
  name: randexp
@@ -332,6 +332,10 @@ files:
332
332
  - lib/pact/doc/markdown/interaction.erb
333
333
  - lib/pact/doc/markdown/interaction_renderer.rb
334
334
  - lib/pact/doc/sort_interactions.rb
335
+ - lib/pact/hal/entity.rb
336
+ - lib/pact/hal/http_client.rb
337
+ - lib/pact/hal/link.rb
338
+ - lib/pact/pact_broker/fetch_pacts.rb
335
339
  - lib/pact/project_root.rb
336
340
  - lib/pact/provider.rb
337
341
  - lib/pact/provider/configuration.rb
@@ -403,7 +407,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
403
407
  version: '0'
404
408
  requirements: []
405
409
  rubyforge_project:
406
- rubygems_version: 2.7.6
410
+ rubygems_version: 2.6.11
407
411
  signing_key:
408
412
  specification_version: 4
409
413
  summary: Enables consumer driven contract testing, providing a mock service and DSL