pact 1.26.0 → 1.27.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
- 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