pact_broker 1.3.0 → 1.3.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. checksums.yaml +5 -13
  2. data/.travis.yml +6 -0
  3. data/CHANGELOG.md +6 -0
  4. data/README.md +3 -1
  5. data/lib/pact_broker/api/contracts/consumer_version_number_validation.rb +32 -0
  6. data/lib/pact_broker/api/contracts/pacticipant_name_contract.rb +27 -0
  7. data/lib/pact_broker/api/contracts/pacticipant_name_validation.rb +30 -0
  8. data/lib/pact_broker/api/contracts/put_pact_params_contract.rb +55 -0
  9. data/lib/pact_broker/api/contracts/request_validations.rb +40 -0
  10. data/lib/pact_broker/api/contracts/webhook_contract.rb +32 -0
  11. data/lib/pact_broker/api/pact_broker_urls.rb +7 -0
  12. data/lib/pact_broker/api/resources/base_resource.rb +9 -0
  13. data/lib/pact_broker/api/resources/pact.rb +10 -3
  14. data/lib/pact_broker/api/resources/pact_webhooks.rb +9 -0
  15. data/lib/pact_broker/constants.rb +5 -0
  16. data/lib/pact_broker/doc/views/pacticipants.markdown +2 -2
  17. data/lib/pact_broker/locale/en.yml +6 -0
  18. data/lib/pact_broker/messages.rb +4 -0
  19. data/lib/pact_broker/models/webhook.rb +10 -8
  20. data/lib/pact_broker/models/webhook_request.rb +5 -17
  21. data/lib/pact_broker/pacts/pact_params.rb +79 -0
  22. data/lib/pact_broker/services/webhook_service.rb +6 -0
  23. data/lib/pact_broker/version.rb +1 -1
  24. data/pact_broker.gemspec +4 -3
  25. data/spec/fixtures/webhook_valid.json +14 -0
  26. data/spec/integration/endpoints/pact_put_spec.rb +16 -0
  27. data/spec/integration/endpoints/pact_webhooks_spec.rb +96 -0
  28. data/spec/lib/pact_broker/api/contracts/put_pact_params_contract_spec.rb +122 -0
  29. data/spec/lib/pact_broker/api/contracts/webhook_contract_spec.rb +106 -0
  30. data/spec/lib/pact_broker/api/resources/pact_spec.rb +15 -1
  31. data/spec/lib/pact_broker/api/resources/pact_webhooks_spec.rb +11 -8
  32. data/spec/lib/pact_broker/models/webhook_request_spec.rb +0 -31
  33. data/spec/lib/pact_broker/models/webhook_spec.rb +0 -21
  34. data/spec/lib/pact_broker/pacts/pact_params_spec.rb +69 -0
  35. data/spec/support/shared_examples_for_responses.rb +14 -0
  36. metadata +88 -55
@@ -2,6 +2,7 @@ require 'pact_broker/models/webhook_request_header'
2
2
  require 'pact_broker/models/webhook_execution_result'
3
3
  require 'pact_broker/logging'
4
4
  require 'pact_broker/messages'
5
+ require 'net/http'
5
6
 
6
7
  module PactBroker
7
8
 
@@ -23,6 +24,10 @@ module PactBroker
23
24
 
24
25
  attr_accessor :method, :url, :headers, :body, :username, :password
25
26
 
27
+ # Reform gets confused by the :method method, as :method is a standard
28
+ # Ruby method.
29
+ alias_method :http_method, :method
30
+
26
31
  def initialize attributes = {}
27
32
  @method = attributes[:method]
28
33
  @url = attributes[:url]
@@ -70,15 +75,6 @@ module PactBroker
70
75
 
71
76
  end
72
77
 
73
- def validate
74
- messages = []
75
- messages << message('errors.validation.attribute_missing', attribute: 'method') unless method
76
- messages << message('errors.validation.attribute_missing', attribute: 'url') unless url
77
- messages << message('errors.validation.invalid_http_method', method: method) unless method && method_valid?
78
- messages << message('errors.validation.invalid_url', url: url) unless url && url_valid?
79
- messages
80
- end
81
-
82
78
  private
