From c216bec84c5aa95f0aa86a6c9fe7277eec816b06 Mon Sep 17 00:00:00 2001 From: Beth Skurrie Date: Wed, 6 Dec 2023 13:52:14 +1100 Subject: [PATCH] feat: add latest version for branch endpoint (#644) --- lib/pact_broker/api.rb | 1 + .../api/decorators/branch_decorator.rb | 2 +- .../api/decorators/version_decorator.rb | 3 ++- .../api/decorators/versions_decorator.rb | 16 ++++++++++--- lib/pact_broker/api/pact_broker_urls.rb | 4 ++++ .../api/resources/latest_version.rb | 2 ++ .../get_latest_version_for_branch_spec.rb | 18 +++++++++++++++ ...cticipant_branches_decorator.approved.json | 2 +- .../api/decorators/version_decorator_spec.rb | 6 ++--- .../api/decorators/versions_decorator_spec.rb | 23 ++++++++++--------- 10 files changed, 57 insertions(+), 20 deletions(-) create mode 100644 spec/features/get_latest_version_for_branch_spec.rb diff --git a/lib/pact_broker/api.rb b/lib/pact_broker/api.rb index 3954a1ce4..4e7cefa00 100644 --- a/lib/pact_broker/api.rb +++ b/lib/pact_broker/api.rb @@ -97,6 +97,7 @@ def self.build_api(application_context = PactBroker::ApplicationContext.default_ add ["pacticipants", :pacticipant_name, "versions", :pacticipant_version_number, "tags", :tag_name], Api::Resources::Tag, {resource_name: "pacticipant_version_tag"} add ["pacticipants", :pacticipant_name, "branches"], Api::Resources::PacticipantBranches, {resource_name: "pacticipant_branches"} add ["pacticipants", :pacticipant_name, "branches", :branch_name], Api::Resources::Branch, { resource_name: "branch" } + add ["pacticipants", :pacticipant_name, "branches", :branch_name, "latest-version"], Api::Resources::LatestVersion, { resource_name: "latest_pacticipant_version_for_branch" } add ["pacticipants", :pacticipant_name, "branches", :branch_name, "versions", :version_number], Api::Resources::BranchVersion, { resource_name: "branch_version" } add ["pacticipants", :pacticipant_name, "branches", :branch_name, "latest-version", "can-i-deploy", "to-environment", :environment_name], Api::Resources::CanIDeployPacticipantVersionByBranchToEnvironment, { resource_name: "can_i_deploy_latest_branch_version_to_environment" } add ["pacticipants", :pacticipant_name, "branches", :branch_name, "latest-version", "can-i-deploy", "to-environment", :environment_name, "badge"], Api::Resources::CanIDeployPacticipantVersionByBranchToEnvironmentBadge, { resource_name: "can_i_deploy_latest_branch_version_to_environment_badge" } diff --git a/lib/pact_broker/api/decorators/branch_decorator.rb b/lib/pact_broker/api/decorators/branch_decorator.rb index a53f4d2db..9f7fee210 100644 --- a/lib/pact_broker/api/decorators/branch_decorator.rb +++ b/lib/pact_broker/api/decorators/branch_decorator.rb @@ -18,7 +18,7 @@ class BranchDecorator < BaseDecorator link "pb:latest-version" do | user_options | { title: "Latest version for branch", - href: branch_versions_url(represented, user_options.fetch(:base_url)) + "?size=1" + href: latest_version_for_branch_url(represented, user_options.fetch(:base_url)) } end diff --git a/lib/pact_broker/api/decorators/version_decorator.rb b/lib/pact_broker/api/decorators/version_decorator.rb index 2c7585739..875d95884 100644 --- a/lib/pact_broker/api/decorators/version_decorator.rb +++ b/lib/pact_broker/api/decorators/version_decorator.rb @@ -21,7 +21,8 @@ class VersionDecorator < BaseDecorator { title: "Version", name: represented.number, - href: version_url(options.fetch(:base_url), represented) + # This decorator is used for multiple Version resources, so dynamically fetch the current resource URL + href: options.fetch(:resource_url) } end diff --git a/lib/pact_broker/api/decorators/versions_decorator.rb b/lib/pact_broker/api/decorators/versions_decorator.rb index 5637f7b50..3cb016e61 100644 --- a/lib/pact_broker/api/decorators/versions_decorator.rb +++ b/lib/pact_broker/api/decorators/versions_decorator.rb @@ -6,13 +6,23 @@ module PactBroker module Api module Decorators class VersionsDecorator < BaseDecorator + class VersionInCollectionDecorator < PactBroker::Api::Decorators::VersionDecorator + # VersionDecorator has a dynamic self URL, depending which path the Version resource is mounted at. + # Hardcode the URL of the embedded Versions in this collection to use the canonical URL with the version number. + link :self do | user_options | + { + title: "Version", + name: represented.number, + href: version_url(user_options.fetch(:base_url), represented) + } + end + end - collection :entries, as: :versions, embedded: true, :extend => PactBroker::Api::Decorators::VersionDecorator + collection :entries, as: :versions, embedded: true, :extend => VersionInCollectionDecorator link :self do | user_options | - href = append_query_if_present(user_options[:resource_url], user_options[:query_string]) { - href: href, + href: user_options.fetch(:request_url), title: user_options[:resource_title] || "All application versions of #{user_options[:pacticipant_name]}" } end diff --git a/lib/pact_broker/api/pact_broker_urls.rb b/lib/pact_broker/api/pact_broker_urls.rb index 3ce937d9d..81aa06755 100644 --- a/lib/pact_broker/api/pact_broker_urls.rb +++ b/lib/pact_broker/api/pact_broker_urls.rb @@ -243,6 +243,10 @@ def branch_version_url(branch_version, base_url = "") "#{branch_versions_url(branch_version.branch, base_url)}/#{url_encode(branch_version.version_number)}" end + def latest_version_for_branch_url(branch, base_url = "") + "#{branch_url(branch, base_url)}/latest-version" + end + def templated_tag_url_for_pacticipant pacticipant_name, base_url = "" pacticipant_url_from_params({ pacticipant_name: pacticipant_name }, base_url) + "/versions/{version}/tags/{tag}" end diff --git a/lib/pact_broker/api/resources/latest_version.rb b/lib/pact_broker/api/resources/latest_version.rb index bac4ab7a2..430410fd8 100644 --- a/lib/pact_broker/api/resources/latest_version.rb +++ b/lib/pact_broker/api/resources/latest_version.rb @@ -17,6 +17,8 @@ def allowed_methods def version if identifier_from_path[:tag] @version ||= version_service.find_by_pacticipant_name_and_latest_tag(identifier_from_path[:pacticipant_name], identifier_from_path[:tag]) + elsif identifier_from_path[:branch_name] + @version ||= version_service.find_latest_by_pacticipant_name_and_branch_name(identifier_from_path[:pacticipant_name], identifier_from_path[:branch_name]) else @version ||= version_service.find_latest_by_pacticpant_name(identifier_from_path) end diff --git a/spec/features/get_latest_version_for_branch_spec.rb b/spec/features/get_latest_version_for_branch_spec.rb new file mode 100644 index 000000000..64bdefd52 --- /dev/null +++ b/spec/features/get_latest_version_for_branch_spec.rb @@ -0,0 +1,18 @@ +describe "Get latest version for branch" do + before do + td.create_consumer("Foo") + .create_consumer_version("1", branch: "main") + .create_consumer_version("2", branch: "main") + .create_consumer_version("3", branch: "not-main") + end + let(:path) { PactBroker::Api::PactBrokerUrls.latest_version_for_branch_url(PactBroker::Versions::Branch.order(:id).first) } + let(:rack_env) { { "CONTENT_TYPE" => "application/json" } } + + subject { get(path, {}, rack_env) } + + it { is_expected.to be_a_hal_json_success_response } + + it "returns the latest version for the branch" do + expect(JSON.parse(subject.body)["number"]).to eq "2" + end +end diff --git a/spec/fixtures/approvals/pacticipant_branches_decorator.approved.json b/spec/fixtures/approvals/pacticipant_branches_decorator.approved.json index 6f719defe..4a3204790 100644 --- a/spec/fixtures/approvals/pacticipant_branches_decorator.approved.json +++ b/spec/fixtures/approvals/pacticipant_branches_decorator.approved.json @@ -11,7 +11,7 @@ }, "pb:latest-version": { "title": "Latest version for branch", - "href": "http://example.org/pacticipants/Foo/branches/main/versions?size=1" + "href": "http://example.org/pacticipants/Foo/branches/main/latest-version" } } } diff --git a/spec/lib/pact_broker/api/decorators/version_decorator_spec.rb b/spec/lib/pact_broker/api/decorators/version_decorator_spec.rb index 22c00e4d3..98b5297a9 100644 --- a/spec/lib/pact_broker/api/decorators/version_decorator_spec.rb +++ b/spec/lib/pact_broker/api/decorators/version_decorator_spec.rb @@ -48,13 +48,13 @@ module Decorators end let(:base_url) { "http://example.org" } - let(:options) { { user_options: { base_url: base_url, environments: environments } } } + let(:options) { { user_options: { base_url: base_url, resource_url: "resource_url", environments: environments } } } let(:decorator) { VersionDecorator.new(version) } subject { JSON.parse(decorator.to_json(options), symbolize_names: true) } it "includes a link to itself" do - expect(subject[:_links][:self][:href]).to eq "http://example.org/pacticipants/Consumer/versions/1.2.3" + expect(subject[:_links][:self][:href]).to eq "resource_url" end it "includes the version number in the link" do @@ -125,7 +125,7 @@ module Decorators end context "when the environments option is not present" do - let(:options) { { user_options: { base_url: base_url } } } + let(:options) { { user_options: { base_url: base_url, resource_url: "resource_url" } } } it "does not include the pb:record-deployment or pb:record-release" do expect(subject[:_links]).to_not have_key(:'pb:record-deployment') diff --git a/spec/lib/pact_broker/api/decorators/versions_decorator_spec.rb b/spec/lib/pact_broker/api/decorators/versions_decorator_spec.rb index e4cd7c58b..867b0f20a 100644 --- a/spec/lib/pact_broker/api/decorators/versions_decorator_spec.rb +++ b/spec/lib/pact_broker/api/decorators/versions_decorator_spec.rb @@ -5,21 +5,18 @@ module PactBroker module Api module Decorators describe VersionsDecorator do + before do + allow_any_instance_of(VersionsDecorator::VersionInCollectionDecorator).to receive(:version_url).and_return("version_url") + end - let(:options) { { resource_url: "http://versions", base_url: "http://example.org", pacticipant_name: "Consumer", query_string: query_string}} - let(:query_string) { nil } + let(:options) { { request_url: "http://versions?foo=bar", base_url: "http://example.org", pacticipant_name: "Consumer", resource_url: "http://versions" } } let(:versions) { [] } + let(:decorator) { VersionsDecorator.new(versions) } + let(:json) { decorator.to_json(user_options: options) } - subject { JSON.parse VersionsDecorator.new(versions).to_json(user_options: options), symbolize_names: true } - - context "with no query string" do - its([:_links, :self, :href]) { is_expected.to eq "http://versions" } - end + subject { JSON.parse(json, symbolize_names: true) } - context "with a query string" do - let(:query_string) { "foo=bar" } - its([:_links, :self, :href]) { is_expected.to eq "http://versions?foo=bar" } - end + its([:_links, :self, :href]) { is_expected.to eq "http://versions?foo=bar" } context "with no versions" do it "doesn't blow up" do @@ -41,6 +38,10 @@ module Decorators expect(subject[:_embedded][:versions]).to be_instance_of(Array) expect(subject[:_embedded][:versions].size).to eq 1 end + + it "has the version href with the version number" do + expect(subject[:_embedded][:versions].first[:_links][:self][:href]).to eq "version_url" + end end end end