diff --git a/lib/pact_broker/api/pact_broker_urls.rb b/lib/pact_broker/api/pact_broker_urls.rb index 7b5a0e5ae..74278baeb 100644 --- a/lib/pact_broker/api/pact_broker_urls.rb +++ b/lib/pact_broker/api/pact_broker_urls.rb @@ -75,7 +75,7 @@ def encode_metadata(metadata) end def decode_pact_metadata(metadata) - if metadata + if metadata && metadata != '' begin Rack::Utils.parse_nested_query(Base64.strict_decode64(metadata)).each_with_object({}) do | (k, v), new_hash | new_hash[k.to_sym] = v diff --git a/lib/pact_broker/api/resources/metadata_resource_methods.rb b/lib/pact_broker/api/resources/metadata_resource_methods.rb new file mode 100644 index 000000000..4ff1031dc --- /dev/null +++ b/lib/pact_broker/api/resources/metadata_resource_methods.rb @@ -0,0 +1,23 @@ +require 'pact_broker/hash_refinements' + +module PactBroker + module Api + module Resources + module MetadataResourceMethods + using PactBroker::HashRefinements + + def pact_params + @pact_params ||= PactBroker::Pacts::PactParams.from_request(request, maybe_params_with_consumer_version_number.merge(path_info)) + end + + def maybe_params_with_consumer_version_number + metadata.slice(:consumer_version_number) + end + + def metadata + @metadata ||= PactBrokerUrls.decode_pact_metadata(identifier_from_path[:metadata]) + end + end + end + end +end diff --git a/lib/pact_broker/api/resources/pact_version.rb b/lib/pact_broker/api/resources/pact_version.rb index 35d47e082..2cf7bccf3 100644 --- a/lib/pact_broker/api/resources/pact_version.rb +++ b/lib/pact_broker/api/resources/pact_version.rb @@ -1,9 +1,12 @@ require 'pact_broker/api/resources/pact' +require 'pact_broker/api/resources/metadata_resource_methods' module PactBroker module Api module Resources class PactVersion < Pact + include MetadataResourceMethods + def allowed_methods ["GET", "OPTIONS"] end diff --git a/lib/pact_broker/api/resources/verifications.rb b/lib/pact_broker/api/resources/verifications.rb index 1af23938b..e62e8e58f 100644 --- a/lib/pact_broker/api/resources/verifications.rb +++ b/lib/pact_broker/api/resources/verifications.rb @@ -4,12 +4,14 @@ require 'pact_broker/api/contracts/verification_contract' require 'pact_broker/api/decorators/verification_decorator' require 'pact_broker/api/resources/webhook_execution_methods' +require 'pact_broker/api/resources/metadata_resource_methods' module PactBroker module Api module Resources class Verifications < BaseResource include WebhookExecutionMethods + include MetadataResourceMethods def content_types_accepted [["application/json", :from_json]] @@ -75,10 +77,6 @@ def decorator_for model PactBroker::Api::Decorators::VerificationDecorator.new(model) end - def metadata - PactBrokerUrls.decode_pact_metadata(identifier_from_path[:metadata]) - end - def wip? metadata[:wip] == 'true' end diff --git a/lib/pact_broker/pacts/repository.rb b/lib/pact_broker/pacts/repository.rb index 9e1d1d4c5..22ffc4b52 100644 --- a/lib/pact_broker/pacts/repository.rb +++ b/lib/pact_broker/pacts/repository.rb @@ -3,6 +3,7 @@ require 'pact_broker/logging' require 'pact_broker/pacts/generate_sha' require 'pact_broker/pacts/pact_publication' +require 'pact_broker/pacts/pact_version' require 'pact_broker/pacts/all_pact_publications' require 'pact_broker/pacts/latest_pact_publications_by_consumer_version' require 'pact_broker/pacts/latest_pact_publications' @@ -295,20 +296,38 @@ def search_for_latest_pact(consumer_name, provider_name, tag = nil) end def find_pact consumer_name, consumer_version, provider_name, pact_version_sha = nil - query = if pact_version_sha - scope_for(AllPactPublications) + pact_publication_by_consumer_version = scope_for(LatestPactPublicationsByConsumerVersion) + .consumer(consumer_name) + .provider(provider_name) + .consumer_version_number(consumer_version) + .limit(1) + + latest_pact_publication_by_sha = scope_for(AllPactPublications) + .consumer(consumer_name) + .provider(provider_name) .pact_version_sha(pact_version_sha) - .reverse_order(:consumer_version_order) + .reverse_order(:consumer_version_order, :revision_number) .limit(1) - else - scope_for(LatestPactPublicationsByConsumerVersion) + + query = if consumer_version && !pact_version_sha + pact_publication_by_consumer_version + .eager(:tags) + .collect(&:to_domain_with_content).first + elsif pact_version_sha && !consumer_version + latest_pact_publication_by_sha + .eager(:tags) + .collect(&:to_domain_with_content).first + elsif consumer_version && pact_version_sha + pact_publication = pact_publication_by_consumer_version.all.first + if pact_publication && pact_publication.pact_version.sha == pact_version_sha + pact_publication.tags + pact_publication.to_domain_with_content + else + latest_pact_publication_by_sha + .eager(:tags) + .collect(&:to_domain_with_content).first + end end - query = query - .eager(:tags) - .consumer(consumer_name) - .provider(provider_name) - query = query.consumer_version_number(consumer_version) if consumer_version - query.collect(&:to_domain_with_content)[0] end def find_all_revisions consumer_name, consumer_version, provider_name @@ -502,7 +521,7 @@ def find_or_create_pact_version consumer_id, provider_id, pact_version_sha, json end def create_pact_version consumer_id, provider_id, sha, json_content - PactVersion.new( + PactBroker::Pacts::PactVersion.new( consumer_id: consumer_id, provider_id: provider_id, sha: sha, diff --git a/lib/pact_broker/test/test_data_builder.rb b/lib/pact_broker/test/test_data_builder.rb index 001f494a0..ad83ad487 100644 --- a/lib/pact_broker/test/test_data_builder.rb +++ b/lib/pact_broker/test/test_data_builder.rb @@ -40,6 +40,7 @@ class TestDataBuilder attr_reader :provider_version attr_reader :version attr_reader :pact + attr_reader :pact_version attr_reader :verification attr_reader :webhook attr_reader :webhook_execution @@ -246,6 +247,20 @@ def revise_pact json_content = nil self end + def create_pact_version_without_publication(json_content = nil ) + json_content = json_content ? json_content : {random: rand}.to_json + pact_version_sha = generate_pact_version_sha(json_content) + + @pact_version = PactBroker::Pacts::PactVersion.new( + consumer_id: consumer.id, + provider_id: provider.id, + sha: pact_version_sha, + content: json_content, + created_at: Sequel.datetime_class.now + ).save + self + end + def create_webhook parameters = {} params = parameters.dup consumer = params.key?(:consumer) ? params.delete(:consumer) : @consumer diff --git a/spec/features/get_pact_spec.rb b/spec/features/get_pact_spec.rb index c1ac35bf9..3eb0f8cc3 100644 --- a/spec/features/get_pact_spec.rb +++ b/spec/features/get_pact_spec.rb @@ -1,6 +1,5 @@ describe "retrieving a pact" do - - subject { get path; last_response } + subject { get(path) } context "when differing case is used in the consumer and provider names" do @@ -30,6 +29,7 @@ end end end + context "when differing case is used in the tag name" do let(:path) { "/pacts/provider/a%20provider/consumer/a%20consumer/latest/PROD" } diff --git a/spec/features/get_pact_version.rb b/spec/features/get_pact_version.rb index cef1ad6ba..c3bd8af69 100644 --- a/spec/features/get_pact_version.rb +++ b/spec/features/get_pact_version.rb @@ -1,8 +1,7 @@ -describe "retrieving a pact" do - subject { get path; last_response } +RSpec.describe "retrieving a pact" do + subject { get(path) } context "when differing case is used in the consumer and provider names" do - let(:td) { TestDataBuilder.new } let(:pact) { td.create_pact_with_hierarchy("Foo", "1", "Bar").and_return(:pact) } let!(:path) { "/pacts/provider/Bar/consumer/Foo/pact-version/#{pact.pact_version_sha}" } @@ -10,4 +9,28 @@ expect(subject.status).to be 200 end end + + context "when there are multiple consumer versions for the same sha" do + before do + td.create_pact_with_hierarchy("Foo", "1", "Bar") + .create_consumer_version("2") + .republish_same_pact + end + + let(:pact) { PactBroker::Pacts::PactPublication.order(:id).first.to_domain } + let(:path) { PactBroker::Api::PactBrokerUrls.pact_version_url(pact) } + + it "returns the latest" do + expect(JSON.parse(subject.body)['_links']['pb:consumer-version']['name']).to eq "2" + end + + context "when there is metadata specifying the consumer version number" do + let(:pact) { PactBroker::Pacts::PactPublication.order(:id).first.to_domain } + let(:path) { PactBroker::Api::PactBrokerUrls.pact_version_url_with_webhook_metadata(pact) } + + it "returns the pact with the matching consumer version number" do + expect(JSON.parse(subject.body)['_links']['pb:consumer-version']['name']).to eq "1" + end + end + end end diff --git a/spec/lib/pact_broker/api/pact_broker_urls_spec.rb b/spec/lib/pact_broker/api/pact_broker_urls_spec.rb index 2538cad5d..073228703 100644 --- a/spec/lib/pact_broker/api/pact_broker_urls_spec.rb +++ b/spec/lib/pact_broker/api/pact_broker_urls_spec.rb @@ -123,6 +123,12 @@ module Api end end + context "when the metadata is empty" do + it "returns an empty hash" do + expect(PactBrokerUrls.decode_pact_metadata("")).to eq({}) + end + end + context "when the metadata is not valid base64" do it "returns an empty hash" do expect(PactBrokerUrls.decode_pact_metadata("foo==,")).to eq({}) diff --git a/spec/lib/pact_broker/api/resources/verifications_spec.rb b/spec/lib/pact_broker/api/resources/verifications_spec.rb index cf271869f..bdab27688 100644 --- a/spec/lib/pact_broker/api/resources/verifications_spec.rb +++ b/spec/lib/pact_broker/api/resources/verifications_spec.rb @@ -17,7 +17,7 @@ module Resources let(:database_connector) { double('database_connector' )} let(:verification) { double(PactBroker::Domain::Verification) } let(:errors_empty) { true } - let(:parsed_metadata) { { the: 'metadata' } } + let(:parsed_metadata) { { the: 'metadata', consumer_version_number: "2"} } let(:base_url) { "http://example.org" } let(:webhook_execution_configuration) { instance_double(PactBroker::Webhooks::ExecutionConfiguration) } @@ -29,10 +29,14 @@ module Resources allow(webhook_execution_configuration).to receive(:with_webhook_context).and_return(webhook_execution_configuration) end - subject { post url, request_body, rack_env; last_response } + subject { post(url, request_body, rack_env) } it "looks up the specified pact" do - allow(Pacts::Service).to receive(:find_pact).with(instance_of(PactBroker::Pacts::PactParams)) + expect(Pacts::Service).to receive(:find_pact) do | arg | + expect(arg).to be_a(PactBroker::Pacts::PactParams) + expect(arg[:consumer_version_number]).to eq "2" + end + subject end context "when the pact does not exist" do diff --git a/spec/lib/pact_broker/pacts/repository_spec.rb b/spec/lib/pact_broker/pacts/repository_spec.rb index bf5264f29..7be0798ef 100644 --- a/spec/lib/pact_broker/pacts/repository_spec.rb +++ b/spec/lib/pact_broker/pacts/repository_spec.rb @@ -609,26 +609,66 @@ module Pacts expect(subject.provider.name).to eq "Provider" expect(subject.consumer_version_number).to eq "1.2.4" expect(subject.revision_number).to eq 2 - end + context "when there are multiple pact publications for the pact version" do before do # Double check the data is set up correctly... expect(pact_1.pact_version_sha).to eq(pact_2.pact_version_sha) end - let(:td) { TestDataBuilder.new } let!(:pact_1) { td.create_pact_with_hierarchy("Foo", "1", "Bar").and_return(:pact) } let!(:pact_2) { td.create_consumer_version("2").create_pact(json_content: pact_1.json_content).and_return(:pact) } - subject { Repository.new.find_pact "Foo", nil, "Bar", pact_1.pact_version_sha } + let(:consumer_version_number) { nil } + + subject { Repository.new.find_pact "Foo", consumer_version_number, "Bar", pact_1.pact_version_sha } it "returns the latest pact, ordered by consumer version order" do expect(subject.consumer_version_number).to eq "2" end + + context "when the consumer_version_number is specified too (from the URL metadata)" do + let(:consumer_version_number) { "1" } + + it "returns the publication with the consumer version specified" do + expect(subject.consumer_version_number).to eq "1" + end + end + + context "when the consumer_version_number is specified too (from the URL metadata) but it doesn't exist (anymore)" do + let(:consumer_version_number) { "9" } + + it "returns the pact matching the sha with the latest consumer version" do + expect(subject.consumer_version_number).to eq "2" + end + end + + # Not sure when this would happen + context "when the consumer_version_number is specified too (from the URL metadata) and it exists but it doesn't match the sha" do + before do + td.create_pact_with_hierarchy("Foo", "8", "Bar") + end + + let(:consumer_version_number) { "8" } + + it "returns the pact matching the sha with the latest consumer version" do + expect(subject.consumer_version_number).to eq "2" + end + end + + context "when the pact has multiple revisions and goes back to a previous revision" do + before do + td.revise_pact + .revise_pact(pact_1.json_content) + end + + it "returns the latest revision" do + expect(subject.revision_number).to eq 3 + end + end end end - end describe "find_all_revisions" do