diff --git a/lib/pact_broker/api.rb b/lib/pact_broker/api.rb index f458512f6..96968a75e 100644 --- a/lib/pact_broker/api.rb +++ b/lib/pact_broker/api.rb @@ -22,6 +22,7 @@ module PactBroker # Verifications add ['pacts', 'provider', :provider_name, 'consumer', :consumer_name, 'pact-version', :pact_version_sha, 'verification-results'], Api::Resources::Verifications, {resource_name: "verification_results"} add ['pacts', 'provider', :provider_name, 'consumer', :consumer_name, 'pact-version', :pact_version_sha, 'verification-results', :verification_number], Api::Resources::Verification, {resource_name: "verification_result"} + add ['pacts', 'provider', :provider_name, 'consumer', :consumer_name, 'pact-version', :pact_version_sha, 'verification-results', :verification_number, 'triggered-webhooks'], Api::Resources::VerificationTriggeredWebhooks, {resource_name: "verification_result_triggered_webhooks"} add ['verification-results', 'consumer', :consumer_name, 'version', :consumer_version_number,'latest'], Api::Resources::LatestVerificationsForConsumerVersion, {resource_name: "verification_results_for_consumer_version"} # Badges diff --git a/lib/pact_broker/api/decorators/verification_decorator.rb b/lib/pact_broker/api/decorators/verification_decorator.rb index d21b2cb2c..c09a0440b 100644 --- a/lib/pact_broker/api/decorators/verification_decorator.rb +++ b/lib/pact_broker/api/decorators/verification_decorator.rb @@ -28,6 +28,12 @@ class VerificationDecorator < BaseDecorator } end + link 'pb:triggered-webhooks' do | options | + { + title: 'Webhooks triggered by the publication of this verification result', + href: verification_triggered_webhooks_url(represented, options.fetch(:base_url)) + } + end end end end diff --git a/lib/pact_broker/api/pact_broker_urls.rb b/lib/pact_broker/api/pact_broker_urls.rb index 84f838625..65d365507 100644 --- a/lib/pact_broker/api/pact_broker_urls.rb +++ b/lib/pact_broker/api/pact_broker_urls.rb @@ -120,6 +120,10 @@ def latest_verifications_for_consumer_version_url version, base_url "#{base_url}/verification-results/consumer/#{url_encode(version.pacticipant.name)}/version/#{version.number}/latest" end + def verification_triggered_webhooks_url verification, base_url = '' + "#{verification_url(verification, base_url)}/triggered-webhooks" + end + def verification_publication_url pact, base_url "#{pactigration_base_url(base_url, pact)}/pact-version/#{pact.pact_version_sha}/verification-results" end diff --git a/lib/pact_broker/api/resources/verification_triggered_webhooks.rb b/lib/pact_broker/api/resources/verification_triggered_webhooks.rb new file mode 100644 index 000000000..c0efeb0c8 --- /dev/null +++ b/lib/pact_broker/api/resources/verification_triggered_webhooks.rb @@ -0,0 +1,45 @@ +require 'pact_broker/api/decorators/triggered_webhooks_decorator' + +module PactBroker + module Api + module Resources + class VerificationTriggeredWebhooks < BaseResource + def allowed_methods + ["GET"] + end + + def content_types_provided + [["application/hal+json", :to_json]] + end + + def resource_exists? + !!verification + end + + def to_json + Decorators::TriggeredWebhooksDecorator.new(triggered_webhooks).to_json(decorator_options) + end + + private + + def triggered_webhooks + webhook_service.find_triggered_webhooks_for_verification(verification) + end + + def resource_title + "Webhooks triggered by the publication of verification result #{verification.number}" + end + + def decorator_options + { + user_options: decorator_context.merge(resource_title: resource_title) + } + end + + def verification + @verification ||= verification_service.find(identifier_from_path) + end + end + end + end +end diff --git a/lib/pact_broker/domain/webhook.rb b/lib/pact_broker/domain/webhook.rb index 9baa347c0..6ea046356 100644 --- a/lib/pact_broker/domain/webhook.rb +++ b/lib/pact_broker/domain/webhook.rb @@ -56,6 +56,14 @@ def consumer_name def provider_name provider && provider.name end + + def trigger_on_contract_content_changed? + events.any?(&:contract_content_changed?) + end + + def trigger_on_provider_verification_published? + events.any?(&:provider_verification_published?) + end end end end diff --git a/lib/pact_broker/webhooks/repository.rb b/lib/pact_broker/webhooks/repository.rb index e74ae9cf3..0d9fb839f 100644 --- a/lib/pact_broker/webhooks/repository.rb +++ b/lib/pact_broker/webhooks/repository.rb @@ -185,6 +185,14 @@ def find_triggered_webhooks_for_pact pact .reverse(:created_at, :id) end + def find_triggered_webhooks_for_verification verification + PactBroker::Webhooks::TriggeredWebhook + .where(verification_id: verification.id) + .eager(:webhook) + .eager(:webhook_executions) + .reverse(:created_at, :id) + end + def fail_retrying_triggered_webhooks TriggeredWebhook.retrying.update(status: TriggeredWebhook::STATUS_FAILURE) end diff --git a/lib/pact_broker/webhooks/service.rb b/lib/pact_broker/webhooks/service.rb index 1bf3ec33c..3cba55d6b 100644 --- a/lib/pact_broker/webhooks/service.rb +++ b/lib/pact_broker/webhooks/service.rb @@ -132,6 +132,10 @@ def self.fail_retrying_triggered_webhooks def self.find_triggered_webhooks_for_pact pact webhook_repository.find_triggered_webhooks_for_pact(pact) end + + def self.find_triggered_webhooks_for_verification verification + webhook_repository.find_triggered_webhooks_for_verification(verification) + end end end end diff --git a/lib/pact_broker/webhooks/webhook_event.rb b/lib/pact_broker/webhooks/webhook_event.rb index 7f84d213a..5cfa5842b 100644 --- a/lib/pact_broker/webhooks/webhook_event.rb +++ b/lib/pact_broker/webhooks/webhook_event.rb @@ -17,6 +17,14 @@ class WebhookEvent < Sequel::Model include PactBroker::Repositories::Helpers end + def contract_content_changed? + name == CONTRACT_CONTENT_CHANGED + end + + def provider_verification_published? + name == VERIFICATION_PUBLISHED + end + end WebhookEvent.plugin :timestamps, update_on_create: true diff --git a/script/seed.rb b/script/seed.rb index 78cd43391..0ee9985e1 100755 --- a/script/seed.rb +++ b/script/seed.rb @@ -54,11 +54,13 @@ def publish_pact params = {} # .create_webhook(method: 'GET', url: 'https://localhost:9393?url=${pactbroker.pactUrl}', body: '${pactbroker.pactUrl}') TestDataBuilder.new + .create_global_webhook(method: 'GET', url: "http://example.org?consumer=${pactbroker.consumerName}&provider=${pactbroker.providerName}") .create_certificate(path: 'spec/fixtures/certificates/self-signed.badssl.com.pem') .create_consumer("Foo") .create_label("microservice") .create_provider("Bar") .create_label("microservice") + .create_verification_webhook(method: 'GET', url: "http://example.org") .create_consumer_webhook(method: 'GET', url: 'https://www.google.com.au', event_names: ['provider_verification_published']) .create_provider_webhook(method: 'GET', url: 'https://theage.com.au') .create_webhook(method: 'GET', url: 'https://self-signed.badssl.com') diff --git a/spec/features/get_triggered_webhooks_for_verification_spec.rb b/spec/features/get_triggered_webhooks_for_verification_spec.rb new file mode 100644 index 000000000..ca76f9a6a --- /dev/null +++ b/spec/features/get_triggered_webhooks_for_verification_spec.rb @@ -0,0 +1,21 @@ +RSpec.describe "Get triggered webhooks for verification" do + before do + td.create_pact_with_hierarchy + .create_verification_webhook + .create_verification + .create_triggered_webhook + .create_webhook_execution + end + + let(:td) { TestDataBuilder.new } + let(:path) { PactBroker::Api::PactBrokerUrls.verification_triggered_webhooks_url(td.verification) } + let(:json_response_body) { JSON.parse(subject.body) } + + subject { get(path); last_response } + + it { is_expected.to be_a_hal_json_success_response } + + it "contains a list of triggered webhooks" do + expect(json_response_body['_embedded']['triggeredWebhooks'].size).to be 1 + end +end diff --git a/spec/lib/pact_broker/api/decorators/verification_decorator_spec.rb b/spec/lib/pact_broker/api/decorators/verification_decorator_spec.rb index bc1886793..82e7a735a 100644 --- a/spec/lib/pact_broker/api/decorators/verification_decorator_spec.rb +++ b/spec/lib/pact_broker/api/decorators/verification_decorator_spec.rb @@ -4,6 +4,9 @@ module PactBroker module Api module Decorators describe VerificationDecorator do + before do + allow_any_instance_of(VerificationDecorator).to receive(:verification_triggered_webhooks_url).and_return("http://triggered-webhooks") + end let(:verification) do instance_double('PactBroker::Domain::Verification', @@ -30,6 +33,7 @@ module Decorators let(:options) { { user_options: { base_url: 'http://example.org' } } } + subject { JSON.parse VerificationDecorator.new(verification).to_json(options), symbolize_names: true } it "includes the success status" do @@ -55,6 +59,10 @@ module Decorators it "includes a link to its pact" do expect(subject[:_links][:'pb:pact-version'][:href]).to match %r{http://example.org/pacts/} end + + it "includes a link to the triggered webhooks" do + expect(subject[:_links][:'pb:triggered-webhooks'][:href]).to eq "http://triggered-webhooks" + end end end end diff --git a/spec/lib/pact_broker/api/pact_broker_urls_spec.rb b/spec/lib/pact_broker/api/pact_broker_urls_spec.rb index b97c4df2a..aaef0d6dd 100644 --- a/spec/lib/pact_broker/api/pact_broker_urls_spec.rb +++ b/spec/lib/pact_broker/api/pact_broker_urls_spec.rb @@ -8,6 +8,13 @@ module Api let(:pact) { double('pact', consumer: consumer, provider: provider, consumer_version_number: "123") } let(:consumer) { double('pacticipant', name: "Foo") } let(:provider) { double('pacticipant', name: "Bar") } + let(:verification) do + instance_double(PactBroker::Domain::Verification, + consumer_name: "Foo", + provider_name: "Bar", + pact_version_sha: "1234", + number: "1") + end describe "templated_tag_url_for_pacticipant" do subject { PactBrokerUrls.templated_tag_url_for_pacticipant("Bar", base_url) } @@ -20,6 +27,12 @@ module Api it { is_expected.to eq "http://example.org/pacts/provider/Bar/consumer/Foo/version/123/triggered-webhooks" } end + + describe "verification_triggered_webhooks_url" do + subject { PactBrokerUrls.verification_triggered_webhooks_url(verification, base_url) } + + it { is_expected.to eq "http://example.org/pacts/provider/Bar/consumer/Foo/pact-version/1234/verification-results/1/triggered-webhooks" } + end end end end diff --git a/spec/lib/pact_broker/api/resources/verification_triggered_webhooks_spec.rb b/spec/lib/pact_broker/api/resources/verification_triggered_webhooks_spec.rb new file mode 100644 index 000000000..043168f73 --- /dev/null +++ b/spec/lib/pact_broker/api/resources/verification_triggered_webhooks_spec.rb @@ -0,0 +1,68 @@ +require 'pact_broker/api/resources/verification_triggered_webhooks' + +module PactBroker + module Api + module Resources + describe VerificationTriggeredWebhooks do + describe "GET" do + before do + allow(Decorators::TriggeredWebhooksDecorator).to receive(:new).and_return(decorator) + allow_any_instance_of(VerificationTriggeredWebhooks).to receive(:webhook_service).and_return(webhook_service) + allow_any_instance_of(VerificationTriggeredWebhooks).to receive(:verification_service).and_return(verification_service) + allow(webhook_service).to receive(:find_triggered_webhooks_for_verification).and_return(triggered_webhooks) + end + + let(:decorator) { instance_double(Decorators::TriggeredWebhooksDecorator, to_json: 'json') } + let(:webhook_service) { class_double(PactBroker::Webhooks::Service) } + let(:verification_service) { class_double(PactBroker::Verifications::Service, find: verification) } + let(:verification) { instance_double(PactBroker::Domain::Verification, number: "1") } + let(:triggered_webhooks) { double('triggered_webhooks') } + let(:path) { "/pacts/provider/bar/consumer/foo/pact-version/1234/verification-results/1/triggered-webhooks" } + + subject { get path; last_response } + + it "searchs for the verification" do + expect(verification_service).to receive(:find).with( + hash_including( + provider_name: "bar", + consumer_name: "foo", + pact_version_sha: "1234", + verification_number: "1" + ) + ) + subject + end + + context "when the verification exists" do + + it "finds the triggered webhooks for the verification" do + expect(webhook_service).to receive(:find_triggered_webhooks_for_verification) + subject + end + + it { is_expected.to be_a_hal_json_success_response } + + it "generates the JSON response body" do + expect(Decorators::TriggeredWebhooksDecorator).to receive(:new).with(triggered_webhooks) + expect(decorator).to receive(:to_json) do | options | + expect(options[:user_options]).to include(resource_title: "Webhooks triggered by the publication of verification result 1") + expect(options[:user_options]).to include(resource_url: "http://example.org#{path}") + end + subject + end + + it "returns the generated JSON response body" do + expect(subject.body).to eq 'json' + end + end + + context "when the verification does not exist" do + let(:verification) { nil } + + it { is_expected.to be_a_404_response } + end + end + end + end + end +end diff --git a/spec/lib/pact_broker/webhooks/repository_spec.rb b/spec/lib/pact_broker/webhooks/repository_spec.rb index 59fc8e696..07cae4bc0 100644 --- a/spec/lib/pact_broker/webhooks/repository_spec.rb +++ b/spec/lib/pact_broker/webhooks/repository_spec.rb @@ -625,7 +625,6 @@ module Webhooks .create_webhook_execution .create_triggered_webhook(trigger_uuid: '777', created_at: DateTime.new(2018)) .create_webhook_execution - end subject { Repository.new.find_latest_triggered_webhooks_for_pact(td.pact) } @@ -635,6 +634,44 @@ module Webhooks end end + describe "find_triggered_webhooks_for_pact" do + before do + td + .create_pact_with_hierarchy("Foo", "1", "Bar") + .create_webhook + .create_triggered_webhook(trigger_uuid: "1") + .create_webhook_execution + .create_consumer_version("2") + .create_pact + .create_triggered_webhook(trigger_uuid: "2") + .create_webhook_execution + end + + subject { Repository.new.find_triggered_webhooks_for_pact(td.pact) } + + it "finds the triggered webhooks" do + expect(subject.collect(&:trigger_uuid).sort).to eq ["2"] + end + end + + describe "find_triggered_webhooks_for_verification" do + before do + td + .create_pact_with_hierarchy("Foo", "1", "Bar") + .create_verification_webhook + .create_verification(provider_version: "1") + .create_triggered_webhook(trigger_uuid: "1") + .create_verification(provider_version: "2", number: 2) + .create_triggered_webhook(trigger_uuid: "2") + end + + subject { Repository.new.find_triggered_webhooks_for_verification(td.verification) } + + it "finds the triggered webhooks" do + expect(subject.collect(&:trigger_uuid).sort).to eq ["2"] + end + end + describe "fail_retrying_triggered_webhooks" do before do td.create_pact_with_hierarchy diff --git a/spec/support/test_data_builder.rb b/spec/support/test_data_builder.rb index 300759f28..6f363b144 100644 --- a/spec/support/test_data_builder.rb +++ b/spec/support/test_data_builder.rb @@ -261,6 +261,10 @@ def create_webhook parameters = {} self end + def create_verification_webhook parameters = {} + create_webhook(parameters.merge(event_names: PactBroker::Webhooks::WebhookEvent::VERIFICATION_PUBLISHED)) + end + def create_global_webhook parameters = {} create_webhook(parameters.merge(consumer: nil, provider: nil)) end @@ -280,7 +284,8 @@ def create_verification_webhook params = {} def create_triggered_webhook params = {} params.delete(:comment) trigger_uuid = params[:trigger_uuid] || webhook_service.next_uuid - @triggered_webhook = webhook_repository.create_triggered_webhook trigger_uuid, @webhook, @pact, nil, PactBroker::Webhooks::Service::RESOURCE_CREATION + verification = @webhook.trigger_on_provider_verification_published? ? @verification : nil + @triggered_webhook = webhook_repository.create_triggered_webhook trigger_uuid, @webhook, @pact, verification, PactBroker::Webhooks::Service::RESOURCE_CREATION @triggered_webhook.update(status: params[:status]) if params[:status] set_created_at_if_set params[:created_at], :triggered_webhooks, {id: @triggered_webhook.id} self