83
79
 
84
80
  def to_s
@@ -89,14 +85,6 @@ module PactBroker
89
85
  Net::HTTP.const_get(method.capitalize).new(url)
90
86
  end
91
87
 
92
- def method_valid?
93
- Net::HTTP.const_defined?(method.capitalize)
94
- end
95
-
96
- def url_valid?
97
- uri.scheme && uri.host
98
- end
99
-
100
88
  def uri
101
89
  URI(url)
102
90
  end
@@ -0,0 +1,79 @@
1
+ require 'pact_broker/json'
2
+ require 'pact_broker/constants'
3
+ require 'ostruct'
4
+
5
+ module PactBroker
6
+ module Pacts
7
+ class PactParams < Hash
8
+
9
+ def initialize attributes
10
+ merge!(attributes)
11
+ end
12
+
13
+ def self.from_request request, path_info
14
+ json_content = request.body.to_s
15
+ pact_hash = begin
16
+ JSON.parse(json_content, PACT_PARSING_OPTIONS)
17
+ rescue
18
+ {}
19
+ end
20
+
21
+ new(
22
+ consumer_name: path_info.fetch(:consumer_name),
23
+ provider_name: path_info.fetch(:provider_name),
24
+ consumer_version_number: path_info.fetch(:consumer_version_number),
25
+ consumer_name_in_pact: pact_hash.fetch('consumer',{})['name'],
26
+ provider_name_in_pact: pact_hash.fetch('provider',{})['name'],
27
+ json_content: json_content
28
+ )
29
+ end
30
+
31
+ def pacticipant_names
32
+ [consumer_name, provider_name]
33
+ end
34
+
35
+ def consumer_name
36
+ self[:consumer_name]
37
+ end
38
+
39
+ def provider_name
40
+ self[:provider_name]
41
+ end
42
+
43
+ def consumer_version_number
44
+ self[:consumer_version_number]
45
+ end
46
+
47
+ def json_content
48
+ self[:json_content]
49
+ end
50
+
51
+ def consumer_name_in_pact
52
+ self[:consumer_name_in_pact]
53
+ end
54
+
55
+ def provider_name_in_pact
56
+ self[:provider_name_in_pact]
57
+ end
58
+
59
+ def consumer
60
+ PacticipantName.new(consumer_name, consumer_name_in_pact, 'consumer')
61
+ end
62
+
63
+ def provider
64
+ PacticipantName.new(provider_name, provider_name_in_pact, 'provider')
65
+ end
66
+
67
+ class PacticipantName < Struct.new(:name, :name_in_pact, :pacticipant)
68
+ def message_args
69
+ {
70
+ name: name,
71
+ name_in_pact: name_in_pact,
72
+ pacticipant: pacticipant
73
+ }
74
+ end
75
+ end
76
+
77
+ end
78
+ end
79
+ end
@@ -14,6 +14,12 @@ module PactBroker
14
14
  SecureRandom.urlsafe_base64
15
15
  end
16
16
 
17
+ def self.errors webhook
18
+ contract = PactBroker::Api::Contracts::WebhookContract.new(webhook)
19
+ contract.validate
20
+ contract.errors
21
+ end
22
+
17
23
  def self.create uuid, webhook, consumer, provider
18
24
  webhook_repository.create uuid, webhook, consumer, provider
19
25
  end
@@ -1,3 +1,3 @@
1
1
  module PactBroker
2
- VERSION = '1.3.0'
2
+ VERSION = '1.3.1'
3
3
  end
data/pact_broker.gemspec CHANGED
@@ -22,10 +22,11 @@ Gem::Specification.new do |gem|
22
22
  #gem.add_runtime_dependency 'pact'
23
23
  gem.add_runtime_dependency 'httparty'
24
24
  gem.add_runtime_dependency 'json'
25
- gem.add_runtime_dependency 'roar'
25
+ gem.add_runtime_dependency 'roar', '~> 0.12.9'
26
+ gem.add_runtime_dependency 'reform', '~> 1.0'
26
27
  gem.add_runtime_dependency 'sequel', '~> 4.12'
