From 615025e9acf8786e98c1965cfe04fcbb54c1212e Mon Sep 17 00:00:00 2001 From: Beth Skurrie Date: Wed, 19 Sep 2018 14:59:20 +1000 Subject: [PATCH] feat(tagged-pact-versions): add endpoint to view and delete a collection of pact versions by tag --- lib/pact_broker/api.rb | 1 + .../api/decorators/pact_version_decorator.rb | 5 -- .../tagged_pact_versions_decorator.rb | 46 ++++++++++ lib/pact_broker/api/resources/index.rb | 6 ++ .../api/resources/tagged_pact_versions.rb | 38 ++++++++ .../views/index/tagged-pact-versions.markdown | 7 ++ lib/pact_broker/pacts/repository.rb | 21 ++++- lib/pact_broker/pacts/service.rb | 4 + .../features/get_tagged_pact_versions_spec.rb | 22 +++++ .../decorators/pact_version_decorator_spec.rb | 3 - .../tagged_pact_versions_decorator_spec.rb | 38 ++++++++ .../resources/tagged_pact_versions_spec.rb | 86 +++++++++++++++++++ spec/lib/pact_broker/pacts/repository_spec.rb | 44 +++++++++- spec/support/shared_context.rb | 10 +++ 14 files changed, 316 insertions(+), 15 deletions(-) create mode 100644 lib/pact_broker/api/decorators/tagged_pact_versions_decorator.rb create mode 100644 lib/pact_broker/api/resources/tagged_pact_versions.rb create mode 100644 lib/pact_broker/doc/views/index/tagged-pact-versions.markdown create mode 100644 spec/features/get_tagged_pact_versions_spec.rb create mode 100644 spec/lib/pact_broker/api/decorators/tagged_pact_versions_decorator_spec.rb create mode 100644 spec/lib/pact_broker/api/resources/tagged_pact_versions_spec.rb create mode 100644 spec/support/shared_context.rb diff --git a/lib/pact_broker/api.rb b/lib/pact_broker/api.rb index 9bf8c4e30..6b07ee707 100644 --- a/lib/pact_broker/api.rb +++ b/lib/pact_broker/api.rb @@ -11,6 +11,7 @@ module PactBroker add ['pacts', 'provider', :provider_name, 'consumer', :consumer_name, 'versions'], Api::Resources::PactVersions, {resource_name: "pact_publications"} add ['pacts', 'provider', :provider_name, 'consumer', :consumer_name, 'versions', :consumer_version_number], Api::Resources::Pact, {resource_name: "pact_publication", deprecated: true} # Not the standard URL, but keep for backwards compatibility + add ['pacts', 'provider', :provider_name, 'consumer', :consumer_name, 'tag', :tag], Api::Resources::TaggedPactVersions, {resource_name: "tagged_pact_publications"} # Pacts add ['pacts', 'provider', :provider_name, 'consumer', :consumer_name, 'version', :consumer_version_number], Api::Resources::Pact, {resource_name: "pact_publication"} diff --git a/lib/pact_broker/api/decorators/pact_version_decorator.rb b/lib/pact_broker/api/decorators/pact_version_decorator.rb index 552bcbf00..c40889de0 100644 --- a/lib/pact_broker/api/decorators/pact_version_decorator.rb +++ b/lib/pact_broker/api/decorators/pact_version_decorator.rb @@ -3,13 +3,9 @@ require 'pact_broker/api/decorators/timestamps' module PactBroker - module Api - module Decorators - class PactVersionDecorator < BaseDecorator - include Timestamps property :consumer_version, as: :consumerVersion, embedded: true, decorator: EmbeddedVersionDecorator @@ -20,7 +16,6 @@ class PactVersionDecorator < BaseDecorator title: represented.name } end - end end end diff --git a/lib/pact_broker/api/decorators/tagged_pact_versions_decorator.rb b/lib/pact_broker/api/decorators/tagged_pact_versions_decorator.rb new file mode 100644 index 000000000..938d92a48 --- /dev/null +++ b/lib/pact_broker/api/decorators/tagged_pact_versions_decorator.rb @@ -0,0 +1,46 @@ +require_relative 'base_decorator' +require_relative 'pact_version_decorator' + +module PactBroker + module Api + module Decorators + class TaggedPactVersionsDecorator < BaseDecorator + + collection :entries, as: :pacts, embedded: true, :extend => PactBroker::Api::Decorators::PactVersionDecorator + + link :self do | context | + { + href: context[:resource_url], + title: "All versions of the pact between #{context[:consumer_name]} and #{context[:provider_name]} with tag #{context[:tag]}" + } + end + + link :'pb:consumer' do | context | + { + href: pacticipant_url(context[:base_url], OpenStruct.new(name: context[:consumer_name])), + title: "Consumer", + name: context[:consumer_name] + } + end + + link :'pb:provider' do | context | + { + href: pacticipant_url(context[:base_url], OpenStruct.new(name: context[:provider_name])), + title: "Provider", + name: context[:provider_name] + } + end + + links :'pb:pact-versions' do | context | + represented.collect do | pact | + { + :href => pact_url(context[:base_url], pact), + :title => "Pact version", + :name => pact.version_and_updated_date + } + end + end + end + end + end +end diff --git a/lib/pact_broker/api/resources/index.rb b/lib/pact_broker/api/resources/index.rb index 076218567..acef34a8d 100644 --- a/lib/pact_broker/api/resources/index.rb +++ b/lib/pact_broker/api/resources/index.rb @@ -38,6 +38,12 @@ def links title: 'Latest pact versions', templated: false }, + 'pb:tagged-pact-versions' => + { + href: base_url + '/pacts/provider/{provider}/consumer/{consumer}/tag/{tag}', + title: 'All versions of a pact for a given consumer, provider and consumer version tag', + templated: false + }, 'pb:pacticipants' => { href: base_url + '/pacticipants', diff --git a/lib/pact_broker/api/resources/tagged_pact_versions.rb b/lib/pact_broker/api/resources/tagged_pact_versions.rb new file mode 100644 index 000000000..981e2270e --- /dev/null +++ b/lib/pact_broker/api/resources/tagged_pact_versions.rb @@ -0,0 +1,38 @@ +require 'pact_broker/api/resources/base_resource' +require 'pact_broker/configuration' +require 'pact_broker/api/decorators/tagged_pact_versions_decorator' + +module PactBroker + module Api + module Resources + class TaggedPactVersions < BaseResource + + def content_types_provided + [["application/hal+json", :to_json]] + end + + def allowed_methods + ["GET", "DELETE", "OPTIONS"] + end + + def resource_exists? + pacticipant_service.find_pacticipant_by_name(consumer_name) && + pacticipant_service.find_pacticipant_by_name(provider_name) + end + + def to_json + PactBroker::Api::Decorators::TaggedPactVersionsDecorator.new(pacts).to_json(user_options: decorator_context(identifier_from_path)) + end + + def delete_resource + pact_service.delete_all_pact_versions_between consumer_name, and: provider_name, tag: identifier_from_path[:tag] + true + end + + def pacts + pact_service.find_all_pact_versions_between consumer_name, and: provider_name, tag: identifier_from_path[:tag] + end + end + end + end +end diff --git a/lib/pact_broker/doc/views/index/tagged-pact-versions.markdown b/lib/pact_broker/doc/views/index/tagged-pact-versions.markdown new file mode 100644 index 000000000..d84159806 --- /dev/null +++ b/lib/pact_broker/doc/views/index/tagged-pact-versions.markdown @@ -0,0 +1,7 @@ +# Tagged pact versions + +Allowed methods: `GET`, `DELETE` + +Lists all the pact versions with the specified consumer, provider and consumer version tag. + +Send a `DELETE` request to the resource to batch delete all the versions. diff --git a/lib/pact_broker/pacts/repository.rb b/lib/pact_broker/pacts/repository.rb index 88cf2906c..013f7d206 100644 --- a/lib/pact_broker/pacts/repository.rb +++ b/lib/pact_broker/pacts/repository.rb @@ -75,15 +75,17 @@ def delete_by_version_id version_id end def find_all_pact_versions_between consumer_name, options - provider_name = options.fetch(:and) - LatestPactPublicationsByConsumerVersion + find_all_database_versions_between(consumer_name, options) .eager(:tags) - .consumer(consumer_name) - .provider(provider_name) .reverse_order(:consumer_version_order) .collect(&:to_domain) end + def delete_all_pact_versions_between consumer_name, options + ids = find_all_database_versions_between(consumer_name, options).select_for_subquery(:id) + PactPublication.where(id: ids).delete + end + def find_latest_pact_versions_for_provider provider_name, tag = nil if tag LatestTaggedPactPublications.provider(provider_name).order_ignore_case(:consumer_name).where(tag_name: tag).collect(&:to_domain) @@ -255,6 +257,17 @@ def create_pact_version consumer_id, provider_id, sha, json_content pact_version = PactVersion.new(consumer_id: consumer_id, provider_id: provider_id, sha: sha, content: json_content) pact_version.save end + + def find_all_database_versions_between(consumer_name, options) + provider_name = options.fetch(:and) + + query = LatestPactPublicationsByConsumerVersion + .consumer(consumer_name) + .provider(provider_name) + + query = query.tag(options[:tag]) if options[:tag] + query + end end end end diff --git a/lib/pact_broker/pacts/service.rb b/lib/pact_broker/pacts/service.rb index a1fbe308e..955677310 100644 --- a/lib/pact_broker/pacts/service.rb +++ b/lib/pact_broker/pacts/service.rb @@ -68,6 +68,10 @@ def find_all_pact_versions_between consumer, options pact_repository.find_all_pact_versions_between consumer, options end + def delete_all_pact_versions_between consumer, options + pact_repository.delete_all_pact_versions_between consumer, options + end + def find_latest_pact_versions_for_provider provider_name, options = {} pact_repository.find_latest_pact_versions_for_provider provider_name, options[:tag] end diff --git a/spec/features/get_tagged_pact_versions_spec.rb b/spec/features/get_tagged_pact_versions_spec.rb new file mode 100644 index 000000000..672d6e264 --- /dev/null +++ b/spec/features/get_tagged_pact_versions_spec.rb @@ -0,0 +1,22 @@ +describe "retrieving tagged pact versions" do + + let(:path) { "/pacts/provider/Provider/consumer/Consumer/tag/prod"} + + subject { get path; last_response } + let(:json_response_body) { JSON.parse(subject.body, symbolize_names: true) } + + before do + TestDataBuilder.new + .create_consumer("Consumer") + .create_provider("Provider") + .create_consumer_version("1.2.3") + .create_consumer_version_tag("prod") + .create_pact + .create_consumer_version("4.5.6") + .create_pact + end + + it "returns the latest tagged pact version" do + expect(json_response_body[:_links][:self][:href]).to end_with("1.2.3") + end +end diff --git a/spec/lib/pact_broker/api/decorators/pact_version_decorator_spec.rb b/spec/lib/pact_broker/api/decorators/pact_version_decorator_spec.rb index fdd709e93..9f820a06c 100644 --- a/spec/lib/pact_broker/api/decorators/pact_version_decorator_spec.rb +++ b/spec/lib/pact_broker/api/decorators/pact_version_decorator_spec.rb @@ -2,11 +2,8 @@ require 'pact_broker/api/decorators/pact_version_decorator' module PactBroker - module Api - module Decorators - describe PactVersionDecorator do let(:json_content) { diff --git a/spec/lib/pact_broker/api/decorators/tagged_pact_versions_decorator_spec.rb b/spec/lib/pact_broker/api/decorators/tagged_pact_versions_decorator_spec.rb new file mode 100644 index 000000000..8b0172456 --- /dev/null +++ b/spec/lib/pact_broker/api/decorators/tagged_pact_versions_decorator_spec.rb @@ -0,0 +1,38 @@ +require 'pact_broker/api/decorators/tagged_pact_versions_decorator' + +def register_fixture name + yield +end + + +module PactBroker + module Api + module Decorators + describe TaggedPactVersionsDecorator do + + let(:user_options) do + register_fixture(:tagged_pact_versions_decorator_args) do + { + consumer_name: "Foo", + provider_name: "Bar" + } + end + end + + let(:pact_versions) { [pact_version] } + let(:pact_version) do + instance_double("PactBroker::Domain::Pact") + end + + let(:decorator) { TaggedPactVersionsDecorator.new(pact_versions) } + let(:json) { decorator.to_json(user_options: user_options) } + subject { JSON.parse(json) } + + xit "" do + subject(subject['_links']['pb:consumer']).to eq({}) + end + + end + end + end +end diff --git a/spec/lib/pact_broker/api/resources/tagged_pact_versions_spec.rb b/spec/lib/pact_broker/api/resources/tagged_pact_versions_spec.rb new file mode 100644 index 000000000..f6ee84099 --- /dev/null +++ b/spec/lib/pact_broker/api/resources/tagged_pact_versions_spec.rb @@ -0,0 +1,86 @@ +require 'pact_broker/api/resources/tagged_pact_versions' + +module PactBroker + module Api + module Resources + describe TaggedPactVersions do + include_context "stubbed services" + + before do + allow(pacticipant_service).to receive(:find_pacticipant_by_name).with("Foo").and_return(consumer) + allow(pacticipant_service).to receive(:find_pacticipant_by_name).with("Bar").and_return(provider) + end + + let(:path) { "/pacts/provider/Bar/consumer/Foo/tag/prod" } + let(:consumer) { double('Bar') } + let(:provider) { double('Foo') } + + context "GET" do + before do + allow(PactBroker::Api::Decorators::TaggedPactVersionsDecorator).to receive(:new).and_return(decorator) + allow(pact_service).to receive(:find_all_pact_versions_between).and_return(pact_versions) + end + + let(:decorator) { instance_double(PactBroker::Api::Decorators::TaggedPactVersionsDecorator, to_json: 'json') } + let(:pact_versions) { double('pacts') } + + subject { get(path) } + + let(:user_options) do + { + base_url: "http://example.org", + resource_url: "http://example.org/pacts/provider/Bar/consumer/Foo/tag/prod", + consumer_name: "Foo", + provider_name: "Bar", + tag: "prod" + } + end + + it "finds all the pacts with the given consumer/provider/tag" do + expect(pact_service).to receive(:find_all_pact_versions_between).with("Foo", and: "Bar", tag: "prod") + subject + end + + it "returns a 200 OK hal+json response" do + expect(subject).to be_a_hal_json_success_response + end + + it "creates a JSON representation of the pact versions" do + expect(PactBroker::Api::Decorators::TaggedPactVersionsDecorator).to receive(:new).with(pact_versions) + expect(decorator).to receive(:to_json).with(user_options: hash_including(user_options)) + subject + end + + it "returns the JSON representation of the pact versions" do + expect(subject.body).to eq 'json' + end + + context "with the consumer or provider do not exist" do + let(:consumer) { nil } + + it "returns a 404" do + expect(subject).to be_a_404_response + end + end + end + + context "DELETE" do + before do + allow(pact_service).to receive(:delete_all_pact_versions_between) + end + + subject { delete(path) } + + it "deletes all the pacts with the given consumer/provider/tag" do + expect(pact_service).to receive(:delete_all_pact_versions_between).with("Foo", and: "Bar", tag: "prod") + subject + end + + it "returns a 204" do + expect(subject.status).to eq 204 + end + end + end + end + end +end diff --git a/spec/lib/pact_broker/pacts/repository_spec.rb b/spec/lib/pact_broker/pacts/repository_spec.rb index 0aa783363..4e742c627 100644 --- a/spec/lib/pact_broker/pacts/repository_spec.rb +++ b/spec/lib/pact_broker/pacts/repository_spec.rb @@ -277,17 +277,55 @@ module Pacts .create_pact end - subject { Repository.new.find_all_pact_versions_between consumer_name, :and => provider_name } + subject { Repository.new.find_all_pact_versions_between(consumer_name, :and => provider_name) } it "returns the pacts between the specified consumer and provider" do expect(subject.size).to eq 2 expect(subject.first.consumer.name).to eq consumer_name expect(subject.first.provider.name).to eq provider_name expect(subject.first.consumer_version.number).to eq "2.3.4" - expect(subject.first.consumer_version.tags.first.name).to eq "branch" - expect(subject.first.consumer_version.tags.last.name).to eq "prod" + expect(subject.first.consumer_version.tags.count).to eq 2 end + context "with a tag" do + subject { Repository.new.find_all_pact_versions_between(consumer_name, :and => provider_name, tag: "prod") } + + it "returns the pacts between the specified consumer and provider with the given tag" do + expect(subject.size).to eq 1 + expect(subject.first.consumer_version.number).to eq "2.3.4" + end + end + end + + describe "#delete_all_pact_versions_between" do + + before do + TestDataBuilder.new + .create_consumer(consumer_name) + .create_consumer_version("1.2.3") + .create_provider(provider_name) + .create_pact + .create_consumer_version("2.3.4") + .create_consumer_version_tag("prod") + .create_consumer_version_tag("branch") + .create_pact + .create_provider("Another Provider") + .create_pact + end + + subject { Repository.new.delete_all_pact_versions_between(consumer_name, :and => provider_name) } + + it "deletes the pacts between the specified consumer and provider" do + expect { subject }.to change { PactPublication.count }.by(-2) + end + + context "with a tag" do + subject { Repository.new.delete_all_pact_versions_between(consumer_name, :and => provider_name, tag: "prod") } + + it "deletes the pacts between the specified consumer and provider with the given tag" do + expect { subject }.to change { PactPublication.count }.by(-1) + end + end end describe "#find_latest_pact_versions_for_provider" do diff --git a/spec/support/shared_context.rb b/spec/support/shared_context.rb new file mode 100644 index 000000000..1a9e637e6 --- /dev/null +++ b/spec/support/shared_context.rb @@ -0,0 +1,10 @@ +RSpec.shared_context "stubbed services" do + + let(:pact_service) { class_double("PactBroker::Pacts::Service").as_stubbed_const } + let(:pacticipant_service) { class_double("PactBroker::Pacticipants::Service").as_stubbed_const } + + before do + allow_any_instance_of(described_class).to receive(:pact_service).and_return(pact_service) + allow_any_instance_of(described_class).to receive(:pacticipant_service).and_return(pacticipant_service) + end +end