diff --git a/lib/pact_broker/api/contracts/verifiable_pacts_json_query_schema.rb b/lib/pact_broker/api/contracts/verifiable_pacts_json_query_schema.rb index ef903d0dc..361444bbb 100644 --- a/lib/pact_broker/api/contracts/verifiable_pacts_json_query_schema.rb +++ b/lib/pact_broker/api/contracts/verifiable_pacts_json_query_schema.rb @@ -1,5 +1,6 @@ require 'dry-validation' require 'pact_broker/hash_refinements' +require 'pact_broker/string_refinements' require 'pact_broker/api/contracts/dry_validation_workarounds' require 'pact_broker/api/contracts/dry_validation_predicates' @@ -9,6 +10,7 @@ module Contracts class VerifiablePactsJSONQuerySchema extend DryValidationWorkarounds using PactBroker::HashRefinements + using PactBroker::StringRefinements SCHEMA = Dry::Validation.Schema do configure do @@ -24,7 +26,7 @@ class VerifiablePactsJSONQuerySchema # end # end - required(:tag).filled(:str?) + optional(:tag).filled(:str?) optional(:latest).filled(included_in?: [true, false]) optional(:fallbackTag).filled(:str?) optional(:consumer).filled(:str?, :not_blank?) @@ -54,8 +56,9 @@ def self.add_cross_field_validation_errors(params, results) errors << "fallbackTag can only be set if latest is true (at index #{index})" end - if selector[:consumer] && selector[:latest] - errors << "specifying a consumer with latest == true is not yet supported (at index #{index})" + + if not_provided?(selector[:tag]) && selector[:latest] != true + errors << "latest must be true, or a tag must be provided (at index #{index})" end end if errors.any? @@ -64,6 +67,10 @@ def self.add_cross_field_validation_errors(params, results) end end end + + def self.not_provided?(value) + value.nil? || value.blank? + end end end end diff --git a/lib/pact_broker/api/contracts/verifiable_pacts_query_schema.rb b/lib/pact_broker/api/contracts/verifiable_pacts_query_schema.rb index 62b9a9720..18498a6c2 100644 --- a/lib/pact_broker/api/contracts/verifiable_pacts_query_schema.rb +++ b/lib/pact_broker/api/contracts/verifiable_pacts_query_schema.rb @@ -30,6 +30,7 @@ class VerifiablePactsQuerySchema def self.call(params) select_first_message(flatten_indexed_messages(SCHEMA.call(params&.symbolize_keys).messages(full: true))) end + end end end diff --git a/lib/pact_broker/pacts/repository.rb b/lib/pact_broker/pacts/repository.rb index d81f22762..6f2b1be36 100644 --- a/lib/pact_broker/pacts/repository.rb +++ b/lib/pact_broker/pacts/repository.rb @@ -405,42 +405,37 @@ def find_pacts_for_which_the_latest_version_is_required(provider_name, consumer_ SelectedPact.new(pact_publication.to_domain, Selectors.create_for_overall_latest) end else - [] + selectors_for_overall_latest = consumer_version_selectors.select(&:overall_latest?) + selectors_for_overall_latest.flat_map do | selector | + query = scope_for(LatestPactPublications).provider(provider_name) + query = query.consumer(selector.consumer) if selector.consumer + query.collect do | latest_pact_publication | + pact_publication = PactPublication.find(id: latest_pact_publication.id) + resolved_selector = selector.consumer ? Selector.latest_for_consumer(selector.consumer) : Selector.overall_latest + SelectedPact.new(pact_publication.to_domain, Selectors.new(resolved_selector)) + end + end end end def find_pacts_for_which_the_latest_version_for_the_tag_is_required(provider_name, consumer_version_selectors) # The tags for which only the latest version is specified - tag_names = consumer_version_selectors.tag_names_of_selectors_for_latest_pacts + selectors = consumer_version_selectors.select(&:latest_for_tag?) - # TODO make this an efficient query! - # These are not yet de-duped. Should make the behaviour consistent between this and find_pacts_for_which_all_versions_for_the_tag_are_required ? - if tag_names.any? - scope_for(LatestTaggedPactPublications) - .provider(provider_name) - .where(tag_name: tag_names) - .all - .group_by(&:pact_version_id) - .values - .collect do | pact_publications | - create_selected_pact(pact_publications) - end - else - [] + selectors.flat_map do | selector | + query = scope_for(LatestTaggedPactPublications).where(tag_name: selector.tag).provider(provider_name) + query = query.consumer(selector.consumer) if selector.consumer + query.all.collect do | latest_tagged_pact_publication | + pact_publication = PactPublication.find(id: latest_tagged_pact_publication.id) + resolved_pact = selector.consumer ? Selector.latest_for_tag_and_consumer(selector.tag, selector.consumer) : Selector.latest_for_tag(selector.tag) + SelectedPact.new( + pact_publication.to_domain, + Selectors.new(resolved_pact) + ) + end end end - def create_selected_pact(pact_publications) - selector_tag_names = pact_publications.collect(&:tag_name) - selectors = Selectors.create_for_latest_of_each_tag(selector_tag_names) - last_pact_publication = pact_publications.sort_by(&:consumer_version_order).last - pact_publication = scope_for(PactPublication).find(id: last_pact_publication.id) - SelectedPact.new( - pact_publication.to_domain, - selectors - ) - end - def create_fallback_selected_pact(pact_publications, consumer_version_selectors) selector_tag_names = pact_publications.collect(&:tag_name) selectors = Selectors.create_for_latest_fallback_of_each_tag(selector_tag_names) @@ -454,10 +449,9 @@ def create_fallback_selected_pact(pact_publications, consumer_version_selectors) def find_pacts_for_which_the_latest_version_for_the_fallback_tag_is_required(provider_name, selectors) selectors.collect do | selector | - scope_for(LatestTaggedPactPublications) - .provider(provider_name) - .where(tag_name: selector.fallback_tag) - .all + query = scope_for(LatestTaggedPactPublications).provider(provider_name).where(tag_name: selector.fallback_tag) + query = query.consumer(selector.consumer) if selector.consumer + query.all .collect do | latest_tagged_pact_publication | pact_publication = unscoped(PactPublication).find(id: latest_tagged_pact_publication.id) SelectedPact.new( diff --git a/lib/pact_broker/pacts/selector.rb b/lib/pact_broker/pacts/selector.rb index 4f6ce26c6..1a0e03be1 100644 --- a/lib/pact_broker/pacts/selector.rb +++ b/lib/pact_broker/pacts/selector.rb @@ -53,6 +53,14 @@ def self.all_for_tag_and_consumer(tag, consumer) Selector.new(tag: tag, consumer: consumer) end + def self.latest_for_tag_and_consumer(tag, consumer) + Selector.new(latest: true, tag: tag, consumer: consumer) + end + + def self.latest_for_consumer(consumer) + Selector.new(latest: true, consumer: consumer) + end + def self.from_hash hash Selector.new(hash) end diff --git a/spec/lib/pact_broker/api/contracts/verifiable_pacts_json_query_schema_spec.rb b/spec/lib/pact_broker/api/contracts/verifiable_pacts_json_query_schema_spec.rb index 34ec8b2c6..8445e8e8c 100644 --- a/spec/lib/pact_broker/api/contracts/verifiable_pacts_json_query_schema_spec.rb +++ b/spec/lib/pact_broker/api/contracts/verifiable_pacts_json_query_schema_spec.rb @@ -71,6 +71,27 @@ module Contracts end end + context "when the latest version for a particular consumer is requested" do + let(:consumer_version_selectors) do + [{ + consumer: "Foo", + latest: true + }] + end + + it { is_expected.to be_empty } + end + + context "when the latest version for all is requested" do + let(:consumer_version_selectors) do + [{ + latest: true + }] + end + + it { is_expected.to be_empty } + end + context "when providerVersionTags is not an array" do let(:provider_version_tags) { true } @@ -89,7 +110,7 @@ module Contracts end it "flattens the messages" do - expect(subject[:consumerVersionSelectors].first).to eq "tag is missing at index 0" + expect(subject[:consumerVersionSelectors].first).to eq "latest must be true, or a tag must be provided (at index 0)" end end @@ -150,9 +171,7 @@ module Contracts }] end - it "has an error" do - expect(subject[:consumerVersionSelectors].first).to include "not yet supported" - end + it { is_expected.to be_empty } end end end diff --git a/spec/lib/pact_broker/pacts/repository_find_for_verification_fallback_spec.rb b/spec/lib/pact_broker/pacts/repository_find_for_verification_fallback_spec.rb index 95ca84e22..823ea1272 100644 --- a/spec/lib/pact_broker/pacts/repository_find_for_verification_fallback_spec.rb +++ b/spec/lib/pact_broker/pacts/repository_find_for_verification_fallback_spec.rb @@ -56,6 +56,20 @@ def find_by_consumer_name_and_consumer_version_number(consumer_name, consumer_ve it "sets the latest flag on the selector" do expect(find_by_consumer_version_number("1").selectors.first.latest).to be true end + + context "when a consumer is specified" do + before do + td.create_pact_with_consumer_version_tag("Foo2", "3", "master", "Bar") + end + + let(:selector) { Selector.new(tag: tag, fallback_tag: fallback_tag, latest: true, consumer: "Foo") } + + it "only returns the pacts for the consumer" do + expect(subject.size).to eq 1 + expect(subject.first.consumer.name).to eq "Foo" + expect(subject.first.selectors.first).to eq selector + end + end end context "when a pact does not exist for either tag or fallback_tag" do diff --git a/spec/lib/pact_broker/pacts/repository_find_for_verification_spec.rb b/spec/lib/pact_broker/pacts/repository_find_for_verification_spec.rb index 100640aa2..410c43282 100644 --- a/spec/lib/pact_broker/pacts/repository_find_for_verification_spec.rb +++ b/spec/lib/pact_broker/pacts/repository_find_for_verification_spec.rb @@ -40,6 +40,68 @@ def find_by_consumer_name_and_consumer_version_number(consumer_name, consumer_ve end end + context "when the selector is latest: true" do + let(:pact_selector_1) { Selector.overall_latest } + let(:consumer_version_selectors) do + Selectors.new(pact_selector_1) + end + + before do + td.create_pact_with_hierarchy("Foo1", "1", "Bar") + .create_pact_with_hierarchy("Foo1", "2", "Bar") + .create_pact_with_hierarchy("Foo2", "3", "Bar") + .create_pact_with_hierarchy("Foo2", "4", "Bar2") + end + + it "returns the latest pact for each consumer" do + expect(subject.size).to eq 2 + expect(find_by_consumer_name_and_consumer_version_number("Foo1", "2").selectors).to eq [Selector.overall_latest] + expect(find_by_consumer_name_and_consumer_version_number("Foo2", "3").selectors).to eq [Selector.overall_latest] + end + end + + context "when the selector is latest: true for a particular consumer" do + let(:pact_selector_1) { Selector.latest_for_consumer("Foo1") } + + let(:consumer_version_selectors) do + Selectors.new(pact_selector_1) + end + + before do + td.create_pact_with_hierarchy("Foo1", "1", "Bar") + .create_pact_with_hierarchy("Foo1", "2", "Bar") + .create_pact_with_hierarchy("Foo2", "2", "Bar") + .create_pact_with_hierarchy("Foo2", "2", "Bar2") + end + + it "returns the latest pact for each consumer" do + expect(subject.size).to eq 1 + expect(find_by_consumer_name_and_consumer_version_number("Foo1", "2").selectors).to eq [pact_selector_1] + end + end + + context "when the selector is latest: true, with a tag, for a particular consumer" do + let(:pact_selector_1) { Selector.latest_for_tag_and_consumer("prod", "Foo1") } + + let(:consumer_version_selectors) do + Selectors.new(pact_selector_1) + end + + before do + td.create_pact_with_hierarchy("Foo1", "1", "Bar") + .create_consumer_version_tag("prod") + .create_pact_with_hierarchy("Foo1", "2", "Bar") + .create_pact_with_hierarchy("Foo2", "2", "Bar") + .create_consumer_version_tag("prod") + .create_pact_with_hierarchy("Foo2", "2", "Bar2") + end + + it "returns the latest pact for each consumer" do + expect(subject.size).to eq 1 + expect(find_by_consumer_name_and_consumer_version_number("Foo1", "1").selectors).to eq [pact_selector_1] + end + end + context "when the latest consumer tag names are specified" do before do td.create_pact_with_hierarchy("Foo", "foo-latest-prod-version", "Bar")