From 76668639118eb18b953b1d1e64aefeeb2bbb30d7 Mon Sep 17 00:00:00 2001 From: Beth Skurrie Date: Mon, 26 Aug 2019 14:12:29 +1000 Subject: [PATCH] feat: add content type to return pact with extra metadata (eg tags) --- .../api/decorators/extended_pact_decorator.rb | 39 ++++++++++++ .../api/decorators/pact_decorator.rb | 3 - lib/pact_broker/api/pact_broker_urls.rb | 4 ++ lib/pact_broker/api/resources/pact.rb | 9 ++- lib/pact_broker/domain/pact.rb | 3 +- .../pacts/all_pact_publications.rb | 9 ++- lib/pact_broker/pacts/pact_publication.rb | 9 ++- .../extended_pact_decorator_spec.rb | 59 +++++++++++++++++++ .../api/decorators/pact_decorator_spec.rb | 1 - .../pacts/pact_publication_spec.rb | 24 +++++++- 10 files changed, 147 insertions(+), 13 deletions(-) create mode 100644 lib/pact_broker/api/decorators/extended_pact_decorator.rb create mode 100644 spec/lib/pact_broker/api/decorators/extended_pact_decorator_spec.rb diff --git a/lib/pact_broker/api/decorators/extended_pact_decorator.rb b/lib/pact_broker/api/decorators/extended_pact_decorator.rb new file mode 100644 index 000000000..760fc7600 --- /dev/null +++ b/lib/pact_broker/api/decorators/extended_pact_decorator.rb @@ -0,0 +1,39 @@ +require 'pact_broker/api/decorators/pact_decorator' + +module PactBroker + module Api + module Decorators + class ExtendedPactDecorator < PactDecorator + class TagDecorator < BaseDecorator + property :name + property :latest, getter: ->(_) { true } + + link "pb:latest-pact" do | opts | + { + name: "The latest pact with the tag #{represented.name}", + href: latest_tagged_pact_url(represented.pact, represented.name, opts[:base_url]) + } + end + end + + property :content_hash, as: :contract + collection :head_tags, exec_context: :decorator, as: :tags, embedded: true, extend: TagDecorator + + # TODO rather than remove the contract keys that we added in the super class, + # it would be better to inherit from a shared super class + def to_hash(options = {}) + keys_to_remove = represented.content_hash.keys + super.each_with_object({}) do | (key, value), new_hash | + new_hash[key] = value unless keys_to_remove.include?(key) + end + end + + def head_tags + represented.head_tag_names.collect do | tag_name | + OpenStruct.new(name: tag_name, pact: represented) + end + end + end + end + end +end \ No newline at end of file diff --git a/lib/pact_broker/api/decorators/pact_decorator.rb b/lib/pact_broker/api/decorators/pact_decorator.rb index 618046cc0..53efe6140 100644 --- a/lib/pact_broker/api/decorators/pact_decorator.rb +++ b/lib/pact_broker/api/decorators/pact_decorator.rb @@ -3,11 +3,8 @@ require 'pact_broker/api/decorators/timestamps' module PactBroker - module Api - module Decorators - class PactDecorator < BaseDecorator include Timestamps diff --git a/lib/pact_broker/api/pact_broker_urls.rb b/lib/pact_broker/api/pact_broker_urls.rb index 9abd4ef5e..1dacdb5a3 100644 --- a/lib/pact_broker/api/pact_broker_urls.rb +++ b/lib/pact_broker/api/pact_broker_urls.rb @@ -90,6 +90,10 @@ def latest_untagged_pact_url pact, base_url "#{pactigration_base_url(base_url, pact)}/latest-untagged" end + def latest_tagged_pact_url pact, tag_name, base_url + "#{latest_pact_url(base_url, pact)}/#{url_encode(tag_name)}" + end + def latest_pacts_url base_url "#{base_url}/pacts/latest" end diff --git a/lib/pact_broker/api/resources/pact.rb b/lib/pact_broker/api/resources/pact.rb index 42bf3f28d..6487a8bec 100644 --- a/lib/pact_broker/api/resources/pact.rb +++ b/lib/pact_broker/api/resources/pact.rb @@ -2,6 +2,7 @@ require 'pact_broker/api/resources/base_resource' require 'pact_broker/api/resources/pacticipant_resource_methods' require 'pact_broker/api/decorators/pact_decorator' +require 'pact_broker/api/decorators/extended_pact_decorator' require 'pact_broker/json' require 'pact_broker/pacts/pact_params' require 'pact_broker/api/contracts/put_pact_params_contract' @@ -26,7 +27,9 @@ class Pact < BaseResource def content_types_provided [["application/hal+json", :to_json], ["application/json", :to_json], - ["text/html", :to_html]] + ["text/html", :to_html], + ["application/vnd.pactbroker.pact.v1+json", :to_extended_json] + ] end def content_types_accepted @@ -78,6 +81,10 @@ def to_json PactBroker::Api::Decorators::PactDecorator.new(pact).to_json(user_options: decorator_context(metadata: identifier_from_path[:metadata])) end + def to_extended_json + PactBroker::Api::Decorators::ExtendedPactDecorator.new(pact).to_json(user_options: decorator_context(metadata: identifier_from_path[:metadata])) + end + def to_html PactBroker.configuration.html_pact_renderer.call( pact, { diff --git a/lib/pact_broker/domain/pact.rb b/lib/pact_broker/domain/pact.rb index 14b61d2b4..de53e2a0d 100644 --- a/lib/pact_broker/domain/pact.rb +++ b/lib/pact_broker/domain/pact.rb @@ -6,7 +6,7 @@ module PactBroker module Domain class Pact - attr_accessor :id, :provider, :consumer_version, :consumer, :created_at, :json_content, :consumer_version_number, :revision_number, :pact_version_sha, :latest_verification + attr_accessor :id, :provider, :consumer_version, :consumer, :created_at, :json_content, :consumer_version_number, :revision_number, :pact_version_sha, :latest_verification, :head_tag_names def initialize attributes attributes.each_pair do | key, value | @@ -54,6 +54,5 @@ def pact_publication_id id end end - end end diff --git a/lib/pact_broker/pacts/all_pact_publications.rb b/lib/pact_broker/pacts/all_pact_publications.rb index 33a3006c6..8924128ff 100644 --- a/lib/pact_broker/pacts/all_pact_publications.rb +++ b/lib/pact_broker/pacts/all_pact_publications.rb @@ -101,7 +101,14 @@ def to_domain_without_tags consumer_version_number: consumer_version_number, revision_number: revision_number, pact_version_sha: pact_version_sha, - created_at: created_at) + created_at: created_at, + head_tag_names: head_tag_names) + end + + def head_tag_names + # Avoid circular dependency + require 'pact_broker/pacts/latest_tagged_pact_publications' + LatestTaggedPactPublications.where(id: id).select(:tag_name).collect{|t| t[:tag_name]} end def to_domain_with_content diff --git a/lib/pact_broker/pacts/pact_publication.rb b/lib/pact_broker/pacts/pact_publication.rb index beddeb12b..724826bbb 100644 --- a/lib/pact_broker/pacts/pact_publication.rb +++ b/lib/pact_broker/pacts/pact_publication.rb @@ -31,10 +31,14 @@ def before_create self.revision_number ||= 1 end - def latest_tag_names + def head_tag_names LatestTaggedPactPublications.where(id: id).select(:tag_name).collect{|t| t[:tag_name]} end + def consumer_version_tags + consumer_version.tags + end + def latest_verification pact_version.latest_verification end @@ -50,7 +54,8 @@ def to_domain json_content: pact_version.content, pact_version_sha: pact_version.sha, latest_verification: latest_verification, - created_at: created_at + created_at: created_at, + head_tag_names: head_tag_names ) end diff --git a/spec/lib/pact_broker/api/decorators/extended_pact_decorator_spec.rb b/spec/lib/pact_broker/api/decorators/extended_pact_decorator_spec.rb new file mode 100644 index 000000000..a874cfdcf --- /dev/null +++ b/spec/lib/pact_broker/api/decorators/extended_pact_decorator_spec.rb @@ -0,0 +1,59 @@ +require 'pact_broker/api/decorators/extended_pact_decorator' + +module PactBroker + module Api + module Decorators + describe ExtendedPactDecorator do + before do + allow(decorator).to receive(:templated_diff_url).and_return('templated-diff-url') + allow(decorator).to receive(:verification_publication_url).and_return('verification-publication-url') + end + let(:content_hash) { + { + 'consumer' => {'name' => 'Consumer'}, + 'provider' => {'name' => 'Provider'}, + 'interactions' => [], + 'metadata' => {} + } + } + + let(:base_url) { 'http://example.org' } + let(:created_at) { Time.new(2014, 3, 4) } + let(:pact) { double('pact', + content_hash: content_hash, + created_at: created_at, + consumer: consumer, + provider: provider, + consumer_version: consumer_version, + consumer_version_number: '1234', + pact_version_sha: '9999', + revision_number: 2, + name: 'A Pact', + head_tag_names: head_tag_names + )} + let(:head_tag_names) { ['prod'] } + let(:consumer) { instance_double(PactBroker::Domain::Pacticipant, name: 'A Consumer')} + let(:provider) { instance_double(PactBroker::Domain::Pacticipant, name: 'A Provider')} + let(:consumer_version) { instance_double(PactBroker::Domain::Version, number: '1234', pacticipant: consumer)} + let(:metadata) { "abcd" } + let(:decorator) { ExtendedPactDecorator.new(pact) } + let(:json) { decorator.to_json(user_options: { base_url: base_url, metadata: metadata }) } + subject { JSON.parse(json, symbolize_names: true) } + + it "includes an array of tags" do + expect(subject[:_embedded][:tags].first).to include name: 'prod', latest: true + # Can't seem to stub the verification_publication_url method on the TagDecorator + expect(subject[:_embedded][:tags].first[:_links][:'pb:latest-pact'][:href]).to eq "http://example.org/pacts/provider/A%20Provider/consumer/A%20Consumer/latest/prod" + end + + it "includes the pact contents under the contract key" do + expect(subject[:contract]).to eq JSON.parse(content_hash.to_json, symbolize_names: true) + end + + it "does not include the contract contents in the root" do + expect(subject).to_not have_key(:interactions) + end + end + end + end +end diff --git a/spec/lib/pact_broker/api/decorators/pact_decorator_spec.rb b/spec/lib/pact_broker/api/decorators/pact_decorator_spec.rb index 04f64a0f4..f9830478d 100644 --- a/spec/lib/pact_broker/api/decorators/pact_decorator_spec.rb +++ b/spec/lib/pact_broker/api/decorators/pact_decorator_spec.rb @@ -5,7 +5,6 @@ module PactBroker module Api module Decorators describe PactDecorator do - before do allow(decorator).to receive(:templated_diff_url).and_return('templated-diff-url') allow(decorator).to receive(:verification_publication_url).and_return('verification-publication-url') diff --git a/spec/lib/pact_broker/pacts/pact_publication_spec.rb b/spec/lib/pact_broker/pacts/pact_publication_spec.rb index 5690bb857..b1d0a1dcb 100644 --- a/spec/lib/pact_broker/pacts/pact_publication_spec.rb +++ b/spec/lib/pact_broker/pacts/pact_publication_spec.rb @@ -64,7 +64,25 @@ module Pacts end end - describe "#latest_tag_names" do + describe "#consumer_version_tags" do + before do + td.create_pact_with_hierarchy("Foo", "1.2.3", "Bar") + .create_consumer_version_tag("no") + .create_consumer_version("3.4.5") + .create_consumer_version_tag("yes") + .create_pact + .create_consumer_version("5.6.7") + .create_consumer_version_tag("no") + end + + let(:pact_publication) { PactPublication.find(id: td.pact.id) } + + it "" do + expect(pact_publication.consumer_version_tags.collect(&:name)).to eq ["yes"] + end + end + + describe "#head_tag_names" do before do td.create_pact_with_hierarchy("Foo", "1.2.3", "Bar") .create_consumer_version_tag("no") @@ -80,13 +98,13 @@ module Pacts context "when the pact is the latest for a tag" do it "returns the relevant tag names" do - expect(pact_publication.latest_tag_names).to eq ["yes"] + expect(pact_publication.head_tag_names).to eq ["yes"] end end context "when the pact is not the latest for a tag" do it "returns the relevant tag names" do - expect(pact_publication.latest_tag_names).to eq ["yes"] + expect(pact_publication.head_tag_names).to eq ["yes"] end end end