diff --git a/lib/pact_broker/pacts/repository.rb b/lib/pact_broker/pacts/repository.rb index da901a748..0318cad91 100644 --- a/lib/pact_broker/pacts/repository.rb +++ b/lib/pact_broker/pacts/repository.rb @@ -125,22 +125,58 @@ def find_latest_pact_versions_for_provider provider_name, tag = nil end end - def find_wip_pact_versions_for_provider provider_name, provider_tags = [], options = {} - return [] if provider_tags.empty? + # To find the work in progress pacts for this verification execution: + # For each provider tag that will be applied to this verification result (usually there will just be one, but + # we have to allow for multiple tags), + # find the head pacts (the pacts that are the latest for their tag) that have been successfully + # verified against the provider tag. + # Then, find all the head pacts, and remove the ones that have been successfully verified by ALL + # of the provider tags supplied. + # Then, for all of the head pacts that are remaining (these are the WIP ones) work out which + # provider tags they are pending for. + # Don't include pact publications that were created + def find_wip_pact_versions_for_provider provider_name, provider_tags_names = [], options = {} + return [] if provider_tags_names.empty? + + provider = pacticipant_repository.find_by_name(provider_name) + + # Hash of provider tag names => list of head pacts + successfully_verified_head_pacts_for_provider_tags = find_successfully_verified_head_pacts_by_provider_tag(provider_name, provider_tags_names, options) + successfully_verified_head_pact_publication_ids_for_each_provider_tag = successfully_verified_head_pacts_for_provider_tags.each_with_object({}) do | (provider_tag_name, head_pacts), hash | + hash[provider_tag_name] = head_pacts.collect(&:id) + end - # Hash of provider tag names => list of pact_publication_ids - successfully_verified_head_pact_publication_ids_for_each_provider_tag = find_successfully_verified_head_pacts_by_provider_tag(provider_name, provider_tags, options) + # list of pact_publication_ids that are NOT work in progress + head_pact_publication_ids_successully_verified_by_all_provider_tags = successfully_verified_head_pacts_for_provider_tags.values.collect{ |head_pacts| head_pacts.collect(&:id) }.reduce(:&) pact_publication_ids = find_head_pacts_that_have_not_been_successfully_verified_by_all_provider_tags( provider_name, - successfully_verified_head_pact_publication_ids_for_each_provider_tag.values.reduce(:&), + head_pact_publication_ids_successully_verified_by_all_provider_tags, options) - pacts = AllPactPublications.where(id: pact_publication_ids).order_ignore_case(:consumer_name).order_append(:consumer_version_order).collect(&:to_domain) + pacts = AllPactPublications.where(id: pact_publication_ids).order_ignore_case(:consumer_name).order_append(:consumer_version_order) + + # The first instance (by date) of each provider tag with that name + # Note: created_at is coming back as a string + provider_tag_collection = PactBroker::Domain::Tag + .select_group(Sequel[:tags][:name], Sequel[:pacticipant_id]) + .select_append(Sequel.function(:min, Sequel[:tags][:created_at]).as(:created_at)) + .distinct + .join(:versions, { Sequel[:tags][:version_id] => Sequel[:versions][:id] } ) + .where(pacticipant_id: provider.id) + .where(name: provider_tags_names) + .all + pacts.collect do | pact| - pending_tags = find_provider_tags_for_which_pact_publication_id_is_pending(pact.id, successfully_verified_head_pact_publication_ids_for_each_provider_tag) - VerifiablePact.new(pact, true, pending_tags, [], pact.consumer_version_tag_names, nil, true) - end + pending_tag_names = find_provider_tags_for_which_pact_publication_id_is_pending(pact, successfully_verified_head_pact_publication_ids_for_each_provider_tag) + pre_existing_tag_names = find_provider_tag_names_that_were_first_used_before_pact_published(pact, provider_tag_collection) + + pre_existing_pending_tags = pending_tag_names & pre_existing_tag_names + + if pre_existing_pending_tags.any? + VerifiablePact.new(pact.to_domain, true, pre_existing_pending_tags, [], pact.head_tag_names, nil, true) + end + end.compact end def find_pact_versions_for_provider provider_name, tag = nil @@ -333,13 +369,17 @@ def find_all_database_versions_between(consumer_name, options, base_class = Late query end - def find_provider_tags_for_which_pact_publication_id_is_pending(pact_publication_id, successfully_verified_head_pact_publication_ids_for_each_provider_tag) + def find_provider_tags_for_which_pact_publication_id_is_pending(pact_publication, successfully_verified_head_pact_publication_ids_for_each_provider_tag) successfully_verified_head_pact_publication_ids_for_each_provider_tag - .select do | provider_tag, pact_publication_ids | - !pact_publication_ids.include?(pact_publication_id) + .select do | _, pact_publication_ids | + !pact_publication_ids.include?(pact_publication.id) end.keys end + def find_provider_tag_names_that_were_first_used_before_pact_published(pact_publication, provider_tag_collection) + provider_tag_collection.select { | tag| DateTime.parse(tag.created_at) < pact_publication.created_at }.collect(&:name) + end + def find_head_pacts_that_have_not_been_successfully_verified_by_all_provider_tags(provider_name, pact_publication_ids_successfully_verified_by_all_provider_tags, options) # Exclude the head pacts that have been successfully verified by all the specified provider tags pact_publication_ids = LatestTaggedPactPublications @@ -349,18 +389,20 @@ def find_head_pacts_that_have_not_been_successfully_verified_by_all_provider_tag .select_for_subquery(:id) end + # Find the head pacts that have been successfully verified by a provider version with the specified tags + # Returns a Hash of provider_tag => LatestTaggedPactPublications with only id and tag_name populated def find_successfully_verified_head_pacts_by_provider_tag(provider_name, provider_tags, options) - provider_tags.compact.each_with_object({}) do | provider_tag, tag_to_ids_hash | - ids = LatestTaggedPactPublications + provider_tags.compact.each_with_object({}) do | provider_tag, hash | + head_pacts = LatestTaggedPactPublications .join(:verifications, { pact_version_id: :pact_version_id }) .join(:tags, { Sequel[:verifications][:provider_version_id] => Sequel[:provider_tags][:version_id] }, {table_alias: :provider_tags}) .where(Sequel[:provider_tags][:name] => provider_tag) .provider(provider_name) .where(Sequel[:verifications][:success] => true) - .where(Sequel.lit('latest_tagged_pact_publications.created_at > ?', options.fetch(:include_wip_pacts_since))) - .select(Sequel[:latest_tagged_pact_publications][:id].as(:id)) - .collect(&:id) - tag_to_ids_hash[provider_tag] = ids + .or(Sequel.lit('latest_tagged_pact_publications.created_at < ?', options.fetch(:include_wip_pacts_since))) + .select(Sequel[:latest_tagged_pact_publications][:id].as(:id), :tag_name) + .all + hash[provider_tag] = head_pacts end end end diff --git a/spec/lib/pact_broker/pacts/repository_find_wip_pact_versions_for_provider_spec.rb b/spec/lib/pact_broker/pacts/repository_find_wip_pact_versions_for_provider_spec.rb index 83c137332..8cad613e9 100644 --- a/spec/lib/pact_broker/pacts/repository_find_wip_pact_versions_for_provider_spec.rb +++ b/spec/lib/pact_broker/pacts/repository_find_wip_pact_versions_for_provider_spec.rb @@ -39,7 +39,13 @@ module Pacts context "when the latest pact for a tag has been successfully verified by one of the given provider tags, but not the other" do before do - td.create_pact_with_hierarchy("foo", "1", "bar") + td.create_provider("bar") + .create_provider_version("44") + .create_provider_version_tag("feat-1") + .add_day + .create_consumer("foo") + .create_consumer_version("1") + .create_pact .create_consumer_version_tag("prod") .create_verification(provider_version: "3", tag_names: %w[dev], comment: "not included because already verified") end @@ -57,7 +63,11 @@ module Pacts context "when the latest pact for a tag has failed verification from the specified provider version" do before do - td.create_pact_with_hierarchy("foo", "1", "bar") + td.create_provider("bar") + .create_provider_version("333") + .create_provider_version_tag("dev") + .add_day + .create_pact_with_hierarchy("foo", "1", "bar") .create_consumer_version_tag("feat-1") .create_verification(provider_version: "3", success: false, tag_names: %[dev]) end @@ -84,7 +94,11 @@ module Pacts context "when the latest pact for a tag has successful and failed verifications" do before do - td.create_pact_with_hierarchy("foo", "1", "bar") + td.create_provider("bar") + .create_provider_version("333") + .create_provider_version_tag("dev") + .add_day + .create_pact_with_hierarchy("foo", "1", "bar") .create_consumer_version_tag("dev") .create_verification(provider_version: "3", success: true, tag_names: %[dev]) .create_verification(provider_version: "5", success: false, number: 2, tag_names: %[dev]) @@ -97,8 +111,12 @@ module Pacts context "when the latest pact for a tag has not been verified" do before do - td.create_pact_with_hierarchy("foo", "1", "bar") - .create_consumer_version_tag("dev") + td.create_provider("bar") + .create_provider_version("333") + .create_provider_version_tag("dev") + .add_day + .create_pact_with_hierarchy("foo", "1", "bar") + .create_consumer_version_tag("feat-1") end it "is included" do @@ -123,7 +141,11 @@ module Pacts context "when the pact was published before the specified include_wip_pacts_since" do before do - td.create_pact_with_hierarchy("foo", "1", "bar") + td.create_provider("bar") + .create_provider_version("333") + .create_provider_version_tag("dev") + .add_day + .create_pact_with_hierarchy("foo", "1", "bar") .create_consumer_version_tag("prod") end @@ -133,6 +155,51 @@ module Pacts expect(subject.size).to be 0 end end + + context "when the first provider tag with a given name was created after the head pact was created" do + before do + td.create_pact_with_hierarchy("foo", "1", "bar") + .create_consumer_version_tag("feat-x") + .add_day + .create_provider_version("5") + .create_provider_version_tag(provider_tags.first) + end + + it "doesn't return any pacts" do + expect(subject.size).to be 0 + end + end + + context "when the provider tag does not exist yet" do + before do + td.create_pact_with_hierarchy("foo", "1", "bar") + .create_consumer_version_tag("feat-x") + end + + it "doesn't return any pacts" do + expect(subject.size).to be 0 + end + end + + context "when a pact was published between the first creation date of two provider tags" do + let(:provider_tags) { %w[dev feat-1] } + + before do + td.create_provider("bar") + .create_provider_version("4") + .create_provider_version_tag(provider_tags.first) + .add_day + .create_pact_with_hierarchy("foo", "1", "bar") + .create_consumer_version_tag("feat-x") + .add_day + .create_provider_version("5") + .create_provider_version_tag(provider_tags.last) + end + + it "is wip for the first tag but not the second" do + expect(subject.first.pending_provider_tags).to eq [provider_tags.first] + end + end end end end