diff --git a/lib/pact_broker/deployments/deployed_version_service.rb b/lib/pact_broker/deployments/deployed_version_service.rb index 2e80b5e26..16bde5122 100644 --- a/lib/pact_broker/deployments/deployed_version_service.rb +++ b/lib/pact_broker/deployments/deployed_version_service.rb @@ -1,8 +1,10 @@ require "pact_broker/deployments/deployed_version" +require "pact_broker/repositories/scopes" module PactBroker module Deployments class DeployedVersionService + extend PactBroker::Repositories::Scopes def self.next_uuid SecureRandom.uuid @@ -78,12 +80,6 @@ def self.record_previous_version_undeployed(pacticipant, environment, target) end private_class_method :record_previous_version_undeployed - - def self.scope_for(scope) - PactBroker.policy_scope!(scope) - end - - private_class_method :scope_for end end end diff --git a/lib/pact_broker/deployments/released_version.rb b/lib/pact_broker/deployments/released_version.rb index 8fc28bd08..bf39ba686 100644 --- a/lib/pact_broker/deployments/released_version.rb +++ b/lib/pact_broker/deployments/released_version.rb @@ -59,6 +59,10 @@ def record_support_ended def currently_supported support_ended_at == nil end + + def version_number + version.number + end end end end diff --git a/lib/pact_broker/deployments/released_version_service.rb b/lib/pact_broker/deployments/released_version_service.rb index a7222c475..a7c898812 100644 --- a/lib/pact_broker/deployments/released_version_service.rb +++ b/lib/pact_broker/deployments/released_version_service.rb @@ -1,8 +1,11 @@ require "pact_broker/deployments/released_version" +require "pact_broker/repositories/scopes" module PactBroker module Deployments class ReleasedVersionService + extend PactBroker::Repositories::Scopes + def self.next_uuid SecureRandom.uuid end @@ -42,6 +45,15 @@ def self.find_released_version_for_version_and_environment(version, environment) def self.record_version_support_ended(released_version) released_version.record_support_ended end + + def self.find_currently_supported_versions_for_pacticipant(pacticipant) + scope_for(ReleasedVersion) + .currently_supported + .where(pacticipant_id: pacticipant.id) + .eager(:version) + .eager(:environment) + .all + end end end end diff --git a/lib/pact_broker/domain/webhook.rb b/lib/pact_broker/domain/webhook.rb index 601d765aa..cf63af1e1 100644 --- a/lib/pact_broker/domain/webhook.rb +++ b/lib/pact_broker/domain/webhook.rb @@ -91,6 +91,10 @@ def trigger_on_provider_verification_failed? events.any?(&:provider_verification_failed?) end + def trigger_on_contract_requiring_verification_published? + events.any?(&:contract_requiring_verification_published?) + end + def expand_currently_deployed_provider_versions? request.uses_parameter?(PactBroker::Webhooks::PactAndVerificationParameters::CURRENTLY_DEPLOYED_PROVIDER_VERSION_NUMBER) end diff --git a/lib/pact_broker/pacts/pact_version.rb b/lib/pact_broker/pacts/pact_version.rb index af1cd564c..9681035d3 100644 --- a/lib/pact_broker/pacts/pact_version.rb +++ b/lib/pact_broker/pacts/pact_version.rb @@ -26,6 +26,14 @@ class PactVersion < Sequel::Model(:pact_versions) dataset_module do include PactBroker::Repositories::Helpers + def for_pact_domain(pact_domain) + where( + sha: pact_domain.pact_version_sha, + consumer_id: pact_domain.consumer.id, + provider_id: pact_domain.provider.id + ).single_record + end + def join_successful_verifications verifications_join = { Sequel[:verifications][:pact_version_id] => Sequel[:pact_versions][:id], diff --git a/lib/pact_broker/repositories/scopes.rb b/lib/pact_broker/repositories/scopes.rb index c5e26e4ed..92307d0fa 100644 --- a/lib/pact_broker/repositories/scopes.rb +++ b/lib/pact_broker/repositories/scopes.rb @@ -1,8 +1,19 @@ module PactBroker module Repositories module Scopes + def with_no_scope + @unscoped = true + yield self + ensure + @unscoped = false + end + def scope_for(scope) - PactBroker.policy_scope!(scope) + if @unscoped == true + scope + else + PactBroker.policy_scope!(scope) + end end # For the times when it doesn't make sense to use the scoped class, this is a way to diff --git a/lib/pact_broker/test/http_test_data_builder.rb b/lib/pact_broker/test/http_test_data_builder.rb index 5f3fbfaff..0c9a46b16 100644 --- a/lib/pact_broker/test/http_test_data_builder.rb +++ b/lib/pact_broker/test/http_test_data_builder.rb @@ -180,17 +180,19 @@ def verify_pact(index: 0, success:, provider: last_provider_name, provider_versi self end - def create_global_webhook_for_contract_changed(uuid: nil, url: "https://postman-echo.com/post", body: nil) + def create_global_webhook_for_event(uuid: nil, url: "https://postman-echo.com/post", body: nil, event_name: ) puts "Creating global webhook for contract changed event with uuid #{uuid}" uuid ||= SecureRandom.uuid default_body = { - "deployedProviderVersion" => "${pactbroker.currentlyDeployedProviderVersionNumber}", + "providerVersionNumber" => "${pactbroker.providerVersionNumber}", + "providerVersionBranch" => "${pactbroker.providerVersionBranch}", + "consumerVersionNumber" => "${pactbroker.consumerVersionNumber}", "consumerVersionBranch" => "${pactbroker.consumerVersionBranch}" } request_body = { "description" => "A webhook for all consumers and providers", "events" => [{ - "name" => "contract_content_changed" + "name" => event_name }], "request" => { "method" => "POST", @@ -204,6 +206,10 @@ def create_global_webhook_for_contract_changed(uuid: nil, url: "https://postman- self end + def create_global_webhook_for_contract_changed(uuid: nil, url: "https://postman-echo.com/post", body: nil) + create_global_webhook_for_event(uuid: uuid, url: url, body: body, event_name: "contract_content_changed") + end + def create_global_webhook_for_anything_published(uuid: nil, url: "https://postman-echo.com/post") puts "Creating global webhook for contract changed event with uuid #{uuid}" uuid ||= SecureRandom.uuid diff --git a/lib/pact_broker/verifications/required_verification.rb b/lib/pact_broker/verifications/required_verification.rb new file mode 100644 index 000000000..2cee65f1a --- /dev/null +++ b/lib/pact_broker/verifications/required_verification.rb @@ -0,0 +1,28 @@ +module PactBroker + module Verifications + class RequiredVerification + attr_reader :provider_version, :provider_version_descriptions + + def initialize(attributes = {}) + attributes.each do | (name, value) | + instance_variable_set("@#{name}", value) if respond_to?(name) + end + end + + def == other + provider_version == other.provider_version && provider_version_descriptions == other.provider_version_descriptions + end + + def + other + if provider_version != other.provider_version + raise PactBroker::Error.new("Can't + RequiredVerifications with different provider versions (#{provider_version.number}/#{other.provider_version.number})") + end + + RequiredVerification.new( + provider_version: provider_version, + provider_version_descriptions: (provider_version_descriptions + other.provider_version_descriptions).uniq + ) + end + end + end +end diff --git a/lib/pact_broker/verifications/service.rb b/lib/pact_broker/verifications/service.rb index 94f3b7140..64c2e8273 100644 --- a/lib/pact_broker/verifications/service.rb +++ b/lib/pact_broker/verifications/service.rb @@ -5,6 +5,7 @@ require "pact_broker/logging" require "pact_broker/hash_refinements" require "pact_broker/events/publisher" +require "pact_broker/verifications/required_verification" module PactBroker module Verifications @@ -89,7 +90,16 @@ def delete_all_verifications_between(consumer_name, options) verification_repository.delete_all_verifications_between(consumer_name, options) end - private + def calculate_required_verifications_for_pact(pact) + pact_version = PactBroker::Pacts::PactVersion.for_pact_domain(pact) + required_verifications = required_verifications_for_main_branch(pact_version) + + required_verifications_for_deployed_versions(pact_version) + + required_verifications_for_released_versions(pact_version) + required_verifications + .group_by(&:provider_version) + .values + .flat_map { | required_verifications_for_provider_version | required_verifications_for_provider_version.reduce(&:+) } + end def broadcast_events(verification, pact, event_context) event_params = { @@ -105,6 +115,44 @@ def broadcast_events(verification, pact, event_context) broadcast(:provider_verification_failed, event_params) end end + private :broadcast_events + + def identify_required_verification(pact_version, provider_version, description) + any_verifications = PactBroker::Domain::Verification.where(pact_version_id: pact_version.id, provider_version_id: provider_version.id).any? + if !any_verifications + RequiredVerification.new(provider_version: provider_version, provider_version_descriptions: [description]) + end + end + private :identify_required_verification + + def required_verifications_for_main_branch(pact_version) + latest_version_from_main_branch = [version_service.find_latest_version_from_main_branch(pact_version.provider)].compact + + latest_version_from_main_branch.collect do | main_branch_version | + identify_required_verification(pact_version, main_branch_version, "latest version from main branch") + end.compact + end + private :required_verifications_for_main_branch + + def required_verifications_for_deployed_versions(pact_version) + deployed_versions = deployed_version_service.with_no_scope do | unscoped_service | + unscoped_service.find_currently_deployed_versions_for_pacticipant(pact_version.provider) + end + deployed_versions.collect do | deployed_version | + identify_required_verification(pact_version, deployed_version.version, "currently deployed version") + end.compact + end + private :required_verifications_for_deployed_versions + + def required_verifications_for_released_versions(pact_version) + released_versions = released_version_service.with_no_scope do | unscoped_service | + unscoped_service.find_currently_supported_versions_for_pacticipant(pact_version.provider) + end + released_versions.collect do | released_version | + identify_required_verification(pact_version, released_version.version, "currently released version") + end.compact + end + private :required_verifications_for_released_versions end end end diff --git a/lib/pact_broker/versions/repository.rb b/lib/pact_broker/versions/repository.rb index e90db8f32..8fbb34bb6 100644 --- a/lib/pact_broker/versions/repository.rb +++ b/lib/pact_broker/versions/repository.rb @@ -128,6 +128,16 @@ def delete_orphan_versions consumer, provider def find_versions_for_selector(selector) PactBroker::Domain::Version.select_all_qualified.for_selector(selector).all end + + def find_latest_version_from_main_branch(pacticipant) + if pacticipant.main_branch + latest_from_main_branch = PactBroker::Domain::Version + .latest_versions_for_pacticipant_branches(pacticipant.id, pacticipant.main_branch) + .single_record + + latest_from_main_branch || find_by_pacticipant_name_and_latest_tag(pacticipant.name, pacticipant.main_branch) + end + end end end end diff --git a/lib/pact_broker/versions/service.rb b/lib/pact_broker/versions/service.rb index fdcdd9c2e..b2e6dfab7 100644 --- a/lib/pact_broker/versions/service.rb +++ b/lib/pact_broker/versions/service.rb @@ -46,6 +46,10 @@ def self.create_or_update(pacticipant_name, version_number, version) version_repository.create_or_update(pacticipant, version_number, version) end + def self.find_latest_version_from_main_branch(pacticipant) + version_repository.find_latest_version_from_main_branch(pacticipant) + end + def self.delete version tag_repository.delete_by_version_id version.id webhook_repository.delete_triggered_webhooks_by_version_id version.id diff --git a/lib/pact_broker/webhooks/event_listener.rb b/lib/pact_broker/webhooks/event_listener.rb index 8b2447c5a..58fe770be 100644 --- a/lib/pact_broker/webhooks/event_listener.rb +++ b/lib/pact_broker/webhooks/event_listener.rb @@ -19,6 +19,9 @@ def initialize(webhook_options) def contract_published(params) handle_event_for_webhook(PactBroker::Webhooks::WebhookEvent::CONTRACT_PUBLISHED, 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) diff --git a/lib/pact_broker/webhooks/trigger_service.rb b/lib/pact_broker/webhooks/trigger_service.rb index fc4f5bc1c..11bfc1d1d 100644 --- a/lib/pact_broker/webhooks/trigger_service.rb +++ b/lib/pact_broker/webhooks/trigger_service.rb @@ -47,19 +47,6 @@ def create_triggered_webhooks_for_event pact, verification, event_name, event_co end end - # private - def create_triggered_webhooks_for_webhooks webhooks, pact, verification, event_name, event_context - webhooks.flat_map do | webhook | - expanded_event_contexts = expand_events_for_currently_deployed_environments(webhook, pact, event_context) - expanded_event_contexts = expanded_event_contexts.flat_map { | ec | expand_events_for_verification_of_multiple_selected_pacts(ec) } - - expanded_event_contexts.collect do | expanded_event_context | - pact_for_triggered_webhook = verification ? find_pact_for_verification_triggered_webhook(pact, expanded_event_context) : pact - webhook_repository.create_triggered_webhook(next_uuid, webhook, pact_for_triggered_webhook, verification, RESOURCE_CREATION, event_name, expanded_event_context) - end - end - end - def schedule_webhooks(triggered_webhooks, options) triggered_webhooks.each_with_index do | triggered_webhook, i | logger.info "Scheduling job for webhook with uuid #{triggered_webhook.webhook.uuid}, context: #{triggered_webhook.event_context}" @@ -76,7 +63,19 @@ def schedule_webhooks(triggered_webhooks, options) end end - private + def create_triggered_webhooks_for_webhooks webhooks, pact, verification, event_name, event_context + webhooks.flat_map do | webhook | + expanded_event_contexts = expand_events_for_currently_deployed_environments(webhook, pact, event_context) + expanded_event_contexts = expand_events_for_required_verifications(event_name, pact, expanded_event_contexts) + expanded_event_contexts = expanded_event_contexts.flat_map { | ec | expand_events_for_verification_of_multiple_selected_pacts(ec) } + + expanded_event_contexts.collect do | expanded_event_context | + pact_for_triggered_webhook = verification ? find_pact_for_verification_triggered_webhook(pact, expanded_event_context) : pact + webhook_repository.create_triggered_webhook(next_uuid, webhook, pact_for_triggered_webhook, verification, RESOURCE_CREATION, event_name, expanded_event_context) + end + end + end + private :create_triggered_webhooks_for_webhooks def merge_consumer_version_selectors(consumer_version_number, selectors, event_context) event_context.merge( @@ -84,6 +83,7 @@ def merge_consumer_version_selectors(consumer_version_number, selectors, event_c consumer_version_tags: selectors.collect{ | selector | selector[:tag] }.compact.uniq ) end + private :merge_consumer_version_selectors # Now that we de-duplicate the pact contents when verifying though the 'pacts for verification' API, # we no longer get a webhook triggered for the verification results publication of each indiviual @@ -103,6 +103,7 @@ def expand_events_for_verification_of_multiple_selected_pacts(event_context) [event_context] end end + private :expand_events_for_verification_of_multiple_selected_pacts def expand_events_for_currently_deployed_environments(webhook, pact, event_context) if PactBroker.feature_enabled?(:expand_currently_deployed_provider_versions) && webhook.expand_currently_deployed_provider_versions? @@ -113,6 +114,7 @@ def expand_events_for_currently_deployed_environments(webhook, pact, event_conte [event_context] end end + private :expand_events_for_currently_deployed_environments def find_pact_for_verification_triggered_webhook(pact, reconstituted_event_context) if reconstituted_event_context[:consumer_version_number] @@ -126,6 +128,20 @@ def find_pact_for_verification_triggered_webhook(pact, reconstituted_event_conte pact end end + private :find_pact_for_verification_triggered_webhook + + def expand_events_for_required_verifications(event_name, pact, event_contexts) + if event_name == PactBroker::Webhooks::WebhookEvent::CONTRACT_REQUIRING_VERIFICATION_PUBLISHED + required_verifications = verification_service.calculate_required_verifications_for_pact(pact) + event_contexts.flat_map do | event_context | + required_verifications.collect do | required_verification | + event_context.merge(provider_version_number: required_verification.provider_version.number, provider_version_descriptions: required_verification.provider_version_descriptions) + end + end + else + event_contexts + end + end end end end diff --git a/lib/pact_broker/webhooks/webhook_event.rb b/lib/pact_broker/webhooks/webhook_event.rb index 78e507588..57b8cc095 100644 --- a/lib/pact_broker/webhooks/webhook_event.rb +++ b/lib/pact_broker/webhooks/webhook_event.rb @@ -12,8 +12,9 @@ class WebhookEvent < Sequel::Model VERIFICATION_SUCCEEDED = "provider_verification_succeeded" VERIFICATION_FAILED = "provider_verification_failed" DEFAULT_EVENT_NAME = CONTRACT_CONTENT_CHANGED + CONTRACT_REQUIRING_VERIFICATION_PUBLISHED = "contract_requiring_verification_published" - EVENT_NAMES = [CONTRACT_PUBLISHED, CONTRACT_CONTENT_CHANGED, VERIFICATION_PUBLISHED, VERIFICATION_SUCCEEDED, VERIFICATION_FAILED] + EVENT_NAMES = [CONTRACT_PUBLISHED, CONTRACT_CONTENT_CHANGED, VERIFICATION_PUBLISHED, VERIFICATION_SUCCEEDED, VERIFICATION_FAILED, CONTRACT_REQUIRING_VERIFICATION_PUBLISHED] dataset_module do include PactBroker::Repositories::Helpers @@ -38,6 +39,10 @@ def provider_verification_succeeded? def provider_verification_failed? name == VERIFICATION_FAILED end + + def contract_requiring_verification_published? + name == CONTRACT_REQUIRING_VERIFICATION_PUBLISHED + end end end end diff --git a/script/data/contract-published-requiring-verification.rb b/script/data/contract-published-requiring-verification.rb new file mode 100755 index 000000000..2de3cbf6e --- /dev/null +++ b/script/data/contract-published-requiring-verification.rb @@ -0,0 +1,26 @@ +#!/usr/bin/env ruby +begin + + $LOAD_PATH << "#{Dir.pwd}/lib" + require "pact_broker/test/http_test_data_builder" + base_url = ENV["PACT_BROKER_BASE_URL"] || "http://localhost:9292" + + td = PactBroker::Test::HttpTestDataBuilder.new(base_url) + td.delete_integration(consumer: "Foo", provider: "Bar") + .delete_integration(consumer: "foo-consumer", provider: "bar-provider") + .create_global_webhook_for_event(uuid: "7a5da39c-8e50-4cc9-ae16-dfa5be043e8c", event_name: "contract_requiring_verification_published") + .create_environment(name: "test") + .create_environment(name: "prod", production: true) + .create_pacticipant("Foo", main_branch: "main") + .create_pacticipant("Bar", main_branch: "main") + .create_version(pacticipant: "Bar", version: "1", branch: "main") + .record_deployment(pacticipant: "Bar", version: "1", environment_name: "test") + .record_deployment(pacticipant: "Bar", version: "1", environment_name: "prod") + .create_version(pacticipant: "Bar", version: "2", branch: "main") + .publish_pact(consumer: "Foo", consumer_version: "1", provider: "Bar", content_id: "111") + +rescue StandardError => e + puts "#{e.class} #{e.message}" + puts e.backtrace + exit 1 +end diff --git a/script/data/expand-currently-deployed.rb b/script/data/expand-currently-deployed.rb new file mode 100755 index 000000000..36f786082 --- /dev/null +++ b/script/data/expand-currently-deployed.rb @@ -0,0 +1,47 @@ +#!/usr/bin/env ruby +begin + + $LOAD_PATH << "#{Dir.pwd}/lib" + require "pact_broker/test/http_test_data_builder" + base_url = ENV["PACT_BROKER_BASE_URL"] || "http://localhost:9292" + + td = PactBroker::Test::HttpTestDataBuilder.new(base_url) + td.delete_integration(consumer: "Foo", provider: "Bar") + .delete_integration(consumer: "foo-consumer", provider: "bar-provider") + .create_environment(name: "test") + .create_environment(name: "prod", production: true) + .publish_pact(consumer: "foo-consumer", consumer_version: "1", provider: "bar-provider", content_id: "111", tag: "main") + .get_pacts_for_verification( + enable_pending: true, + provider_version_tag: "main", + include_wip_pacts_since: "2020-01-01", + consumer_version_selectors: [{ tag: "main", latest: true }]) + .verify_pact( + index: 0, + provider_version_tag: "main", + provider_version: "1", + success: true + ) + .record_deployment(pacticipant: "bar-provider", version: "1", environment_name: "test") + .record_deployment(pacticipant: "bar-provider", version: "1", environment_name: "prod") + .record_deployment(pacticipant: "foo-consumer", version: "1", environment_name: "prod") + .get_pacts_for_verification( + enable_pending: true, + provider_version_tag: "main", + include_wip_pacts_since: "2020-01-01", + consumer_version_selectors: [{ tag: "main", latest: true }]) + .verify_pact( + index: 0, + provider_version_tag: "main", + provider_version: "2", + success: true + ) + .record_deployment(pacticipant: "bar-provider", version: "2", environment_name: "test") + .create_global_webhook_for_contract_changed(uuid: "7a5da39c-8e50-4cc9-ae16-dfa5be043e8c") + .publish_pact(consumer: "foo-consumer", consumer_version: "2", provider: "bar-provider", content_id: "222", tag: "main") + +rescue StandardError => e + puts "#{e.class} #{e.message}" + puts e.backtrace + exit 1 +end diff --git a/spec/lib/pact_broker/verifications/service_spec.rb b/spec/lib/pact_broker/verifications/service_spec.rb index 266289e8e..bdd9516b9 100644 --- a/spec/lib/pact_broker/verifications/service_spec.rb +++ b/spec/lib/pact_broker/verifications/service_spec.rb @@ -80,6 +80,152 @@ module Verifications end + describe "#calculate_required_verifications_for_pact" do + subject { Service.calculate_required_verifications_for_pact(pact) } + + context "when there is no verification from the latest version from the main branch" do + let!(:pact) do + td.create_consumer("Foo") + .create_provider("Bar", main_branch: "main") + .create_provider_version("1", branch: "main") + .create_consumer_version("1") + .create_pact + .and_return(:pact) + end + + it "returns the required verification for the main branch" do + expect(subject).to eq [ + RequiredVerification.new( + provider_version: td.find_version("Bar", "1"), + provider_version_descriptions: ["latest version from main branch"] + ) + ] + end + end + + context "when there is a verification from the latest version from the main branch" do + let!(:pact) do + td.create_consumer("Foo") + .create_provider("Bar", main_branch: "main") + .create_provider_version("1", branch: "main") + .create_consumer_version("1") + .create_pact + .create_verification(provider_version: "2", branch: "main") + .and_return(:pact) + end + + it "does not return a required verification" do + expect(subject).to eq [] + end + end + + context "when there is no verification for a deployed version" do + let!(:pact) do + td.create_environment("test") + .create_consumer("Foo") + .create_provider("Bar") + .create_provider_version("1") + .create_consumer_version("1") + .create_pact + .create_deployed_version_for_provider_version + .and_return(:pact) + end + + it "returns the required verification for the deployed version" do + expect(subject).to eq [ + RequiredVerification.new( + provider_version: td.find_version("Bar", "1"), + provider_version_descriptions: ["currently deployed version"] + ) + ] + end + end + + context "when there is a verification for a deployed version" do + let!(:pact) do + td.create_environment("test") + .create_consumer("Foo") + .create_provider("Bar") + .create_provider_version("1") + .create_consumer_version("1") + .create_pact + .create_verification(provider_version: "1") + .create_deployed_version_for_provider_version + .and_return(:pact) + end + + it "does not return a required verification for the deployed version" do + expect(subject).to eq [] + end + end + + context "when there is no verification for a released version" do + let!(:pact) do + td.create_environment("test") + .create_consumer("Foo") + .create_provider("Bar") + .create_provider_version("1") + .create_consumer_version("1") + .create_pact + .create_released_version_for_provider_version + .and_return(:pact) + end + + it "returns the required verification for the released version" do + expect(subject).to eq [ + RequiredVerification.new( + provider_version: td.find_version("Bar", "1"), + provider_version_descriptions: ["currently released version"] + ) + ] + end + end + + context "when there is a verification for a released version" do + let!(:pact) do + td.create_environment("test") + .create_consumer("Foo") + .create_provider("Bar") + .create_provider_version("1") + .create_consumer_version("1") + .create_pact + .create_verification(provider_version: "1") + .create_released_version_for_provider_version + .and_return(:pact) + end + + it "does not return a required verification for the deployed version" do + expect(subject).to eq [] + end + end + + context "when the latest version from the main branch is deployed and released and has no verification" do + let!(:pact) do + td.create_environment("test") + .create_consumer("Foo") + .create_provider("Bar", main_branch: "main") + .create_provider_version("1", branch: "main") + .create_consumer_version("1") + .create_pact + .create_deployed_version_for_provider_version + .create_released_version_for_provider_version + .and_return(:pact) + end + + it "deduplicates the required versions" do + expect(subject).to eq [ + RequiredVerification.new( + provider_version: td.find_version("Bar", "1"), + provider_version_descriptions: [ + "latest version from main branch", + "currently deployed version", + "currently released version" + ]) + ] + end + end + end + describe "#errors" do let(:params) { {} } diff --git a/spec/lib/pact_broker/versions/repository_spec.rb b/spec/lib/pact_broker/versions/repository_spec.rb index fb489ce6d..32e4faa92 100644 --- a/spec/lib/pact_broker/versions/repository_spec.rb +++ b/spec/lib/pact_broker/versions/repository_spec.rb @@ -4,8 +4,6 @@ module PactBroker module Versions describe Repository do - - let(:td) { TestDataBuilder.new } let(:pacticipant_name) { "test_pacticipant" } let(:version_number) { "1.2.3" } @@ -176,6 +174,44 @@ module Versions end end end + + describe "#find_latest_version_from_main_branch" do + subject { Repository.new.find_latest_version_from_main_branch(td.find_pacticipant("Foo")) } + + context "when there is a version with the provider's configured main branch as the branch" do + before do + td.create_consumer("Foo", main_branch: "main") + .create_consumer_version("1", branch: "main") + .create_consumer_version("2", branch: "main") + .create_consumer_version("3", tag_name: "main") + .create_consumer_version("4", branch: "not-main") + end + + its(:number) { is_expected.to eq "2" } + end + + context "when there is a version with the provider's configured main branch as the tag" do + before do + td.create_consumer("Foo", main_branch: "main") + .create_consumer_version("1", branch: "not-main") + .create_consumer_version("2", branch: "not-main") + .create_consumer_version("3", tag_name: "main") + .create_consumer_version("4", branch: "not-main") + end + + its(:number) { is_expected.to eq "3" } + end + + context "when there are no versions with a matching branch or tag set" do + before do + td.create_consumer("Foo", main_branch: "main") + .create_consumer_version("1", branch: "not-main") + .create_consumer_version("2", tag_name: "not-main") + end + + it { is_expected.to be_nil } + end + end end end end diff --git a/spec/lib/pact_broker/webhooks/trigger_service_spec.rb b/spec/lib/pact_broker/webhooks/trigger_service_spec.rb index a908f0c91..90a0b714a 100644 --- a/spec/lib/pact_broker/webhooks/trigger_service_spec.rb +++ b/spec/lib/pact_broker/webhooks/trigger_service_spec.rb @@ -62,13 +62,18 @@ def find_result_with_message_including(message) let(:provider) { PactBroker::Domain::Pacticipant.new(name: "Provider") } let(:webhooks) { [webhook]} let(:webhook) do - instance_double(PactBroker::Domain::Webhook, description: "description", uuid: "1244", expand_currently_deployed_provider_versions?: expand_currently_deployed) + instance_double(PactBroker::Domain::Webhook, + description: "description", + uuid: "1244", + expand_currently_deployed_provider_versions?: expand_currently_deployed + ) end let(:expand_currently_deployed) { false } + let(:trigger_on_contract_requiring_verification_published) { false } let(:triggered_webhook) { instance_double(PactBroker::Webhooks::TriggeredWebhook) } let(:event_name) { PactBroker::Webhooks::WebhookEvent::CONTRACT_CONTENT_CHANGED } let(:event_context) { { some: "data" } } - let(:expected_event_context) { { some: "data", event_name: PactBroker::Webhooks::WebhookEvent::CONTRACT_CONTENT_CHANGED } } + let(:expected_event_context) { { some: "data", event_name: event_name } } let(:webhook_repository) { instance_double(Repository, create_triggered_webhook: triggered_webhook, find_webhooks_to_trigger: webhooks) } subject { TriggerService.create_triggered_webhooks_for_event(pact, verification, event_name, event_context) } @@ -111,6 +116,53 @@ def find_result_with_message_including(message) end end + context "when the event is contract_requiring_verification_published" do + before do + allow(TriggerService).to receive(:verification_service).and_return(verification_service) + allow(verification_service).to receive(:calculate_required_verifications_for_pact).and_return(required_verifications) + end + + let(:event_name) { PactBroker::Webhooks::WebhookEvent::CONTRACT_REQUIRING_VERIFICATION_PUBLISHED } + let(:verification_service) { class_double("PactBroker::Verifications::Service").as_stubbed_const } + let(:required_verifications) { [required_verification] } + let(:required_verification) do + instance_double("PactBroker::Verifications::RequiredVerification", + provider_version: double("version", number: "1"), + provider_version_descriptions: ["foo"] + ) + end + + it "creates a triggered webhook for each required verification" do + expect(webhook_repository).to receive(:create_triggered_webhook).with( + anything, + webhook, + pact, + verification, + TriggerService::RESOURCE_CREATION, + PactBroker::Webhooks::WebhookEvent::CONTRACT_REQUIRING_VERIFICATION_PUBLISHED, + expected_event_context.merge(provider_version_number: "1", provider_version_descriptions: ["foo"]) + ) + subject + end + + it "returns the triggered webhooks" do + expect(subject.size).to eq 1 + end + + context "when there are no required verifications" do + let(:required_verifications) { [] } + + it "does not create any triggered webhooks" do + expect(webhook_repository).to_not receive(:create_triggered_webhook) + subject + end + + it "returns an empty array" do + expect(subject).to eq [] + end + end + end + context "when there should be a webhook triggered for each consumer version that had a pact verified" do # let(:triggered_webhooks) { [instance_double(TriggeredWebhook, event_context: { some: 'context'}, webhook: instance_double(Webhook, uuid: "webhook-uuid"))]}