From 125c2722427ba1b2b27b47fe8847956040772fc3 Mon Sep 17 00:00:00 2001 From: Beth Skurrie Date: Wed, 27 Feb 2019 17:20:48 +1100 Subject: [PATCH] feat(matrix): allow provider to be deployed to an environment without the consumer having to be deployed there already Closes: https://github.com/pact-foundation/pact_broker-client/issues/48 --- .../matrix/deployment_status_summary.rb | 31 ++-- lib/pact_broker/matrix/integration.rb | 38 ++++- lib/pact_broker/matrix/repository.rb | 134 +++++++++++------- lib/pact_broker/matrix/resolved_selector.rb | 59 ++++++++ .../matrix/deployment_status_summary_spec.rb | 50 ++++++- .../matrix/repository_dependency_spec.rb | 58 ++++++++ .../lib/pact_broker/matrix/repository_spec.rb | 15 +- spec/lib/pact_broker/matrix/service_spec.rb | 42 ++++++ 8 files changed, 343 insertions(+), 84 deletions(-) create mode 100644 lib/pact_broker/matrix/resolved_selector.rb create mode 100644 spec/lib/pact_broker/matrix/repository_dependency_spec.rb diff --git a/lib/pact_broker/matrix/deployment_status_summary.rb b/lib/pact_broker/matrix/deployment_status_summary.rb index 6d0ed0b74..3c16381d1 100644 --- a/lib/pact_broker/matrix/deployment_status_summary.rb +++ b/lib/pact_broker/matrix/deployment_status_summary.rb @@ -17,28 +17,24 @@ def counts { success: rows.count{ |row| row.success }, failed: rows.count { |row| row.success == false }, - unknown: integrations_without_a_row.count + rows.count { |row| row.success.nil? } + unknown: required_integrations_without_a_row.count + rows.count { |row| row.success.nil? } } end def deployable? - return nil if rows.empty? + # return nil if rows.empty? return nil if rows.any?{ |row| row.success.nil? } - return nil if integrations_without_a_row.any? + return nil if required_integrations_without_a_row.any? rows.all?{ |row| row.success } end def reasons @reasons ||= begin reasons = [] - if rows.empty? - reasons << "No results matched the given query" - else - reasons.concat(missing_reasons) - reasons.concat(failure_messages) - reasons.concat(unverified_messages) - reasons.concat(success_messages) - end + reasons.concat(missing_reasons) + reasons.concat(failure_messages) + reasons.concat(unverified_messages) + reasons.concat(success_messages) reasons end end @@ -60,16 +56,19 @@ def failure_messages end def success_messages - if rows.all?{ |row| row.success } && integrations_without_a_row.empty? + if rows.all?{ |row| row.success } && required_integrations_without_a_row.empty? ["All verification results are published and successful"] else [] end end - def integrations_without_a_row - @integrations_without_a_row ||= begin - integrations.select do | relationship | + # For deployment, the consumer requires the provider, + # but the provider does not require the consumer + # This method tells us which providers are missing. + def required_integrations_without_a_row + @required_integrations_without_a_row ||= begin + integrations.select(&:required?).select do | relationship | !rows.find do | row | row.consumer_id == relationship.consumer_id && row.provider_id == relationship.provider_id end @@ -78,7 +77,7 @@ def integrations_without_a_row end def missing_reasons - integrations_without_a_row.collect do | missing_relationship| + required_integrations_without_a_row.collect do | missing_relationship| consumer_version_desc = "#{missing_relationship.consumer_name} (#{resolved_version_for(missing_relationship.consumer_id)})" provider_version_desc = "#{missing_relationship.provider_name} (#{resolved_version_for(missing_relationship.provider_id)})" "There is no verified pact between #{consumer_version_desc} and #{provider_version_desc}" diff --git a/lib/pact_broker/matrix/integration.rb b/lib/pact_broker/matrix/integration.rb index ccfaaa3ea..d78e6a11d 100644 --- a/lib/pact_broker/matrix/integration.rb +++ b/lib/pact_broker/matrix/integration.rb @@ -1,17 +1,22 @@ -# -# Represents the integration relationship between a consumer and a provider -# + +# Represents the integration relationship between a consumer and a provider in the context +# of a matrix or can-i-deploy query. +# If the required flag is set, then one of the pacticipants (consumers) specified in the HTTP query +# requires the provider. It would not be required if a provider was specified, and it had an +# integration with a consumer. + module PactBroker module Matrix class Integration attr_reader :consumer_name, :consumer_id, :provider_name, :provider_id - def initialize consumer_id, consumer_name, provider_id, provider_name + def initialize consumer_id, consumer_name, provider_id, provider_name, required @consumer_id = consumer_id @consumer_name = consumer_name @provider_id = provider_id @provider_name = provider_name + @required = required end def self.from_hash hash @@ -19,10 +24,15 @@ def self.from_hash hash hash.fetch(:consumer_id), hash.fetch(:consumer_name), hash.fetch(:provider_id), - hash.fetch(:provider_name) + hash.fetch(:provider_name), + hash.fetch(:required) ) end + def required? + @required + end + def == other consumer_id == other.consumer_id && provider_id == other.provider_id end @@ -30,7 +40,7 @@ def == other def <=> other comparison = consumer_name <=> other.consumer_name return comparison if comparison != 0 - provider_name <=> other.provider_name + comparison =provider_name <=> other.provider_name end def to_hash @@ -49,6 +59,22 @@ def pacticipant_names def to_s "Relationship between #{consumer_name} (id=#{consumer_id}) and #{provider_name} (id=#{provider_id})" end + + def involves_consumer_with_id?(consumer_id) + self.consumer_id == consumer_id + end + + def involves_consumer_with_names?(consumer_names) + consumer_names.include?(self.consumer_name) + end + + def involves_provider_with_name?(provider_name) + self.provider_name == provider_name + end + + def involves_consumer_with_name?(consumer_name) + self.consumer_name == consumer_name + end end end end diff --git a/lib/pact_broker/matrix/repository.rb b/lib/pact_broker/matrix/repository.rb index 9ced4d740..920da74a2 100644 --- a/lib/pact_broker/matrix/repository.rb +++ b/lib/pact_broker/matrix/repository.rb @@ -5,6 +5,7 @@ require 'pact_broker/matrix/query_results' require 'pact_broker/matrix/integration' require 'pact_broker/matrix/query_results_with_deployment_status_summary' +require 'pact_broker/matrix/resolved_selector' module PactBroker module Matrix @@ -48,10 +49,10 @@ def find_ids_for_pacticipant_names params end # Return the latest matrix row (pact/verification) for each consumer_version_number/provider_version_number - def find selectors, options = {} - resolved_selectors = resolve_selectors(selectors, options) + def find specified_selectors, options = {} + resolved_selectors = resolve_selectors(specified_selectors, options) lines = query_matrix(resolved_selectors, options) - lines = apply_latestby(options, selectors, lines) + lines = apply_latestby(options, specified_selectors, lines) # This needs to be done after the latestby, so can't be done in the db unless # the latestby logic is moved to the db @@ -59,7 +60,7 @@ def find selectors, options = {} lines = lines.select{ |l| options[:success].include?(l.success) } end - QueryResults.new(lines.sort, selectors, options, resolved_selectors) + QueryResults.new(lines.sort, specified_selectors, options, resolved_selectors) end def find_for_consumer_and_provider pacticipant_1_name, pacticipant_2_name @@ -73,13 +74,19 @@ def find_compatible_pacticipant_versions selectors end def find_integrations(pacticipant_names) - selectors = pacticipant_names.collect{ | pacticipant_name | add_ids(pacticipant_name: pacticipant_name) } + selectors = pacticipant_names.collect{ | pacticipant_name | add_ids_to_selector(pacticipant_name: pacticipant_name) } Row .select(:consumer_name, :consumer_id, :provider_name, :provider_id) .matching_selectors(selectors) .distinct .all - .collect{ |row | Integration.from_hash(row.to_hash) }.uniq + .collect do |row | + row.to_hash + end + .uniq + .collect do | hash | + Integration.from_hash(hash.merge(required: pacticipant_names.include?(hash[:consumer_name]))) + end end private @@ -127,85 +134,102 @@ def view_for(options) Row end - def resolve_selectors(selectors, options) - resolved_selectors = look_up_version_numbers(selectors, options) + def resolve_selectors(specified_selectors, options) + resolved_specified_selectors = resolve_versions_and_add_ids(specified_selectors, options) if options[:latest] || options[:tag] - apply_latest_and_tag_to_inferred_selectors(resolved_selectors, options) + add_inferred_selectors(resolved_specified_selectors, options) else - resolved_selectors + resolved_specified_selectors end end # Find the version number for selectors with the latest and/or tag specified - def look_up_version_numbers(selectors, options) + def resolve_versions_and_add_ids(selectors, options, required = true) selectors.collect do | selector | - if selector[:tag] && selector[:latest] - version = version_repository.find_by_pacticipant_name_and_latest_tag(selector[:pacticipant_name], selector[:tag]) - raise Error.new("No version of #{selector[:pacticipant_name]} found with tag #{selector[:tag]}") unless version - # validation in resource should ensure we always have a version - { - pacticipant_name: selector[:pacticipant_name], - pacticipant_version_number: version.number - } - elsif selector[:latest] - version = version_repository.find_latest_by_pacticpant_name(selector[:pacticipant_name]) - raise Error.new("No version of #{selector[:pacticipant_name]} found") unless version - { - pacticipant_name: selector[:pacticipant_name], - pacticipant_version_number: version.number - } - elsif selector[:tag] - # validation in resource should ensure we always have at least one version - versions = version_repository.find_by_pacticipant_name_and_tag(selector[:pacticipant_name], selector[:tag]) - raise Error.new("No version of #{selector[:pacticipant_name]} found with tag #{selector[:tag]}") unless versions.any? + pacticipant = PactBroker::Domain::Pacticipant.find(name: selector[:pacticipant_name]) + + versions = find_versions_for_selector(selector, required) + + if versions versions.collect do | version | - { - pacticipant_name: selector[:pacticipant_name], - pacticipant_version_number: version.number - } + if version + selector_for_version(pacticipant, version) + else + selector_for_non_existing_version(pacticipant) + end end else - selector.dup + selector_without_version(pacticipant) end - end.flatten.compact.collect do | selector | - add_ids(selector) + end.flatten + end + + def find_versions_for_selector(selector, required) + if selector[:tag] && selector[:latest] + version = version_repository.find_by_pacticipant_name_and_latest_tag(selector[:pacticipant_name], selector[:tag]) + # raise Error.new("No version of #{selector[:pacticipant_name]} found with tag #{selector[:tag]}") if required && !version + [version] + elsif selector[:latest] + version = version_repository.find_latest_by_pacticpant_name(selector[:pacticipant_name]) + # raise Error.new("No version of #{selector[:pacticipant_name]} found") if required && !version + [version] + elsif selector[:tag] + versions = version_repository.find_by_pacticipant_name_and_tag(selector[:pacticipant_name], selector[:tag]) + # raise Error.new("No version of #{selector[:pacticipant_name]} found with tag #{selector[:tag]}") if required && versions.empty? + versions.any? ? versions : [nil] + elsif selector[:pacticipant_version_number] + version = version_repository.find_by_pacticipant_name_and_number(selector[:pacticipant_name], selector[:pacticipant_version_number]) + # raise Error.new("No version #{selector[:pacticipant_version_number]} of #{selector[:pacticipant_name]} found") if required && !version + [version] + else + nil end end - def add_ids(selector) + def add_ids_to_selector(selector) if selector[:pacticipant_name] pacticipant = PactBroker::Domain::Pacticipant.find(name: selector[:pacticipant_name]) selector[:pacticipant_id] = pacticipant ? pacticipant.id : nil end - if selector[:pacticipant_name] && selector[:pacticipant_version_number] + if selector[:pacticipant_name] && selector[:pacticipant_version_number] && !selector[:pacticipant_version_id] version = version_repository.find_by_pacticipant_name_and_number(selector[:pacticipant_name], selector[:pacticipant_version_number]) selector[:pacticipant_version_id] = version ? version.id : nil end - if selector[:pacticipant_version_number].nil? - selector[:pacticipant_version_id] = nil + if !selector.key?(:pacticipant_version_id) + selector[:pacticipant_version_id] = nil end selector end # eg. when checking to see if Foo version 2 can be deployed to prod, # need to look up all the 'partner' pacticipants, and determine their latest prod versions - def apply_latest_and_tag_to_inferred_selectors(selectors, options) - all_pacticipant_names = all_pacticipant_names_in_specified_matrix(selectors) - specified_names = selectors.collect{ |s| s[:pacticipant_name] } - inferred_names = all_pacticipant_names - specified_names + def add_inferred_selectors(resolved_specified_selectors, options) + integrations = find_integrations(resolved_specified_selectors.collect{|s| s[:pacticipant_name]}) + all_pacticipant_names = integrations.collect(&:pacticipant_names).flatten.uniq + specified_names = resolved_specified_selectors.collect{ |s| s[:pacticipant_name] } + inferred_pacticipant_names = all_pacticipant_names - specified_names + # Inferred providers are required for a consumer to be deployed + required_inferred_pacticipant_names = inferred_pacticipant_names.select{ | n | integrations.any?{ |i| i.involves_provider_with_name?(n) } } + # Inferred consumers are NOT required for a provider to be deployed + optional_inferred_pacticipant_names = inferred_pacticipant_names - required_inferred_pacticipant_names + + resolved_specified_selectors + + build_inferred_selectors(required_inferred_pacticipant_names, options, true) + + build_inferred_selectors(optional_inferred_pacticipant_names, options, false) + end - inferred_selectors = inferred_names.collect do | pacticipant_name | + def build_inferred_selectors(inferred_pacticipant_names, options, required) + selectors = inferred_pacticipant_names.collect do | pacticipant_name | selector = { - pacticipant_name: pacticipant_name, + pacticipant_name: pacticipant_name } selector[:tag] = options[:tag] if options[:tag] selector[:latest] = options[:latest] if options[:latest] selector end - - selectors + look_up_version_numbers(inferred_selectors, options) + resolve_versions_and_add_ids(selectors, options, required) end def all_pacticipant_names_in_specified_matrix(selectors) @@ -214,6 +238,18 @@ def all_pacticipant_names_in_specified_matrix(selectors) .flatten .uniq end + + def selector_for_non_existing_version(pacticipant) + ResolvedSelector.for_pacticipant_and_non_existing_version(pacticipant) + end + + def selector_for_version(pacticipant, version) + ResolvedSelector.for_pacticipant_and_version(pacticipant, version) + end + + def selector_without_version(pacticipant) + ResolvedSelector.for_pacticipant(pacticipant) + end end end end diff --git a/lib/pact_broker/matrix/resolved_selector.rb b/lib/pact_broker/matrix/resolved_selector.rb new file mode 100644 index 000000000..d84ddeacf --- /dev/null +++ b/lib/pact_broker/matrix/resolved_selector.rb @@ -0,0 +1,59 @@ +# A selector with the pacticipant id, name, version number, and version id set +module PactBroker + module Matrix + class ResolvedSelector < Hash + NULL_VERSION_ID = -1 + + def initialize(params) + merge!(params) + end + + def self.for_pacticipant(pacticipant) + ResolvedSelector.new( + pacticipant_id: pacticipant.id, + pacticipant_name: pacticipant.name + ) + end + + def self.for_pacticipant_and_version(pacticipant, version) + ResolvedSelector.new( + pacticipant_id: pacticipant.id, + pacticipant_name: pacticipant.name, + pacticipant_version_id: version.id, + pacticipant_version_number: version.number + ) + end + + # An ID of -1 will not match any rows, which is what we want + def self.for_pacticipant_and_non_existing_version(pacticipant) + ResolvedSelector.new( + pacticipant_id: pacticipant.id, + pacticipant_name: pacticipant.name, + pacticipant_version_id: NULL_VERSION_ID, + pacticipant_version_number: "" + ) + end + + def pacticipant_id + self[:pacticipant_id] + end + + def pacticipant_name + self[:pacticipant_name] + end + + def pacticipant_version_id + self[:pacticipant_version_id] + end + + def pacticipant_version_number + self[:pacticipant_version_number] + end + + def version_not_found? + pacticipant_version_id == NULL_VERSION_ID + end + end + + end +end \ No newline at end of file diff --git a/spec/lib/pact_broker/matrix/deployment_status_summary_spec.rb b/spec/lib/pact_broker/matrix/deployment_status_summary_spec.rb index 8324f55ff..dc4b8640a 100644 --- a/spec/lib/pact_broker/matrix/deployment_status_summary_spec.rb +++ b/spec/lib/pact_broker/matrix/deployment_status_summary_spec.rb @@ -39,21 +39,21 @@ module Matrix let(:integrations) do [ - Integration.new(1, "Foo", 2, "Bar"), - Integration.new(1, "Foo", 3, "Baz") + Integration.new(1, "Foo", 2, "Bar", true), + Integration.new(1, "Foo", 3, "Baz", true) ] end let(:resolved_selectors) do [ { - pacticipant_id: 1, pacticipant_version_number: "ddec8101dabf4edf9125a69f9a161f0f294af43c" + pacticipant_id: 1, pacticipant_name: "Foo", pacticipant_version_number: "ddec8101dabf4edf9125a69f9a161f0f294af43c" }, { - pacticipant_id: 2, pacticipant_version_number: "14131c5da3abf323ccf410b1b619edac76231243" + pacticipant_id: 2, pacticipant_name: "Bar", pacticipant_version_number: "14131c5da3abf323ccf410b1b619edac76231243" }, { - pacticipant_id: 3, pacticipant_version_number: "4ee06460f10e8207ad904fa9fa6c4842e462ab59" + pacticipant_id: 3, pacticipant_name: "Baz", pacticipant_version_number: "4ee06460f10e8207ad904fa9fa6c4842e462ab59" } ] end @@ -70,7 +70,12 @@ module Matrix let(:rows) { [] } its(:deployable?) { is_expected.to be nil } - its(:reasons) { is_expected.to eq ["No results matched the given query"] } + its(:reasons) do + is_expected.to eq [ + "There is no verified pact between Foo (ddec8101dabf4edf9125a69f9a161f0f294af43c) and Bar (14131c5da3abf323ccf410b1b619edac76231243)", + "There is no verified pact between Foo (ddec8101dabf4edf9125a69f9a161f0f294af43c) and Baz (4ee06460f10e8207ad904fa9fa6c4842e462ab59)", + ] + end its(:counts) { is_expected.to eq success: 0, failed: 0, unknown: 2 } end @@ -90,7 +95,38 @@ module Matrix its(:counts) { is_expected.to eq success: 1, failed: 1, unknown: 0 } end - context "when there is a relationship missing" do + context "when there is a provider relationship missing" do + let(:rows) { [row_1] } + + its(:deployable?) { is_expected.to be nil } + its(:reasons) { is_expected.to eq ["There is no verified pact between Foo (ddec8101dabf4edf9125a69f9a161f0f294af43c) and Baz (4ee06460f10e8207ad904fa9fa6c4842e462ab59)"] } + its(:counts) { is_expected.to eq success: 1, failed: 0, unknown: 1 } + end + + context "when there is a consumer row missing and only the provider was specified in the query" do + let(:rows) { [row_1] } + let(:integrations) do + [ + Integration.new(1, "Foo", 2, "Bar", true), + Integration.new(3, "Baz", 2, "Bar", false) # the missing one + ] + end + + its(:deployable?) { is_expected.to be true } + its(:reasons) { is_expected.to eq ["All verification results are published and successful"] } + its(:counts) { is_expected.to eq success: 1, failed: 0, unknown: 0 } + end + + context "when there is a consumer relationship missing and only the consumer was specified in the query" do + let(:rows) { [row_1] } + + let(:integrations) do + [ + Integration.new(1, "Foo", 2, "Bar", true), + Integration.new(1, "Foo", 3, "Baz", true) # the missing one + ] + end + let(:rows) { [row_1] } its(:deployable?) { is_expected.to be nil } diff --git a/spec/lib/pact_broker/matrix/repository_dependency_spec.rb b/spec/lib/pact_broker/matrix/repository_dependency_spec.rb new file mode 100644 index 000000000..3869d1e45 --- /dev/null +++ b/spec/lib/pact_broker/matrix/repository_dependency_spec.rb @@ -0,0 +1,58 @@ +require 'pact_broker/matrix/repository' + +module PactBroker + module Matrix + describe Repository do + let(:td) { TestDataBuilder.new} + + def build_selectors(hash) + hash.collect do | key, value | + { pacticipant_name: key, pacticipant_version_number: value } + end + end + + def shorten_row row + "#{row[:consumer_name]}#{row[:consumer_version_number]} #{row[:provider_name]}#{row[:provider_version_number] || '?'} n#{row[:verification_number] || '?'}" + end + + def shorten_rows rows + rows.collect{ |r| shorten_row(r) } + end + + describe "find when deploying a provider to prod for the first time and the consumer has not been deployed yet" do + before do + td.create_pact_with_hierarchy("Foo", "1", "Bar") + .create_verification(provider_version: "2") + end + + let(:selectors) { [ { pacticipant_name: "Bar", pacticipant_version_number: "2" } ] } + let(:options) { { latest: true, tag: "prod"} } + + subject { shorten_rows(rows) } + let(:rows) { Repository.new.find(selectors, options) } + + it "returns an empty array" do + expect(rows).to eq [] + end + end + + describe "find when deploying a consumer to prod for the first time and the provider has not been deployed yet" do + before do + td.create_pact_with_hierarchy("Foo", "1", "Bar") + .create_verification(provider_version: "2") + end + + let(:selectors) { [ { pacticipant_name: "Foo", pacticipant_version_number: "1" } ] } + let(:options) { { latest: true, tag: "prod"} } + + subject { shorten_rows(rows) } + let(:results) { Repository.new.find(selectors, options) } + + it "returns an empty array" do + expect(results).to eq [] + expect(results.resolved_selectors.find{ |s | s[:pacticipant_name] == "Bar"}[:pacticipant_version_id]).to eq -1 + end + end + end + end +end diff --git a/spec/lib/pact_broker/matrix/repository_spec.rb b/spec/lib/pact_broker/matrix/repository_spec.rb index b2615db3e..a90c49790 100644 --- a/spec/lib/pact_broker/matrix/repository_spec.rb +++ b/spec/lib/pact_broker/matrix/repository_spec.rb @@ -956,24 +956,27 @@ def shorten_rows rows context "when the latest tag is specified" do let(:selectors) { [{ pacticipant_name: 'D', latest: true, tag: 'dev' } ] } - it "raises an error" do - expect { subject }.to raise_error Error, /No version of D found/ + it "returns an empty array" do + expect(subject).to eq [] + expect(subject.resolved_selectors.find{ |s | s[:pacticipant_name] == "D"}[:pacticipant_version_id]).to eq -1 end end context "when all tags are specified" do let(:selectors) { [{ pacticipant_name: 'D', tag: 'dev' } ] } - it "raises an error" do - expect { subject }.to raise_error Error, /No version of D found/ + it "returns an empty array" do + expect(subject).to eq [] + expect(subject.resolved_selectors.find{ |s | s[:pacticipant_name] == "D"}[:pacticipant_version_id]).to eq -1 end end context "when no tags are specified" do let(:selectors) { [{ pacticipant_name: 'E', latest: true } ] } - it "raises an error" do - expect { subject }.to raise_error Error, /No version of E found/ + it "returns an empty array" do + expect(subject).to eq [] + expect(subject.resolved_selectors.find{ |s | s[:pacticipant_name] == "E"}[:pacticipant_version_id]).to eq -1 end end end diff --git a/spec/lib/pact_broker/matrix/service_spec.rb b/spec/lib/pact_broker/matrix/service_spec.rb index e8ae75061..572f77bac 100644 --- a/spec/lib/pact_broker/matrix/service_spec.rb +++ b/spec/lib/pact_broker/matrix/service_spec.rb @@ -31,6 +31,48 @@ module Matrix end end + describe "integration - when deploying a provider to prod for the first time and the consumer is not yet deployed" do + before do + td.create_pact_with_hierarchy("Foo", "1", "Bar") + .create_verification(provider_version: "2") + end + + let(:selectors) do + [ { pacticipant_name: "Bar", pacticipant_version_number: "2" } ] + end + + let(:options) do + { latest: true, tag: "prod" } + end + + subject { Service.find(selectors, options) } + + it "allows the provider to be deployed" do + expect(subject.deployment_status_summary.deployable?).to be true + end + end + + describe "integration - when deploying a consumer to prod for the first time and the provider is not yet deployed" do + before do + td.create_pact_with_hierarchy("Foo", "1", "Bar") + .create_verification(provider_version: "2") + end + + let(:selectors) do + [ { pacticipant_name: "Foo", pacticipant_version_number: "1" } ] + end + + let(:options) do + { latest: true, tag: "prod" } + end + + subject { Service.find(selectors, options) } + + it "does not allow the provider to be deployed" do + expect(subject.deployment_status_summary.deployable?).to_not be true + end + end + describe "validate_selectors" do subject { Service.validate_selectors(selectors) }