pact_broker 1.3.0 → 1.3.1
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 +5 -13
- data/.travis.yml +6 -0
- data/CHANGELOG.md +6 -0
- data/README.md +3 -1
- data/lib/pact_broker/api/contracts/consumer_version_number_validation.rb +32 -0
- data/lib/pact_broker/api/contracts/pacticipant_name_contract.rb +27 -0
- data/lib/pact_broker/api/contracts/pacticipant_name_validation.rb +30 -0
- data/lib/pact_broker/api/contracts/put_pact_params_contract.rb +55 -0
- data/lib/pact_broker/api/contracts/request_validations.rb +40 -0
- data/lib/pact_broker/api/contracts/webhook_contract.rb +32 -0
- data/lib/pact_broker/api/pact_broker_urls.rb +7 -0
- data/lib/pact_broker/api/resources/base_resource.rb +9 -0
- data/lib/pact_broker/api/resources/pact.rb +10 -3
- data/lib/pact_broker/api/resources/pact_webhooks.rb +9 -0
- data/lib/pact_broker/constants.rb +5 -0
- data/lib/pact_broker/doc/views/pacticipants.markdown +2 -2
- data/lib/pact_broker/locale/en.yml +6 -0
- data/lib/pact_broker/messages.rb +4 -0
- data/lib/pact_broker/models/webhook.rb +10 -8
- data/lib/pact_broker/models/webhook_request.rb +5 -17
- data/lib/pact_broker/pacts/pact_params.rb +79 -0
- data/lib/pact_broker/services/webhook_service.rb +6 -0
- data/lib/pact_broker/version.rb +1 -1
- data/pact_broker.gemspec +4 -3
- data/spec/fixtures/webhook_valid.json +14 -0
- data/spec/integration/endpoints/pact_put_spec.rb +16 -0
- data/spec/integration/endpoints/pact_webhooks_spec.rb +96 -0
- data/spec/lib/pact_broker/api/contracts/put_pact_params_contract_spec.rb +122 -0
- data/spec/lib/pact_broker/api/contracts/webhook_contract_spec.rb +106 -0
- data/spec/lib/pact_broker/api/resources/pact_spec.rb +15 -1
- data/spec/lib/pact_broker/api/resources/pact_webhooks_spec.rb +11 -8
- data/spec/lib/pact_broker/models/webhook_request_spec.rb +0 -31
- data/spec/lib/pact_broker/models/webhook_spec.rb +0 -21
- data/spec/lib/pact_broker/pacts/pact_params_spec.rb +69 -0
- data/spec/support/shared_examples_for_responses.rb +14 -0
- 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
|
data/lib/pact_broker/version.rb
CHANGED
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'
|
@@ -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
|