27
- gem.add_runtime_dependency 'webmachine'
28
- gem.add_runtime_dependency 'versionomy'
28
+ gem.add_runtime_dependency 'webmachine', '~> 1.2'
29
+ gem.add_runtime_dependency 'versionomy', '~> 0.4'
29
30
  gem.add_runtime_dependency 'rack'
30
31
  gem.add_runtime_dependency 'redcarpet', '~>3.1'
31
32
  gem.add_runtime_dependency 'pact', '~>1.3', '>=1.3.2'
@@ -0,0 +1,14 @@
1
+ {
2
+ "request": {
3
+ "method": "POST",
4
+ "url": "http://some.url",
5
+ "username": "username",
6
+ "password": "password",
7
+ "headers": {
8
+ "Accept": "application/json"
9
+ },
10
+ "body": {
11
+ "a" : "body"
12
+ }
13
+ }
14
+ }
@@ -39,5 +39,21 @@ describe "pacts/provider/:provider/consumer/:consumer/version/:version" do
39
39
  expect(response_body_json).to include JSON.parse(pact_content)
40
40
  end
41
41
  end
42
+
43
+ context "when the pacticipant names in the path do not match those in the pact" do
44
+ let(:path) { "/pacts/provider/Another%20Provider/consumer/A%20Consumer/version/1.2.3" }
45
+
46
+ it "returns a json error response" do
47
+ expect(subject).to be_a_json_error_response "does not match"
48
+ end
49
+ end
50
+
51
+ context "when the pact is another type of CDC that doesn't have the Consumer or Provider names in the expected places" do
52
+ let(:pact_content) { {}.to_json }
53
+
54
+ it "accepts the un-pact Pact" do
55
+ expect(subject.status).to be 201
56
+ end
57
+ end
42
58
  end
43
59
  end
