From 15f0688c8feb363ae9dac36dfe5492af2c234669 Mon Sep 17 00:00:00 2001 From: Beth Skurrie Date: Thu, 3 May 2018 17:14:53 +1000 Subject: [PATCH] feat: create endpoint to compare arbitrary pact versions, ignoring interaction/message order --- lib/pact_broker/api.rb | 2 + .../api/resources/pact_content_diff.rb | 16 +++++++- lib/pact_broker/pacts/diff.rb | 37 +++++++++++------ lib/pact_broker/pacts/pact_params.rb | 10 +++++ .../pacts/sort_verifiable_content.rb | 41 +++++++++++++++++++ spec/lib/pact_broker/pacts/diff_spec.rb | 41 +++++++++++++++---- .../lib/pact_broker/pacts/pact_params_spec.rb | 41 +++++++++++++++---- .../pacts/sort_verifiable_content_spec.rb | 25 +++++++++++ 8 files changed, 183 insertions(+), 30 deletions(-) create mode 100644 lib/pact_broker/pacts/sort_verifiable_content.rb create mode 100644 spec/lib/pact_broker/pacts/sort_verifiable_content_spec.rb diff --git a/lib/pact_broker/api.rb b/lib/pact_broker/api.rb index 00fcdf08e..a420b80f5 100644 --- a/lib/pact_broker/api.rb +++ b/lib/pact_broker/api.rb @@ -16,6 +16,8 @@ module PactBroker add ['pacts', 'provider', :provider_name, 'consumer', :consumer_name, 'pact-version', :pact_version_sha], Api::Resources::PactVersion, {resource_name: "pact_publication"} add ['pacts', 'provider', :provider_name, 'consumer', :consumer_name, 'version', :consumer_version_number, 'previous-distinct'], Api::Resources::PreviousDistinctPactVersion, {resource_name: "previous_distinct_pact_version"} add ['pacts', 'provider', :provider_name, 'consumer', :consumer_name, 'version', :consumer_version_number, 'diff', 'previous-distinct'], Api::Resources::PactContentDiff, {resource_name: "previous_distinct_pact_version_diff"} + add ['pacts', 'provider', :provider_name, 'consumer', :consumer_name, 'version', :consumer_version_number, 'diff', 'version', :comparison_consumer_version], Api::Resources::PactContentDiff, {resource_name: "pact_version_diff_by_consumer_version"} + add ['pacts', 'provider', :provider_name, 'consumer', :consumer_name, 'pact-version', :pact_version_sha, 'diff', 'pact-version', :comparison_pact_version_sha], Api::Resources::PactContentDiff, {resource_name: "pact_version_diff_by_pact_version_sha"} # Verifications add ['pacts', 'provider', :provider_name, 'consumer', :consumer_name, 'pact-version', :pact_version_sha, 'verification-results'], Api::Resources::Verifications, {resource_name: "verification_results"} diff --git a/lib/pact_broker/api/resources/pact_content_diff.rb b/lib/pact_broker/api/resources/pact_content_diff.rb index 3ac35a914..ac3d90f0d 100644 --- a/lib/pact_broker/api/resources/pact_content_diff.rb +++ b/lib/pact_broker/api/resources/pact_content_diff.rb @@ -20,7 +20,7 @@ def resource_exists? end def to_text - output = PactBroker::Pacts::Diff.new.process pact_params.merge(base_url: base_url) + output = PactBroker::Pacts::Diff.new.process pact_params.merge(base_url: base_url), comparison_pact_params, raw: false response.body = output end @@ -29,7 +29,19 @@ def pact end def pact_params - @pact_params ||= PactBroker::Pacts::PactParams.from_request request, path_info + @pact_params ||= PactBroker::Pacts::PactParams.from_path_info identifier_from_path + end + + def comparison_pact_params + if identifier_from_path[:comparison_consumer_version_number] || identifier_from_path[:comparison_pact_version_sha] + comparison_identifier_from_path = identifier_from_path.merge( + consumer_version_number: identifier_from_path[:comparison_consumer_version_number], + pact_version_sha: identifier_from_path[:comparison_pact_version_sha], + base_url: base_url) + PactBroker::Pacts::PactParams.from_path_info(comparison_identifier_from_path) + else + nil + end end end end diff --git a/lib/pact_broker/pacts/diff.rb b/lib/pact_broker/pacts/diff.rb index 3778a0be5..25ab8238c 100644 --- a/lib/pact_broker/pacts/diff.rb +++ b/lib/pact_broker/pacts/diff.rb @@ -1,6 +1,7 @@ require 'pact_broker/api/pact_broker_urls' require 'pact_broker/date_helper' require 'pact_broker/pacts/create_formatted_diff' +require 'pact_broker/pacts/sort_verifiable_content' require 'pact_broker/repositories' require 'yaml' @@ -10,13 +11,13 @@ module Pacts class Diff include PactBroker::Repositories - def process(params) + def process(params, comparison_pact_params = nil, options = {}) pact = find_pact(params) - previous_distinct_pact = pact_repository.find_previous_distinct_pact(pact) + comparison_pact = comparison_pact_params ? find_pact(comparison_pact_params) : pact_repository.find_previous_distinct_pact(pact) - if previous_distinct_pact - next_pact = pact_repository.find_next_pact(previous_distinct_pact) - DiffDecorator.new(pact, previous_distinct_pact, next_pact, params[:base_url]).to_text + if comparison_pact + next_pact = pact_repository.find_next_pact(comparison_pact) || pact + DiffDecorator.new(pact, comparison_pact, next_pact, params[:base_url], { raw: options[:raw] }).to_text else no_previous_version_message pact end @@ -27,7 +28,8 @@ def process(params) def find_pact(params) pact_repository.find_pact(params.consumer_name, params.consumer_version_number, - params.provider_name) + params.provider_name, + params.pact_version_sha) end def no_previous_version_message(pact) @@ -46,11 +48,12 @@ def no_previous_version_message(pact) # the latest distinct version content was first created. class DiffDecorator - def initialize(pact, previous_distinct_pact, next_pact, base_url) + def initialize(pact, comparison_pact, next_pact, base_url, options) @pact = pact - @previous_distinct_pact = previous_distinct_pact + @comparison_pact = comparison_pact @next_pact = next_pact @base_url = base_url + @options = options end def to_text @@ -59,7 +62,7 @@ def to_text private - attr_reader :pact, :previous_distinct_pact, :next_pact, :base_url + attr_reader :pact, :comparison_pact, :next_pact, :base_url, :options def change_date_in_words DateHelper.local_date_in_words next_pact.created_at @@ -70,7 +73,7 @@ def now end def header - title = "# Diff between versions #{previous_distinct_pact.consumer_version_number} and #{pact.consumer_version_number} of the pact between #{pact.consumer.name} and #{pact.provider.name}" + title = "# Diff between versions #{comparison_pact.consumer_version_number} and #{pact.consumer_version_number} of the pact between #{pact.consumer.name} and #{pact.provider.name}" description = "The following changes were made #{change_date_ago_in_words} ago (#{change_date_in_words})" title + "\n\n" + description @@ -78,7 +81,7 @@ def header def links self_url = PactBroker::Api::PactBrokerUrls.pact_url(base_url, pact) - previous_distinct_url = PactBroker::Api::PactBrokerUrls.pact_url(base_url, previous_distinct_pact) + previous_distinct_url = PactBroker::Api::PactBrokerUrls.pact_url(base_url, comparison_pact) links = { "current-pact-version" => { @@ -88,7 +91,7 @@ def links }, "previous-distinct-pact-version" => { "title" => "Pact", - "name" => previous_distinct_pact.name, + "name" => comparison_pact.name, "href" => previous_distinct_url } } @@ -96,12 +99,20 @@ def links end def diff - CreateFormattedDiff.(pact.json_content, previous_distinct_pact.json_content) + CreateFormattedDiff.(prepare_content(pact.json_content), prepare_content(comparison_pact.json_content)) end def change_date_ago_in_words DateHelper.distance_of_time_in_words next_pact.created_at, now end + + def prepare_content json_content + if options[:raw] + json_content + else + PactBroker::Pacts::SortVerifiableContent.call(json_content) + end + end end end end diff --git a/lib/pact_broker/pacts/pact_params.rb b/lib/pact_broker/pacts/pact_params.rb index 06de93f26..456658ac2 100644 --- a/lib/pact_broker/pacts/pact_params.rb +++ b/lib/pact_broker/pacts/pact_params.rb @@ -10,6 +10,16 @@ def initialize attributes merge!(attributes) end + def self.from_path_info path_info + new( + consumer_name: path_info.fetch(:consumer_name), + provider_name: path_info.fetch(:provider_name), + consumer_version_number: path_info[:consumer_version_number], + revision_number: path_info[:revision_number], + pact_version_sha: path_info[:pact_version_sha] + ) + end + def self.from_request request, path_info json_content = request.body.to_s parsed_content = begin diff --git a/lib/pact_broker/pacts/sort_verifiable_content.rb b/lib/pact_broker/pacts/sort_verifiable_content.rb new file mode 100644 index 000000000..b9149e506 --- /dev/null +++ b/lib/pact_broker/pacts/sort_verifiable_content.rb @@ -0,0 +1,41 @@ +require 'pact_broker/json' + +module PactBroker + module Pacts + class SortVerifiableContent + + def self.call json + hash = JSON.parse(json, PACT_PARSING_OPTIONS) + verifiable_content = if hash['interactions'] + hash['interactions'] + elsif hash['messages'] + hash['messages'] + end + order_verifiable_content(verifiable_content).to_json + end + + def self.order_verifiable_content array + array_with_ordered_hashes = order_hashes(array) + array_with_ordered_hashes.sort{|a, b| a.to_json <=> b.to_json } + end + + def self.order_hashes thing + case thing + when Hash then order_hash(thing) + when Array then order_child_array(thing) + else thing + end + end + + def self.order_child_array array + array.collect{|thing| order_hashes(thing) } + end + + def self.order_hash hash + hash.keys.sort.each_with_object({}) do | key, new_hash | + new_hash[key] = order_hashes(hash[key]) + end + end + end + end +end diff --git a/spec/lib/pact_broker/pacts/diff_spec.rb b/spec/lib/pact_broker/pacts/diff_spec.rb index c1287b50b..914f3409f 100644 --- a/spec/lib/pact_broker/pacts/diff_spec.rb +++ b/spec/lib/pact_broker/pacts/diff_spec.rb @@ -15,6 +15,19 @@ module Pacts end let(:pact_content_version_2) { load_fixture('consumer-provider.json') } let(:pact_content_version_3) { pact_content_version_2 } + let(:pact_content_version_4) do + hash = load_json_fixture('consumer-provider.json') + hash['interactions'].first['request']['method'] = 'delete' + hash.to_json + end + + let(:pact_params) do + PactBroker::Pacts::PactParams.new( + consumer_name: 'Consumer', + provider_name: 'Provider', + consumer_version_number: '3' + ) + end before do TestDataBuilder.new @@ -26,21 +39,36 @@ module Pacts .create_pact(json_content: pact_content_version_2) .create_consumer_version("3") .create_pact(json_content: pact_content_version_3) + .create_consumer_version("4") + .create_pact(json_content: pact_content_version_4) + allow(DateHelper).to receive(:local_date_in_words).and_return("a date") end - subject { Diff.new.process(pact_params.merge(base_url: 'http://example.org')) } - - context "when there is a previous distinct version" do + subject { Diff.new.process(pact_params.merge(base_url: 'http://example.org'), nil, raw: true) } - let(:pact_params) do + context "when a comparison version is specified" do + let(:comparison_pact_params) do PactBroker::Pacts::PactParams.new( consumer_name: 'Consumer', provider_name: 'Provider', - consumer_version_number: '3' - ) + consumer_version_number: '4' + ).merge(base_url: 'http://example.org') end + subject { Diff.new.process(pact_params.merge(base_url: 'http://example.org'), comparison_pact_params) } + + it "compares the two pacts" do + expect(subject).to include "Pact between Consumer (v3) and Provider" + expect(subject).to include "Pact between Consumer (v4) and Provider" + end + + it "includes a link to the comparison pact", pending: true do + expect(subject).to include "comparision-pact-version:" + end + end + + context "when there is a previous distinct version" do it "indicates when the previous change was made" do expect(subject).to include "The following changes were made less than a minute ago (a date)" end @@ -66,7 +94,6 @@ module Pacts end end end - end end end diff --git a/spec/lib/pact_broker/pacts/pact_params_spec.rb b/spec/lib/pact_broker/pacts/pact_params_spec.rb index 87ce56748..c693e4234 100644 --- a/spec/lib/pact_broker/pacts/pact_params_spec.rb +++ b/spec/lib/pact_broker/pacts/pact_params_spec.rb @@ -9,19 +9,44 @@ module Pacts let(:consumer_version_number) { '1.2.3' } let(:headers) { { 'X-Pact-Consumer-Version' => consumer_version_number, 'Host' => 'example.org' } } let(:revision_number) { '1' } + let(:path_info) do + { + consumer_name: 'Consumer', + provider_name: 'Provider', + consumer_version_number: '1.2.3', + revision_number: revision_number, + pact_version_sha: '123' + } + end + + describe "from_path_info" do + subject { PactParams.from_path_info(path_info) } + + it "extracts the consumer name from the path" do + expect(subject.consumer_name).to eq "Consumer" + end + + it "extracts the provider name from the path" do + expect(subject.provider_name).to eq "Provider" + end + + it "extracts the consumer_version_number from the path" do + expect(subject.consumer_version_number).to eq "1.2.3" + end + + it "extracts the revision_number from the path" do + expect(subject.revision_number).to eq "1" + end + + it "extracts the pact_version_sha from the path" do + expect(subject.pact_version_sha).to eq "123" + end + end describe "from_request" do context "from a PUT request" do let(:request) { Webmachine::Request.new("PUT", "/", headers, body)} - let(:path_info) do - { - consumer_name: 'Consumer', - provider_name: 'Provider', - consumer_version_number: '1.2.3', - revision_number: revision_number - } - end subject { PactParams.from_request(request, path_info) } diff --git a/spec/lib/pact_broker/pacts/sort_verifiable_content_spec.rb b/spec/lib/pact_broker/pacts/sort_verifiable_content_spec.rb new file mode 100644 index 000000000..459e916d1 --- /dev/null +++ b/spec/lib/pact_broker/pacts/sort_verifiable_content_spec.rb @@ -0,0 +1,25 @@ +require 'pact_broker/pacts/sort_verifiable_content' + +module PactBroker + module Pacts + describe SortVerifiableContent do + let(:pact_content_1) do + { + a: 1, + interactions: [{ a: 1, b: 2 }, { a: 2, b: 3 }] + }.to_json + end + + let(:pact_content_2) do + { + interactions: [{ b: 3, a: 2}, { b: 2, a: 1 }], + a: 1 + }.to_json + end + + it "sorts the interactions/messages and keys in a deterministic way" do + expect(SortVerifiableContent.call(pact_content_1).to_json).to eq(SortVerifiableContent.call(pact_content_2).to_json) + end + end + end +end