From b45398bd2627f0a8f057d9bf0aae2f2c5f4caa44 Mon Sep 17 00:00:00 2001 From: Beth Skurrie Date: Fri, 17 Sep 2021 09:25:14 +1000 Subject: [PATCH] feat: in the webhook body for contract_published and contract_content_changed, use the latest verification from the main branch if present --- lib/pact_broker/domain/verification.rb | 16 ++++++ lib/pact_broker/pacts/pact_publication.rb | 4 ++ lib/pact_broker/pacts/pact_version.rb | 24 +++++++++ lib/pact_broker/test/test_data_builder.rb | 2 +- lib/pact_broker/verifications/repository.rb | 4 ++ lib/pact_broker/verifications/service.rb | 2 +- lib/pact_broker/webhooks/event_listener.rb | 6 ++- .../pact_and_verification_parameters.rb | 1 + .../webhooks/pact_publication_spec.rb | 51 +++++++++++++++++++ .../pact_broker/pacts/pact_version_spec.rb | 32 ++++++++++++ 10 files changed, 138 insertions(+), 4 deletions(-) create mode 100644 spec/integration/webhooks/pact_publication_spec.rb diff --git a/lib/pact_broker/domain/verification.rb b/lib/pact_broker/domain/verification.rb index 384d281d2..7fe57fefe 100644 --- a/lib/pact_broker/domain/verification.rb +++ b/lib/pact_broker/domain/verification.rb @@ -37,6 +37,22 @@ def for_consumer_name(consumer_name) where(consumer: PactBroker::Domain::Pacticipant.find_by_name(consumer_name)) end + # TODO optimise this + def from_provider_main_branch + providers_join = { + Sequel[:verifications][:provider_id] => Sequel[:providers][:id] + } + + branch_versions_join = { + Sequel[:verifications][:provider_version_id] => Sequel[:branch_versions][:version_id], + Sequel[:providers][:main_branch] => Sequel[:branch_versions][:branch_name] + } + + join(:pacticipants, providers_join, { table_alias: :providers }) + .join(:branch_versions, branch_versions_join) + end + + # TODO change this to a group by def latest_by_pact_version base_query = self base_join = { diff --git a/lib/pact_broker/pacts/pact_publication.rb b/lib/pact_broker/pacts/pact_publication.rb index 8b71f9bdb..9f2ed7e92 100644 --- a/lib/pact_broker/pacts/pact_publication.rb +++ b/lib/pact_broker/pacts/pact_publication.rb @@ -113,6 +113,10 @@ def latest_verification pact_version.latest_verification end + def latest_main_branch_verification + pact_version.latest_main_branch_verification + end + def latest_for_branch? if !defined?(@latest_for_branch) if consumer_version.branch_versions.empty? diff --git a/lib/pact_broker/pacts/pact_version.rb b/lib/pact_broker/pacts/pact_version.rb index b19be30eb..05384588f 100644 --- a/lib/pact_broker/pacts/pact_version.rb +++ b/lib/pact_broker/pacts/pact_version.rb @@ -15,6 +15,30 @@ class PactVersion < Sequel::Model(:pact_versions) associate(:many_to_one, :consumer, class: "PactBroker::Domain::Pacticipant", key: :consumer_id, primary_key: :id) associate(:many_to_many, :consumer_versions, class: "PactBroker::Domain::Version", join_table: :pact_publications, left_key: :pact_version_id, right_key: :consumer_version_id, order: :order) + one_to_one(:latest_main_branch_verification, + class: "PactBroker::Domain::Verification", + read_only: true, + dataset: lambda { + providers_join = { + Sequel[:providers][:id] => Sequel[:latest_verification_id_for_pact_version_and_provider_version][:provider_id] + } + + branch_versions_join = { + Sequel[:latest_verification_id_for_pact_version_and_provider_version][:provider_version_id] => Sequel[:branch_versions][:version_id], + Sequel[:providers][:main_branch] => Sequel[:branch_versions][:branch_name] + } + max_verification_id_for_pact_version = PactBroker::Verifications::LatestVerificationIdForPactVersionAndProviderVersion + .join(:pacticipants, providers_join, { table_alias: :providers }) + .join(:branch_versions, branch_versions_join) + .select(Sequel.function(:max, :verification_id)) + .where(pact_version_id: id) + PactBroker::Domain::Verification.where(id: max_verification_id_for_pact_version) + }, + key: :pact_version_id, + primary_key: :id, + eager_block: lambda { | ds | ds.from_provider_main_branch.latest_by_pact_version } + ) + one_to_one(:latest_verification, class: "PactBroker::Domain::Verification", read_only: true, diff --git a/lib/pact_broker/test/test_data_builder.rb b/lib/pact_broker/test/test_data_builder.rb index f3205aeb7..ef2f0af5a 100644 --- a/lib/pact_broker/test/test_data_builder.rb +++ b/lib/pact_broker/test/test_data_builder.rb @@ -282,7 +282,7 @@ def create_webhook parameters = {} params[:events] || [{ name: PactBroker::Webhooks::WebhookEvent::DEFAULT_EVENT_NAME }] end events = event_params.collect{ |e| PactBroker::Webhooks::WebhookEvent.new(e) } - template_params = { method: "POST", url: "http://example.org", headers: {"Content-Type" => "application/json"}, username: params[:username], password: params[:password]} + template_params = { method: "POST", url: "http://example.org", headers: {"Content-Type" => "application/json"}, username: params[:username], password: params[:password] } request = PactBroker::Webhooks::WebhookRequestTemplate.new(template_params.merge(params)) @webhook = PactBroker::Webhooks::Repository.new.create uuid, PactBroker::Domain::Webhook.new(request: request, events: events, description: params[:description], enabled: enabled), consumer, provider self diff --git a/lib/pact_broker/verifications/repository.rb b/lib/pact_broker/verifications/repository.rb index e30d258db..c72ecdb98 100644 --- a/lib/pact_broker/verifications/repository.rb +++ b/lib/pact_broker/verifications/repository.rb @@ -60,6 +60,10 @@ def find_latest_for_pact(pact) PactBroker::Pacts::PactPublication.where(id: pact.id).single_record.latest_verification end + def find_latest_from_main_branch_for_pact(pact) + PactBroker::Pacts::PactPublication.where(id: pact.id).single_record.latest_main_branch_verification + end + def any_verifications?(consumer, provider) PactBroker::Domain::Verification.where(consumer_id: consumer.id, provider_id: provider.id).any? end diff --git a/lib/pact_broker/verifications/service.rb b/lib/pact_broker/verifications/service.rb index e14f59cba..1965fead8 100644 --- a/lib/pact_broker/verifications/service.rb +++ b/lib/pact_broker/verifications/service.rb @@ -21,7 +21,7 @@ module Service using PactBroker::HashRefinements extend PactBroker::Events::Publisher - delegate [:any_verifications?] => :verification_repository + delegate [:any_verifications?, :find_latest_from_main_branch_for_pact] => :verification_repository def next_number verification_repository.next_number diff --git a/lib/pact_broker/webhooks/event_listener.rb b/lib/pact_broker/webhooks/event_listener.rb index bcee3f298..f64ade8f6 100644 --- a/lib/pact_broker/webhooks/event_listener.rb +++ b/lib/pact_broker/webhooks/event_listener.rb @@ -18,14 +18,16 @@ def initialize(webhook_options) end def contract_published(params) - handle_event_for_webhook(PactBroker::Webhooks::WebhookEvent::CONTRACT_PUBLISHED, params) + main_branch_verification = verification_service.find_latest_from_main_branch_for_pact(params.fetch(:pact)) + handle_event_for_webhook(PactBroker::Webhooks::WebhookEvent::CONTRACT_PUBLISHED, { verification: main_branch_verification }.compact.merge(params)) if verification_service.calculate_required_verifications_for_pact(params.fetch(:pact)).any? handle_event_for_webhook(PactBroker::Webhooks::WebhookEvent::CONTRACT_REQUIRING_VERIFICATION_PUBLISHED, params) end end def contract_content_changed(params) - handle_event_for_webhook(PactBroker::Webhooks::WebhookEvent::CONTRACT_CONTENT_CHANGED, params) + main_branch_verification = verification_service.find_latest_from_main_branch_for_pact(params.fetch(:pact)) + handle_event_for_webhook(PactBroker::Webhooks::WebhookEvent::CONTRACT_CONTENT_CHANGED, { verification: main_branch_verification }.compact.merge(params)) end def contract_content_unchanged(params) diff --git a/lib/pact_broker/webhooks/pact_and_verification_parameters.rb b/lib/pact_broker/webhooks/pact_and_verification_parameters.rb index d6f6e4a23..f5ea41319 100644 --- a/lib/pact_broker/webhooks/pact_and_verification_parameters.rb +++ b/lib/pact_broker/webhooks/pact_and_verification_parameters.rb @@ -41,6 +41,7 @@ class PactAndVerificationParameters CURRENTLY_DEPLOYED_PROVIDER_VERSION_NUMBER ] + # TODO change this verification to the latest main branch def initialize(pact, trigger_verification, webhook_context) @pact = pact @verification = trigger_verification || (pact && pact.latest_verification) diff --git a/spec/integration/webhooks/pact_publication_spec.rb b/spec/integration/webhooks/pact_publication_spec.rb new file mode 100644 index 000000000..0ea92e3f8 --- /dev/null +++ b/spec/integration/webhooks/pact_publication_spec.rb @@ -0,0 +1,51 @@ +RSpec.describe "triggering a webhook for a pact publication" do + before do + td.create_global_webhook(event_names: ["contract_published"], body: { "provider_version" => "${pactbroker.providerVersionNumber}"}) + end + + let(:pact_content) { td.random_json_content("Foo", "Bar") } + + let!(:request) do + stub_request(:post, /http/).with(body: expected_webhook_body).to_return(:status => 200) + end + + let(:database_connector) { ->(&block) { block.call } } + + subject { put("/pacts/provider/Bar/consumer/Foo/version/2", pact_content, { "CONTENT_TYPE" => "application/json", "pactbroker.database_connector" => database_connector}) } + + context "when there is a verification from the main branch of the provider" do + before do + td.create_consumer("Foo") + .create_provider("Bar", main_branch: "main") + .create_consumer_version("1") + .create_pact(json_content: pact_content) + .create_verification(provider_version: "1", branch: "main") + .create_verification(provider_version: "2", branch: "feat/x", number: 2) + end + + let(:expected_webhook_body) { { provider_version: "1"}.to_json } + + it "uses that in the webhook" do + subject + expect(request).to have_been_made + end + end + + context "when there is not a verification from the main branch of the provider" do + before do + td.create_consumer("Foo") + .create_provider("Bar", main_branch: "main") + .create_consumer_version("1") + .create_pact(json_content: pact_content) + .create_verification(provider_version: "1", branch: "feat/y") + .create_verification(provider_version: "2", branch: "feat/x", number: 2) + end + + let(:expected_webhook_body) { { provider_version: "2"}.to_json } + + it "uses the latest verification in the webhook" do + subject + expect(request).to have_been_made + end + end +end diff --git a/spec/lib/pact_broker/pacts/pact_version_spec.rb b/spec/lib/pact_broker/pacts/pact_version_spec.rb index 52cd60381..ed83c3935 100644 --- a/spec/lib/pact_broker/pacts/pact_version_spec.rb +++ b/spec/lib/pact_broker/pacts/pact_version_spec.rb @@ -65,6 +65,38 @@ module Pacts end end + describe "#latest_main_branch_verification" do + before do + td.create_pact_with_verification("Foo", "1", "Bar", "2") + .create_verification(provider_version: "3", number: 2, branch: "main") + .create_verification(provider_version: "3", number: 3) + .create_verification(provider_version: "3", number: 4) + .create_pact_with_verification("NotFoo", "1", "Bar", "4") + .create_verification(provider_version: "5", number: 5) + .create_pact_with_verification("NotFoo2", "1", "NotBar", "4") + .create_verification(provider_version: "6", number: 6) + .create_pact_with_verification("NotFoo3", "2", "NotBar", "5") + .create_verification(provider_version: "7", number: 7) + end + + context "lazy loading" do + it "lazy loads" do + expect(PactPublication.order(:id).all.first.pact_version.latest_main_branch_verification).to have_attributes(provider_version_number: "3", number: 4) + expect(PactPublication.order(:id).all.last.pact_version.latest_main_branch_verification).to be_nil + end + end + + context "eager loading" do + let(:pact_version_1) { PactVersion.eager(:latest_main_branch_verification).order(:id).all.first } + let(:pact_version_2) { PactVersion.eager(:latest_main_branch_verification).order(:id).all.last } + + it "eager loads" do + expect(PactPublication.order(:id).all.first.pact_version.latest_main_branch_verification).to have_attributes(provider_version_number: "3", number: 4) + expect(pact_version_2.associations[:latest_main_branch_verification]).to be_nil + end + end + end + describe "latest_consumer_version" do before do td.create_consumer("consumer")