Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: do not allow the deployment of a provider version with no results when one of its consumers is already deployed #486

Merged
merged 13 commits into from
Aug 11, 2021
Merged
2 changes: 1 addition & 1 deletion lib/pact_broker/matrix/integration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ def pacticipant_names
end

def to_s
"Integration between #{consumer_name} (id=#{consumer_id}) and #{provider_name} (id=#{provider_id})"
"Integration between #{consumer_name} (id=#{consumer_id}) and #{provider_name} (id=#{provider_id}) required=#{required?}"
end

def involves_consumer_with_id?(consumer_id)
Expand Down
10 changes: 10 additions & 0 deletions lib/pact_broker/matrix/quick_row.rb
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,16 @@ class QuickRow < Sequel::Model(Sequel.as(:latest_pact_publication_ids_for_consum
select(*SELECT_ALL_COLUMN_ARGS)
select(*SELECT_PACTICIPANT_IDS_ARGS)

def distinct_integrations_for_selector_as_consumer(selector)
select(:consumer_id, :provider_id)
.distinct
.where({ consumer_id: selector.pacticipant_id, consumer_version_id: selector.pacticipant_version_id }.compact)
.from_self(alias: :integrations)
.select(:consumer_id, :provider_id, Sequel[:consumers][:name].as(:consumer_name), Sequel[:providers][:name].as(:provider_name))
.join_consumers(:integrations, :consumers)
.join_providers(:integrations, :providers)
end

def distinct_integrations selectors, infer_integrations
query = if selectors.size == 1
pacticipant_ids_matching_one_selector_optimised(selectors)
Expand Down
67 changes: 64 additions & 3 deletions lib/pact_broker/matrix/repository.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
require "pact_broker/logging"
require "pact_broker/repositories/helpers"
require "pact_broker/matrix/row"
require "pact_broker/matrix/quick_row"
Expand All @@ -20,6 +21,7 @@ class Error < PactBroker::Error; end
class Repository
include PactBroker::Repositories::Helpers
include PactBroker::Repositories
include PactBroker::Logging

# TODO move latest verification logic in to database

Expand Down Expand Up @@ -59,7 +61,7 @@ def find_ids_for_pacticipant_names params
def find specified_selectors, options = {}
resolved_ignore_selectors = resolve_ignore_selectors(options)
resolved_specified_selectors = resolve_versions_and_add_ids(specified_selectors, :specified, resolved_ignore_selectors)
integrations = find_integrations_for_specified_selectors(resolved_specified_selectors, infer_selectors_for_integrations?(options))
integrations = find_integrations_for_specified_selectors(resolved_specified_selectors, options)
resolved_selectors = add_any_inferred_selectors(resolved_specified_selectors, resolved_ignore_selectors, integrations, options)
considered_rows, ignored_rows = find_considered_and_ignored_rows(resolved_selectors, resolved_ignore_selectors, options)
QueryResults.new(considered_rows.sort, ignored_rows.sort, specified_selectors, options, resolved_selectors, resolved_ignore_selectors, integrations)
Expand All @@ -81,17 +83,76 @@ def find_considered_and_ignored_rows(resolved_selectors, resolved_ignore_selecto
return considered_rows, ignored_rows
end

def find_integrations_for_specified_selectors(resolved_specified_selectors, infer_integrations)
def find_integrations_for_specified_selectors(resolved_specified_selectors, options)
if infer_selectors_for_integrations?(options)
find_integrations_for_specified_selectors_with_inferred_integrations(resolved_specified_selectors, options)
else
find_integrations_for_specified_selectors_only(resolved_specified_selectors)
end
end

def find_integrations_for_specified_selectors_only(resolved_specified_selectors)
specified_pacticipant_names = resolved_specified_selectors.collect(&:pacticipant_name)
base_model_for_integrations
.distinct_integrations(resolved_specified_selectors, infer_integrations)
.distinct_integrations(resolved_specified_selectors, false)
.collect(&:to_hash)
.collect do | integration_hash |
required = is_a_row_for_this_integration_required?(specified_pacticipant_names, integration_hash[:consumer_name])
Integration.from_hash(integration_hash.merge(required: required))
end
end

def find_integrations_for_specified_selectors_with_inferred_integrations(resolved_specified_selectors, options)
integrations = integrations_where_specified_selector_is_consumer(resolved_specified_selectors) +
integrations_where_specified_selector_is_provider(resolved_specified_selectors, options)
deduplicate_integrations(integrations)
end

def integrations_where_specified_selector_is_consumer(resolved_specified_selectors)
resolved_specified_selectors.flat_map do | selector |
base_model_for_integrations
.distinct_integrations_for_selector_as_consumer(selector)
.all
.collect do | integration |
Integration.from_hash(
consumer_id: integration[:consumer_id],
consumer_name: integration[:consumer_name],
provider_id: integration[:provider_id],
provider_name: integration[:provider_name],
required: true
)
end
end
end

def integrations_where_specified_selector_is_provider(resolved_specified_selectors, options)
integrations_involving_specified_providers = PactBroker::Integrations::Integration
.where(provider_id: resolved_specified_selectors.collect(&:pacticipant_id))
.eager(:consumer)
.all

destination_selector = PactBroker::Matrix::UnresolvedSelector.new(options.slice(:latest, :tag, :branch, :environment_name).compact)

integrations_involving_specified_providers.collect do | integration |
required = PactBroker::Domain::Version.where(pacticipant: integration.consumer).for_selector(destination_selector).any?

Integration.from_hash(
consumer_id: integration.consumer.id,
consumer_name: integration.consumer.name,
provider_id: integration.provider.id,
provider_name: integration.provider.name,
required: required
)
end
end

def deduplicate_integrations(integrations)
integrations
.group_by{ | integration| [integration.consumer_id, integration.provider_id] }
.values
.collect { | duplicate_integrations | duplicate_integrations.find(&:required?) || duplicate_integrations.first }
end

def add_any_inferred_selectors(resolved_specified_selectors, resolved_ignore_selectors, integrations, options)
if infer_selectors_for_integrations?(options)
resolved_specified_selectors + inferred_selectors(resolved_specified_selectors, resolved_ignore_selectors, integrations, options)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,10 +90,10 @@
"PactBroker::Matrix::PactNotEverVerifiedByProvider"
]
},
"PactBroker::Matrix::Service find when deploying a provider and ignoring a consumer with a missing verification from a provider does allows the provider to be deployed even without ignoring anything because there is no connection between that version of the provider and the consumer": {
"deployable": true,
"PactBroker::Matrix::Service find when deploying a provider and ignoring a consumer with a missing verification from a provider does not allows the provider to be deployed": {
"deployable": null,
"reasons": [
"PactBroker::Matrix::NoDependenciesMissing"
"PactBroker::Matrix::PactNotEverVerifiedByProvider"
]
},
"PactBroker::Matrix::Service find when deploying a provider and ignoring a consumer with a failed verification from a provider deployment_status_summary is expected to be deployable": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,12 +156,18 @@
"PactBroker::Matrix::Successful"
]
},
"PactBroker::Matrix::Service find when there is a consumer with two providers, and only one of them has a verification, and the consumer and the verified provider are explicitly specified should allow the consumer to be deployed": {
"PactBroker::Matrix::Service find when there is a consumer with two providers, and only one of them has a verification, and the consumer and the verified provider are explicitly specified allows the consumer to be deployed": {
"deployable": true,
"reasons": [
"PactBroker::Matrix::NoEnvironmentSpecified",
"PactBroker::Matrix::SelectorWithoutPacticipantVersionNumberSpecified",
"PactBroker::Matrix::Successful"
]
},
"PactBroker::Matrix::Service find when a provider version has no verification results with the consumer version already in the environment does not allow the provider to be deployed": {
"deployable": null,
"reasons": [
"PactBroker::Matrix::PactNotEverVerifiedByProvider"
]
}
}
6 changes: 3 additions & 3 deletions spec/lib/pact_broker/matrix/integration_ignore_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -199,9 +199,9 @@ module Matrix