@@ -0,0 +1,96 @@
1
+
2
+ require 'support/provider_state_builder'
3
+
4
+ module PactBroker::Api
5
+
6
+ module Resources
7
+
8
+ describe PactWebhooks do
9
+
10
+ before do
11
+ ProviderStateBuilder.new.create_pact_with_hierarchy("Some Consumer", "1", "Some Provider")
12
+ end
13
+
14
+
15
+ let(:path) { "/webhooks/provider/Some%20Provider/consumer/Some%20Consumer" }
16
+ let(:headers) { {'CONTENT_TYPE' => 'application/json'} }
17
+
18
+ describe "POST" do
19
+
20
+ subject { post path, webhook_json, headers }
21
+
22
+ context "with invalid attributes" do
23
+
24
+ let(:errors) { ["Request can't be blank"] }
25
+
26
+ let(:webhook_json) do
27
+ {
28
+
29
+ }.to_json
30
+ end
31
+
32
+ it "returns a 400" do
33
+ subject
34
+ expect(last_response.status).to be 400
35
+ end
36
+
37
+ it "returns a JSON content type" do
38
+ subject
39
+ expect(last_response.headers['Content-Type']).to eq 'application/json'
40
+ end
41
+
42
+ it "returns the validation errors" do
43
+ subject
44
+ expect(JSON.parse(last_response.body, symbolize_names: true)).to eq errors: errors
45
+ end
46
+
47
+ end
48
+
49
+ xcontext "with valid attributes" do
50
+
51
+ let(:webhook_response_json) { {some: 'webhook'}.to_json }
52
+ let(:decorator) { instance_double(Decorators::WebhookDecorator) }
53
+
54
+ before do
55
+ allow_any_instance_of(Decorators::WebhookDecorator).to receive(:to_json).and_return(webhook_response_json)
56
+ end
57
+
58
+ it "saves the webhook" do
59
+ expect(PactBroker::Services::WebhookService).to receive(:create).with(next_uuid, webhook, consumer, provider)
60
+ subject
61
+ end
62
+
63
+ it "returns a 201 response" do
64
+ subject
65
+ expect(last_response.status).to be 201
66
+ end
67
+
68
+ it "returns the Location header" do
69
+ subject
70
+ expect(last_response.headers['Location']).to include(next_uuid)
71
+ end
72
+
73
+ it "returns a JSON content type" do
74
+ subject
75
+ expect(last_response.headers['Content-Type']).to eq 'application/hal+json'
76
+ end
77
+
78
+ it "generates the JSON response body" do
79
+ allow(Decorators::WebhookDecorator).to receive(:new).and_call_original #Deserialise
80
+ expect(Decorators::WebhookDecorator).to receive(:new).with(saved_webhook).and_return(decorator) #Serialize
81
+ expect(decorator).to receive(:to_json).with(base_url: 'http://example.org')
82
+ subject
83
+ end
84
+
85
+ it "returns the JSON representation of the webhook" do
86
+ subject
87
+ expect(last_response.body).to eq webhook_response_json
88
+ end
89
+ end
90
+
91
+ end
92
+
93
+ end
94
+ end
95
+
96
+ end
@@ -0,0 +1,122 @@
1
+ require 'spec_helper'
2
+ require 'pact_broker/api/contracts/put_pact_params_contract'
3
+
4
+ module PactBroker
5
+ module Api
6
+ module Contracts
7
+
8
+ describe PutPactParamsContract do
9
+
10
+ let(:json_content) { {'some' => 'json' }.to_json }
11
+ let(:pact_params) { Pacts::PactParams.new(attributes) }
12
+
13
+ let(:valid_attributes) do
14
+ {
15
+ consumer_name: "consumer",
16
+ provider_name: "provider",
17
+ consumer_version_number: '1.2.3',
18
+ json_content: json_content,
19
+ consumer_name_in_pact: "consumer",
20
+ provider_name_in_pact: "provider"
21
+ }
22
+ end
23
+
24
+ subject { PutPactParamsContract.new(pact_params) }
25
+
26
+ describe "errors" do
27
+
28
+ let(:attributes) { valid_attributes }
29
+
30
+ before do
31
+ subject.validate
32
+ end
33
+
34
+ context "with valid params" do
35
+
36
+ it "is empty" do
37
+ expect(subject.errors.any?).to be false
38
+ end
39
+ end
40
+
41
+ context "with a nil consumer version number" do
42
+ let(:attributes) do
43
+ valid_attributes.merge(consumer_version_number: nil)
44
+ end
45
+
46
+ it "returns an error" do
47
+ expect(subject.errors.full_messages).to include "Consumer version number can't be blank"
48
+ end
49
+ end
50
+
51
+ context "with an empty consumer version number" do
52
+ let(:attributes) do
53
+ valid_attributes.merge(consumer_version_number: '')
54
+ end
55
+
56
+ it "returns an error" do
57
+ expect(subject.errors.full_messages).to include "Consumer version number can't be blank"
58
+ end
59
+ end
60
+
61
+ context "with an invalid version number" do
62
+ let(:attributes) { {consumer_version_number: 'blah'} }
63
+
64
+ it "returns an error" do
65
+ expect(subject.errors[:base]).to include "Consumer version number 'blah' is not recognised as a standard semantic version. eg. 1.3.0 or 2.0.4.rc1"
66
+ end
67
+ end
68
+
69
+ context "with a consumer name in the pact that does not match the consumer name in the path" do
70
+
71
+ let(:attributes) do
72
+ valid_attributes.merge(consumer_name: "another consumer")
73
+ end
74
+
75
+ it "returns an error" do
76
+ expect(subject.errors.full_messages).to include "Consumer name in pact ('consumer') does not match consumer name in path ('another consumer')."
77
+ end
78
+ end
79
+
80
+ context "with a provider name in the pact that does not match the provider name in the path" do
81
+
82
+ let(:attributes) do
83
+ valid_attributes.merge(provider_name: "another provider")
84
+ end
85
+
86
+ it "returns an error" do
87
+ expect(subject.errors.full_messages).to include "Provider name in pact ('provider') does not match provider name in path ('another provider')."
88
+ end
89
+ end
90
+
91
+ context "when the consumer name in the pact is not present" do
92
+
93
+ let(:attributes) do
94
+ valid_attributes.tap do | atts |
95
+ atts.delete(:consumer_name_in_pact)
96
+ end
97
+ end
98
+
99
+ it "returns no error because I don't want to stop a different CDC from being published" do
100
+ expect(subject.errors.any?).to be false
101
+ end
102
+ end
103
+
104
+ context "when the provider name in the pact is not present" do
105
+
106
+ let(:attributes) do
107
+ valid_attributes.tap do | atts |
108
+ atts.delete(:provider_name_in_pact)
109
+ end
110
+ end
111
+
112
+ it "returns no error because I don't want to stop a different CDC from being published" do
113
+ expect(subject.errors.any?).to be false
114
+ end
115
+ end
116
+
117
+ end
118
+
119
+ end
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,106 @@
1
+ require 'spec_helper'
2
+ require 'pact_broker/api/contracts/webhook_contract'
3
+ require 'pact_broker/api/decorators/webhook_decorator'
4
+
5
+ module PactBroker
6
+ module Api
7
+ module Contracts
8
+ describe WebhookContract do
9
+
10
+ let(:json) { load_fixture 'webhook_valid.json' }
11
+ let(:webhook) { PactBroker::Api::Decorators::WebhookDecorator.new(Models::Webhook.new).from_json(json) }
12
+ let(:subject) { WebhookContract.new(webhook) }
13
+
14
+ def valid_webhook_with
15
+ hash = load_json_fixture 'webhook_valid.json'
16
+ yield hash
17
+ hash.to_json
18
+ end
19
+
20
+ describe "errors" do
21
+
22
+ before do
23
+ subject.validate
24
+ end
25
+
26
+ context "with valid fields" do
27
+ it "is empty" do
28
+ expect(subject.errors.any?).to be false
29
+ end
30
+ end
31
+
32
+ context "with no request defined" do
33
+
34
+ let(:json) { {}.to_json }
35
+
36
+ it "contains an error for missing request" do
37
+ subject.validate
38
+ expect(subject.errors[:request]).to eq ["can't be blank"]
39
+ end
40
+ end
41
+
42
+ context "with no method" do
43
+ let(:json) do
44
+ valid_webhook_with do |hash|
45
+ hash['request'].delete('method')
46
+ end
47
+ end
48
+
49
+ it "contains an error for missing method" do
50
+ expect(subject.errors[:"request.http_method"]).to eq ["can't be blank"]
51
+ end
52
+ end
53
+
54
+ context "with an invalid method" do
55
+ let(:json) do
56
+ valid_webhook_with do |hash|
57
+ hash['request']['method'] = 'blah'
58
+ end
59
+ end
60
+
61
+ it "contains an error for invalid method" do
62
+ expect(subject.errors[:"request.method"]).to eq ["is not a recognised HTTP method"]
63
+ end
64
+ end
65
+
66
+ context "with no URL" do
67
+ let(:json) do
68
+ valid_webhook_with do |hash|
69
+ hash['request'].delete('url')
70
+ end
71
+ end
72
+
73
+ it "contains an error for missing URL" do
74
+ expect(subject.errors[:"request.url"]).to eq ["can't be blank"]
75
+ end
76
+ end
77
+
78
+ context "with an invalid URL" do
79
+ let(:json) do
80
+ valid_webhook_with do |hash|
81
+ hash['request']['url'] = 'bl ah'
82
+ end
83
+ end
84
+
85
+ it "contains an error for invalid URL" do
86
+ expect(subject.errors[:"request.url"]).to eq ["is not a valid URL eg. http://example.org"]
87
+ end
88
+ end
89
+
90
+ context "with an URL missing a scheme" do
91
+ let(:json) do
92
+ valid_webhook_with do |hash|
93
+ hash['request']['url'] = 'blah'
94
+ end
95
+ end
96
+
97
+ it "contains an error for invalid URL" do
98
+ expect(subject.errors[:"request.url"]).to eq ["is not a valid URL eg. http://example.org"]
99
+ end
100
+ end
101
+ end
102
+
103
+ end
104
+ end
105
+ end
106
+ end