diff --git a/lib/pact_broker/pacts/selector.rb b/lib/pact_broker/pacts/selector.rb index 2f88154a7..992915906 100644 --- a/lib/pact_broker/pacts/selector.rb +++ b/lib/pact_broker/pacts/selector.rb @@ -136,83 +136,83 @@ def in_environment? end def self.overall_latest - Selector.new(latest: true) + new(latest: true) end def self.for_main_branch - Selector.new(main_branch: true) + new(main_branch: true) end def self.latest_for_tag(tag) - Selector.new(latest: true, tag: tag) + new(latest: true, tag: tag) end def self.latest_for_branch(branch) - Selector.new(latest: true, branch: branch) + new(latest: true, branch: branch) end def self.latest_for_tag_with_fallback(tag, fallback_tag) - Selector.new(latest: true, tag: tag, fallback_tag: fallback_tag) + new(latest: true, tag: tag, fallback_tag: fallback_tag) end def self.latest_for_branch_with_fallback(branch, fallback_branch) - Selector.new(latest: true, branch: branch, fallback_branch: fallback_branch) + new(latest: true, branch: branch, fallback_branch: fallback_branch) end def self.all_for_tag(tag) - Selector.new(tag: tag) + new(tag: tag) end def self.all_for_tag_and_consumer(tag, consumer) - Selector.new(tag: tag, consumer: consumer) + new(tag: tag, consumer: consumer) end def self.latest_for_tag_and_consumer(tag, consumer) - Selector.new(latest: true, tag: tag, consumer: consumer) + new(latest: true, tag: tag, consumer: consumer) end def self.latest_for_branch_and_consumer(branch, consumer) - Selector.new(latest: true, branch: branch, consumer: consumer) + new(latest: true, branch: branch, consumer: consumer) end def self.latest_for_consumer(consumer) - Selector.new(latest: true, consumer: consumer) + new(latest: true, consumer: consumer) end def self.for_currently_deployed(environment_name = nil) - Selector.new( { currently_deployed: true, environment_name: environment_name }.compact ) + new( { currently_deployed: true, environment_name: environment_name }.compact ) end def self.for_currently_supported(environment_name = nil) - Selector.new( { currently_supported: true, environment_name: environment_name }.compact ) + new( { currently_supported: true, environment_name: environment_name }.compact ) end def self.for_currently_deployed_and_consumer(consumer) - Selector.new(currently_deployed: true, consumer: consumer) + new(currently_deployed: true, consumer: consumer) end def self.for_currently_deployed_and_environment_and_consumer(environment_name, consumer) - Selector.new(currently_deployed: true, environment_name: environment_name, consumer: consumer) + new(currently_deployed: true, environment_name: environment_name, consumer: consumer) end def self.for_currently_supported_and_environment_and_consumer(environment_name, consumer) - Selector.new(currently_supported: true, environment_name: environment_name, consumer: consumer) + new(currently_supported: true, environment_name: environment_name, consumer: consumer) end def self.for_environment(environment_name) - Selector.new(environment_name: environment_name) + new(environment_name: environment_name) end def self.for_environment_and_consumer(environment_name, consumer) - Selector.new(environment_name: environment_name, consumer: consumer) + new(environment_name: environment_name, consumer: consumer) end def self.from_hash hash - Selector.new(hash) + new(hash) end def for_consumer(consumer) - Selector.new(to_h.merge(consumer: consumer)) + self.class.new(to_h.merge(consumer: consumer)) end def latest_for_main_branch? diff --git a/lib/pact_broker/verifications/required_verification.rb b/lib/pact_broker/verifications/required_verification.rb index 2cee65f1a..0e91f3f5e 100644 --- a/lib/pact_broker/verifications/required_verification.rb +++ b/lib/pact_broker/verifications/required_verification.rb @@ -1,7 +1,7 @@ module PactBroker module Verifications class RequiredVerification - attr_reader :provider_version, :provider_version_descriptions + attr_reader :provider_version, :provider_version_selectors, :provider_version_descriptions def initialize(attributes = {}) attributes.each do | (name, value) | @@ -10,7 +10,9 @@ def initialize(attributes = {}) end def == other - provider_version == other.provider_version && provider_version_descriptions == other.provider_version_descriptions + provider_version == other.provider_version && + provider_version_selectors == other.provider_version_selectors && + provider_version_descriptions == other.provider_version_descriptions end def + other @@ -20,6 +22,7 @@ def + other RequiredVerification.new( provider_version: provider_version, + provider_version_selectors: provider_version_selectors + other.provider_version_selectors, provider_version_descriptions: (provider_version_descriptions + other.provider_version_descriptions).uniq ) end diff --git a/lib/pact_broker/verifications/service.rb b/lib/pact_broker/verifications/service.rb index 4fe52e055..cf47956bc 100644 --- a/lib/pact_broker/verifications/service.rb +++ b/lib/pact_broker/verifications/service.rb @@ -6,6 +6,8 @@ require "pact_broker/hash_refinements" require "pact_broker/events/publisher" require "pact_broker/verifications/required_verification" +require "pact_broker/versions/selector" +require "pact_broker/versions/selectors" module PactBroker module Verifications @@ -101,7 +103,7 @@ def delete_all_verifications_between(consumer_name, options) 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 = required_verifications_for_main_branch(pact_version, pact.provider.main_branch) + required_verifications_for_deployed_versions(pact_version) + required_verifications_for_released_versions(pact_version) required_verifications @@ -126,19 +128,24 @@ def broadcast_events(verification, pact, event_context) end private :broadcast_events - def identify_required_verification(pact_version, provider_version, description) + def identify_required_verification(pact_version, provider_version, description, provider_version_selector) 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]) + RequiredVerification.new( + provider_version: provider_version, + provider_version_selectors: PactBroker::Versions::Selectors.new(provider_version_selector), + provider_version_descriptions: [description] + ) end end private :identify_required_verification - def required_verifications_for_main_branch(pact_version) + def required_verifications_for_main_branch(pact_version, main_branch_name) 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 from main branch") + selector = PactBroker::Versions::Selector.for_main_branch.resolve_for_branch(main_branch_version, main_branch_name) + identify_required_verification(pact_version, main_branch_version, "latest from main branch", selector) end.compact end private :required_verifications_for_main_branch @@ -148,7 +155,7 @@ def required_verifications_for_deployed_versions(pact_version) 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, "deployed in #{deployed_version.environment_name}") + identify_required_verification(pact_version, deployed_version.version, "deployed in #{deployed_version.environment_name}", PactBroker::Versions::Selector.for_currently_deployed) end.compact end private :required_verifications_for_deployed_versions @@ -158,7 +165,7 @@ def required_verifications_for_released_versions(pact_version) 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, "released in #{released_version.environment_name}") + identify_required_verification(pact_version, released_version.version, "released in #{released_version.environment_name}", PactBroker::Versions::Selector.for_currently_supported) end.compact end private :required_verifications_for_released_versions diff --git a/lib/pact_broker/versions/selector.rb b/lib/pact_broker/versions/selector.rb new file mode 100644 index 000000000..ca55f5209 --- /dev/null +++ b/lib/pact_broker/versions/selector.rb @@ -0,0 +1,32 @@ +require "pact_broker/pacts/selector" +require "pact_broker/hash_refinements" + +module PactBroker + module Versions + class Selector < PactBroker::Pacts::Selector + def resolve_for_branch(consumer_version, resolved_branch_name) + # Need to rename branch to branch_name + ResolvedSelector.new(self.to_h.merge({ resolved_branch_name: resolved_branch_name }.compact), consumer_version) + end + end + + class ResolvedSelector < PactBroker::Pacts::ResolvedSelector + using PactBroker::HashRefinements + + PROPERTY_NAMES = PactBroker::Pacts::Selector::PROPERTY_NAMES + [:version, :resolved_branch_name] + + def initialize(properties = {}, version) + properties.without(*PROPERTY_NAMES).tap { |it| warn("WARN: Unsupported property for #{self.class.name}: #{it.keys.join(", ")} at #{caller[0..3]}") if it.any? } + merge!(properties.merge(version: version)) + end + + def resolved_branch_name + self[:resolved_branch_name] + end + + def version + self[:version] + end + end + end +end diff --git a/lib/pact_broker/versions/selectors.rb b/lib/pact_broker/versions/selectors.rb new file mode 100644 index 000000000..8cfc2b86c --- /dev/null +++ b/lib/pact_broker/versions/selectors.rb @@ -0,0 +1,20 @@ +require "pact_broker/pacts/selector" +require "pact_broker/versions/selector" + +module PactBroker + module Versions + class Selectors < Array + def initialize *selectors + super([*selectors].flatten) + end + + def + other + Selectors.new(super) + end + + def sort + Selectors.new(super) + end + end + end +end diff --git a/lib/pact_broker/webhooks/pact_and_verification_parameters.rb b/lib/pact_broker/webhooks/pact_and_verification_parameters.rb index 20353ba06..9f8b62356 100644 --- a/lib/pact_broker/webhooks/pact_and_verification_parameters.rb +++ b/lib/pact_broker/webhooks/pact_and_verification_parameters.rb @@ -171,8 +171,8 @@ def provider_version_number end def provider_version_branch - if webhook_context[:provider_version_branch] - webhook_context[:provider_version_branch] + if webhook_context.key?(:provider_version_branch) + webhook_context[:provider_version_branch] || "" else verification&.provider_version&.branch || "" end diff --git a/lib/pact_broker/webhooks/trigger_service.rb b/lib/pact_broker/webhooks/trigger_service.rb index 444684f27..e0f1a31db 100644 --- a/lib/pact_broker/webhooks/trigger_service.rb +++ b/lib/pact_broker/webhooks/trigger_service.rb @@ -135,13 +135,21 @@ def expand_events_for_required_verifications(event_name, pact, event_contexts) 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.uniq) + event_context.merge( + provider_version_number: required_verification.provider_version.number, + provider_version_branch: provider_version_branch_for_required_verification(required_verification, ), + provider_version_descriptions: required_verification.provider_version_descriptions.uniq + ) end end else event_contexts end end + + def provider_version_branch_for_required_verification(required_verification) + required_verification.provider_version_selectors.find(&:latest_for_main_branch?)&.resolved_branch_name || required_verification.provider_version.branch_versions.last&.branch_name + end end end end diff --git a/spec/lib/pact_broker/verifications/service_spec.rb b/spec/lib/pact_broker/verifications/service_spec.rb index f57db42dc..1b1eb71f0 100644 --- a/spec/lib/pact_broker/verifications/service_spec.rb +++ b/spec/lib/pact_broker/verifications/service_spec.rb @@ -112,12 +112,15 @@ module Verifications end it "returns the required verification for the main branch" do - expect(subject).to eq [ - RequiredVerification.new( + expect(subject).to contain_exactly( + have_attributes( provider_version: td.find_version("Bar", "1"), + provider_version_selectors: contain_exactly( + have_attributes(resolved_branch_name: "main", main_branch: true) + ), provider_version_descriptions: ["latest from main branch"] ) - ] + ) end end @@ -153,6 +156,7 @@ module Verifications expect(subject).to eq [ RequiredVerification.new( provider_version: td.find_version("Bar", "1"), + provider_version_selectors: PactBroker::Versions::Selectors.new(PactBroker::Versions::Selector.for_currently_deployed), provider_version_descriptions: ["deployed in test"] ) ] @@ -193,6 +197,7 @@ module Verifications expect(subject).to eq [ RequiredVerification.new( provider_version: td.find_version("Bar", "1"), + provider_version_selectors: PactBroker::Versions::Selectors.new(PactBroker::Versions::Selector.for_currently_supported), provider_version_descriptions: ["released in test"] ) ] @@ -231,15 +236,16 @@ module Verifications end it "deduplicates the required versions" do - expect(subject).to eq [ - RequiredVerification.new( - provider_version: td.find_version("Bar", "1"), - provider_version_descriptions: [ - "latest from main branch", - "deployed in test", - "released in test" - ]) - ] + expect(subject.size).to eq 1 + expect(subject.first.provider_version).to eq td.find_version("Bar", "1") + expect(subject.first.provider_version_selectors[0]).to have_attributes(main_branch: true, resolved_branch_name: "main") + expect(subject.first.provider_version_selectors[1]).to eq PactBroker::Versions::Selector.for_currently_deployed + expect(subject.first.provider_version_selectors[2]).to eq PactBroker::Versions::Selector.for_currently_supported + expect(subject.first.provider_version_descriptions).to eq [ + "latest from main branch", + "deployed in test", + "released in test" + ] 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 4c0a58931..442a1fdc3 100644 --- a/spec/lib/pact_broker/webhooks/trigger_service_spec.rb +++ b/spec/lib/pact_broker/webhooks/trigger_service_spec.rb @@ -1,4 +1,6 @@ require "pact_broker/webhooks/trigger_service" +require "pact_broker/versions/selectors" +require "pact_broker/versions/selector" module PactBroker module Webhooks @@ -59,7 +61,8 @@ def find_result_with_message_including(message) let(:pact) { instance_double(PactBroker::Domain::Pact, consumer: consumer, provider: provider, consumer_version: consumer_version)} let(:consumer_version) { PactBroker::Domain::Version.new(number: "1.2.3") } let(:consumer) { PactBroker::Domain::Pacticipant.new(name: "Consumer") } - let(:provider) { PactBroker::Domain::Pacticipant.new(name: "Provider") } + let(:provider) { PactBroker::Domain::Pacticipant.new(name: "Provider", main_branch: provider_main_branch) } + let(:provider_main_branch) { "main" } let(:webhooks) { [webhook]} let(:webhook) do instance_double(PactBroker::Domain::Webhook, @@ -127,10 +130,14 @@ def find_result_with_message_including(message) let(:required_verifications) { [required_verification] } let(:required_verification) do instance_double("PactBroker::Verifications::RequiredVerification", - provider_version: double("version", number: "1"), + provider_version: double("version", number: "1", branch_versions: branch_versions), + provider_version_selectors: provider_version_selectors, provider_version_descriptions: ["foo"] ) end + let(:provider_version_selectors) { [instance_double("PactBroker::Versions::ResolvedSelector", latest_for_main_branch?: latest_for_main_branch, resolved_branch_name: "the-main-branch")] } + let(:latest_for_main_branch) { true } + let(:branch_versions) { [instance_double("PactBroker::Versions::BranchVersion", branch_name: "main")] } it "creates a triggered webhook for each required verification" do expect(webhook_repository).to receive(:create_triggered_webhook).with( @@ -140,7 +147,7 @@ def find_result_with_message_including(message) verification, TriggerService::RESOURCE_CREATION, PactBroker::Webhooks::WebhookEvent::CONTRACT_REQUIRING_VERIFICATION_PUBLISHED, - expected_event_context.merge(provider_version_number: "1", provider_version_descriptions: ["foo"]) + expected_event_context.merge(provider_version_number: "1", provider_version_branch: "the-main-branch", provider_version_descriptions: ["foo"]) ) subject end @@ -149,6 +156,63 @@ def find_result_with_message_including(message) expect(subject.size).to eq 1 end + context "when a version is required because it was the main branch" do + it "uses the main branch" do + expect(webhook_repository).to receive(:create_triggered_webhook).with( + anything, + anything, + anything, + anything, + anything, + anything, + hash_including(provider_version_branch: "the-main-branch") + ) + subject + end + end + + context "when a version was required but not because it was for the main branch" do + let(:latest_for_main_branch) { false } + + context "when there are multiple branches" do + let(:branch_versions) do + [ + instance_double("PactBroker::Versions::BranchVersion", branch_name: "foo"), + instance_double("PactBroker::Versions::BranchVersion", branch_name: "bar") + ] + end + it "uses the last branch" do + expect(webhook_repository).to receive(:create_triggered_webhook).with( + anything, + anything, + anything, + anything, + anything, + anything, + hash_including(provider_version_branch: "bar") + ) + subject + end + end + + context "when there are no branches" do + let(:branch_versions) { [] } + + it "uses nil" do + expect(webhook_repository).to receive(:create_triggered_webhook).with( + anything, + anything, + anything, + anything, + anything, + anything, + hash_including(provider_version_branch: nil) + ) + subject + end + end + end + context "when there are no required verifications" do let(:required_verifications) { [] }