let(:ignore_selectors) { [] }

it "does allows the provider to be deployed even without ignoring anything because there is no connection between that version of the provider and the consumer" do
expect(subject.deployment_status_summary).to be_deployable
expect(subject.deployment_status_summary.reasons.collect(&:class)).to include(PactBroker::Matrix::NoDependenciesMissing)
it "does not allows the provider to be deployed" do
expect(subject.deployment_status_summary).to_not be_deployable
expect(subject.deployment_status_summary.reasons.collect(&:class)).to include(PactBroker::Matrix::PactNotEverVerifiedByProvider)
end
end

Expand Down
27 changes: 25 additions & 2 deletions spec/lib/pact_broker/matrix/integration_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -426,13 +426,36 @@ module Matrix
]
end

let(:options) { { latestby: "cvpv"} }
let(:options) { { latestby: "cvpv" } }

it "should allow the consumer to be deployed" do
it "allows the consumer to be deployed" do
expect(subject.deployment_status_summary).to be_deployable
end
end

# https://github.com/pact-foundation/pact_broker/issues/485
describe "when a provider version has no verification results with the consumer version already in the environment" do
before do
td.create_pact_with_verification("Foo", "1", "Bar", "1")
.create_pact_with_verification("NotInTest", "1", "Bar", "1")
.create_consumer_version_tag("test")
.create_provider_version("2")
end

let(:selectors) do
[
UnresolvedSelector.new(pacticipant_name: "Bar", pacticipant_version_number: "2"),
]
end

let(:options) { { latestby: "cvp", tag: "test", latest: true } }

it "does not allow the provider to be deployed" do
expect(subject.deployment_status_summary).to_not be_deployable
expect(subject.deployment_status_summary.reasons.collect(&:class)).to eq [PactBroker::Matrix::PactNotEverVerifiedByProvider]
end
end

describe "when verification results are published missing tests for some interactions" do
let(:pact_content) do
{
Expand Down
2 changes: 0 additions & 2 deletions spec/lib/pact_broker/matrix/repository_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@
module PactBroker
module Matrix
describe Repository do
let(:td) { TestDataBuilder.new}

def build_selectors(hash)
hash.collect do | key, value |
UnresolvedSelector.new(pacticipant_name: key, pacticipant_version_number: value)
Expand Down
2 changes: 1 addition & 1 deletion spec/spec_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -61,5 +61,5 @@ def app

if ENV["DEBUG"] == "true"
SemanticLogger.add_appender(io: $stdout)
SemanticLogger.default_level = :info
SemanticLogger.default_level = :debug
end
3 changes: 1 addition & 2 deletions spec/support/approvals.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,11 @@ def print_diff(exception)

module MatrixQueryContentForApproval
def matrix_query_content_for_approval(result)
content = {
{
"deployable" => result.deployment_status_summary.deployable?,
"reasons" => result.deployment_status_summary.reasons.collect(&:class).collect(&:name).sort
}
end

end

RSpec.configure do | config |
Expand Down