Skip to content

Commit

Permalink
feat: add endpoint to list branches for a pacticipant (#638)
Browse files Browse the repository at this point in the history
PACT-1358
  • Loading branch information
bethesque authored Oct 3, 2023
1 parent 8e1afe8 commit ff7e3a5
Show file tree
Hide file tree
Showing 23 changed files with 330 additions and 11 deletions.
1 change: 1 addition & 0 deletions lib/pact_broker/api.rb
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ def self.build_api(application_context = PactBroker::ApplicationContext.default_
add ["pacticipants", :pacticipant_name, "latest-version", :tag, "can-i-deploy", "to", :to, "badge"], Api::Resources::CanIDeployPacticipantVersionByTagToTagBadge, { resource_name: "can_i_deploy_latest_tagged_version_to_tag_badge" }
add ["pacticipants", :pacticipant_name, "latest-version"], Api::Resources::LatestVersion, {resource_name: "latest_pacticipant_version"}
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, "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" }
Expand Down
6 changes: 6 additions & 0 deletions lib/pact_broker/api/decorators/branch_decorator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ class BranchDecorator < BaseDecorator
end

include Timestamps

# When this decorator is embedded in the PacticipantBranchesDecorator,
# we need to eager load the pacticipants for generating the URL
def self.eager_load_associations
super + [:pacticipant]
end
end
end
end
Expand Down
3 changes: 2 additions & 1 deletion lib/pact_broker/api/decorators/decorator_context.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@ module PactBroker
module Api
module Decorators
class DecoratorContext < Hash
attr_reader :base_url, :resource_url, :resource_title, :env, :query_string
attr_reader :base_url, :resource_url, :resource_title, :env, :query_string, :request_url

def initialize base_url, resource_url, env, options = {}
@base_url = self[:base_url] = base_url
@resource_url = self[:resource_url] = resource_url
@resource_title = self[:resource_title] = options[:resource_title]
@env = self[:env] = env
@query_string = self[:query_string] = (env["QUERY_STRING"] && !env["QUERY_STRING"].empty? ? env["QUERY_STRING"] : nil)
@request_url = self[:request_url] = query_string ? resource_url + "?" + query_string : resource_url
merge!(options)
end

Expand Down
32 changes: 32 additions & 0 deletions lib/pact_broker/api/decorators/pacticipant_branches_decorator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
require "pact_broker/api/decorators/base_decorator"
require "pact_broker/api/decorators/timestamps"
require "pact_broker/api/decorators/pagination_links"
require "pact_broker/api/decorators/branch_decorator"

module PactBroker
module Api
module Decorators
class PacticipantBranchesDecorator < BaseDecorator
collection :entries, as: :branches, embedded: true, :extend => PactBroker::Api::Decorators::BranchDecorator

link :self do | user_options |
{
title: "#{user_options.fetch(:pacticipant).name} branches",
href: user_options.fetch(:request_url)
}
end

links "pb:branches" do | user_options |
represented.collect do | branch |
{
name: branch.name,
href: branch_url(branch, user_options.fetch(:base_url))
}
end
end

include PaginationLinks
end
end
end
end
4 changes: 4 additions & 0 deletions lib/pact_broker/api/decorators/pacticipant_decorator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ def self.eager_load_associations
versions_url(options[:base_url], represented)
end

link :'pb:branches' do | options |
pacticipant_branches_url(represented, options[:base_url])
end

link :'pb:version' do | options |
{
title: "Get, create or delete a pacticipant version",
Expand Down
6 changes: 5 additions & 1 deletion lib/pact_broker/api/pact_broker_urls.rb
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,11 @@ def tag_url base_url, tag
end

def branch_url(branch, base_url = "")
"#{pacticipant_url(base_url, branch.pacticipant)}/branches/#{url_encode(branch.name)}"
"#{pacticipant_branches_url(branch.pacticipant, base_url)}/#{url_encode(branch.name)}"
end

def pacticipant_branches_url(pacticipant, base_url = "")
"#{pacticipant_url(base_url, pacticipant)}/branches"
end

def branch_versions_url(branch, base_url = "")
Expand Down
49 changes: 49 additions & 0 deletions lib/pact_broker/api/resources/pacticipant_branches.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
require "pact_broker/api/resources/base_resource"
require "pact_broker/api/resources/pagination_methods"
require "pact_broker/api/resources/filter_methods"

module PactBroker
module Api
module Resources
class PacticipantBranches < BaseResource
include PaginationMethods
include FilterMethods

def content_types_provided
[["application/hal+json", :to_json]]
end

def allowed_methods
["GET", "OPTIONS"]
end

def resource_exists?
!!pacticipant
end

def to_json
decorator_class(:pacticipant_branches_decorator).new(branches).to_json(**decorator_options(pacticipant: pacticipant))
end

def policy_name
:'versions::branches'
end

private

def branches
@branches ||= branch_service.find_all_branches_for_pacticipant(
pacticipant,
filter_options,
default_pagination_options.merge(pagination_options),
eager_load_associations
)
end

def eager_load_associations
decorator_class(:pacticipant_branches_decorator).eager_load_associations
end
end
end
end
end
4 changes: 4 additions & 0 deletions lib/pact_broker/api/resources/pagination_methods.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ def pagination_options
{}
end
end

def default_pagination_options
{ page_number: 1, page_size: 100 }
end
end
end
end
Expand Down
6 changes: 5 additions & 1 deletion lib/pact_broker/test/test_data_builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -614,6 +614,10 @@ def create_pacticipant_version(version_number, pacticipant, params = {})
tag = PactBroker::Domain::Tag.create(name: tag_name, version: version)
set_created_at_if_set(params[:created_at], :tags, { name: tag.name, version_id: version.id })
end
if params[:branch]
set_created_at_if_set params[:created_at], :branches, { name: params[:branch], pacticipant_id: pacticipant.id }
set_created_at_if_set params[:created_at], :branch_versions, { branch_name: params[:branch], pacticipant_id: pacticipant.id, version_id: version.id }
end
version
end

Expand Down Expand Up @@ -650,7 +654,7 @@ def set_created_at_if_set created_at, table_name, selector, date_column_name = :
if date_to_set
Sequel::Model.db[table_name].where(selector).update(date_column_name => date_to_set)
if Sequel::Model.db.schema(table_name).any?{ |col| col.first == :updated_at }
Sequel::Model.db[table_name].where(selector.keys.first => selector.values.first).update(updated_at: date_to_set)
Sequel::Model.db[table_name].where(selector).update(updated_at: date_to_set)
end
end
end
Expand Down
15 changes: 15 additions & 0 deletions lib/pact_broker/versions/branch_repository.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,22 @@
require "pact_broker/repositories/scopes"
module PactBroker
module Versions
class BranchRepository
include PactBroker::Services
include PactBroker::Repositories::Scopes

# @param [PactBroker::Domain::Pacticipant] pacticipant
# @param [Hash] filter_options with key :query_string
# @param [Hash] pagination_options with keys :page_size and :page_number
# @param [Array] eager_load_associations the associations to eager load
def find_all_branches_for_pacticipant(pacticipant, filter_options = {}, pagination_options = {}, eager_load_associations = [])
query = scope_for(Branch).where(pacticipant_id: pacticipant.id).select_all_qualified
query = query.filter(:name, filter_options[:query_string]) if filter_options[:query_string]
query
.order(Sequel.desc(:created_at), Sequel.desc(:id))
.eager(*eager_load_associations)
.all_with_pagination_options(pagination_options)
end

# @param [String] pacticipant_name
# @param [String] branch_name
Expand Down
2 changes: 1 addition & 1 deletion lib/pact_broker/versions/branch_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ class BranchService
class << self
extend Forwardable
delegate [:find_branch_version, :find_or_create_branch_version, :delete_branch_version] => :branch_version_repository
delegate [:find_branch, :delete_branch] => :branch_repository
delegate [:find_branch, :delete_branch, :find_all_branches_for_pacticipant] => :branch_repository
end
end
end
Expand Down
46 changes: 46 additions & 0 deletions spec/features/get_pacticipant_branches_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
describe "Get pacticipant branches" do
before do
td.create_consumer("Foo")
.create_consumer_version("1", branch: "main")
.create_consumer_version("2", branch: "feat/bar")
.create_consumer_version("3", branch: "feat/foo")
end
let(:path) { PactBroker::Api::PactBrokerUrls.pacticipant_branches_url(td.and_return(:pacticipant)) }
let(:headers) { { "CONTENT_TYPE" => "application/json" } }
let(:response_body_hash) { JSON.parse(subject.body, symbolize_names: true) }
let(:params) { nil }

subject { get(path, params, headers) }

it { is_expected.to be_a_hal_json_success_response }

it "returns a list of branches" do
expect(response_body_hash[:_embedded][:branches].size).to eq 3
end

it_behaves_like "a page"

context "with pagination options" do
subject { get(path, { "pageSize" => "2", "pageNumber" => "1" }) }

it "only returns the number of items specified in the pageSize" do
expect(response_body_hash[:_links][:"pb:branches"].size).to eq 2
end

it_behaves_like "a paginated response"
end

context "with filter options" do
let(:params) { { "q" => "feat" } }

it "returns a list of branches matching the filter" do
expect(response_body_hash[:_embedded][:branches].size).to eq 2
end
end

context "when the pacticipant does not exist" do
let(:path) { PactBroker::Api::PactBrokerUrls.pacticipant_branches_url(OpenStruct.new(name: "Bar")) }

its(:status) { is_expected.to eq 404 }
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@
"pb:versions": {
"href": "https://pact-broker/pacticipants/foo/versions"
},
"pb:branches": {
"href": "https://pact-broker/pacticipants/foo/branches"
},
"pb:version": {
"title": "Get, create or delete a pacticipant version",
"href": "https://pact-broker/pacticipants/foo/versions/{version}",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@
"pb:versions": {
"href": "https://pact-broker/pacticipants/foo/versions"
},
"pb:branches": {
"href": "https://pact-broker/pacticipants/foo/branches"
},
"pb:version": {
"title": "Get, create or delete a pacticipant version",
"href": "https://pact-broker/pacticipants/foo/versions/{version}",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@
"pb:versions": {
"href": "https://pact-broker/pacticipants/foo/versions"
},
"pb:branches": {
"href": "https://pact-broker/pacticipants/foo/branches"
},
"pb:version": {
"title": "Get, create or delete a pacticipant version",
"href": "https://pact-broker/pacticipants/foo/versions/{version}",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"_embedded": {
"branches": [
{
"name": "main",
"createdAt": "2020-01-01T00:00:00+00:00",
"_links": {
"self": {
"title": "Branch",
"href": "http://example.org/pacticipants/Foo/branches/main"
},
"pb:latest-version": {
"title": "Latest version for branch",
"href": "http://example.org/pacticipants/Foo/branches/main/versions?pageSize=1"
}
}
}
]
},
"_links": {
"self": {
"title": "Foo branches",
"href": "http://example.org/pacticipants/Foo/branches"
},
"pb:branches": [
{
"name": "main",
"href": "http://example.org/pacticipants/Foo/branches/main"
}
]
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
require "pact_broker/api/decorators/pacticipant_branches_decorator"

module PactBroker
module Api
module Decorators
describe PacticipantBranchesDecorator do
it "ensures the pacticipant is eager loaded for the branches collection" do
expect(PacticipantBranchesDecorator.eager_load_associations).to include :pacticipant
end

describe "to_json" do
let(:branch_1) { instance_double("PactBroker::Versions::Branch", name: "main", pacticipant: pacticipant_1, created_at: td.in_utc { DateTime.new(2020, 1, 1) } ) }
let(:pacticipant_1) { instance_double("PactBroker::Domain::Pacticipant", name: "Foo") }
let(:branches) { [branch_1] }
let(:options) do
{
user_options: {
pacticipant: pacticipant_1,
base_url: "http://example.org",
request_url: "http://example.org/pacticipants/Foo/branches"
}
}
end
let(:decorator) { PacticipantBranchesDecorator.new(branches) }

subject { JSON.parse(decorator.to_json(options)) }

it "generates json" do
Approvals.verify(subject, :name => "pacticipant_branches_decorator", format: :json)
end
end
end
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ module Decorators
pacticipant.updated_at = updated_at
allow_any_instance_of(PacticipantDecorator).to receive(:templated_tag_url_for_pacticipant).and_return("version_tag_url")
allow_any_instance_of(PacticipantDecorator).to receive(:templated_version_url_for_pacticipant).and_return("version_url")
allow_any_instance_of(PacticipantDecorator).to receive(:pacticipant_branches_url).and_return("pacticipant_branches_url")
end

subject { JSON.parse PacticipantDecorator.new(pacticipant).to_json(user_options: { base_url: base_url }), symbolize_names: true }
Expand All @@ -67,6 +68,11 @@ module Decorators
expect(subject[:_links][:'pb:version'][:href]).to eq "version_url"
end

it "includes a relation for the branches" do
expect_any_instance_of(PacticipantDecorator).to receive(:pacticipant_branches_url).with(pacticipant, base_url)
expect(subject[:_links][:'pb:branches'][:href]).to eq "pacticipant_branches_url"
end

context "when there is a latest_version" do
before { td.create_version("1.2.107") }

Expand Down
2 changes: 1 addition & 1 deletion spec/lib/pact_broker/api/resources/all_routes_spec.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# The purpose of this spec is to ensure that every new resource either has a policy_record, or it does not need a policy_record
# (because the all the context can be implied from the route).
# (because the all the context can be implied from the route, which will most likely contain a :pacticipant, or a :consumer, and/or a :provider).
# This test will fail when a new resource is added that does not either have a policy_record which returns an object,
# or has not been explicitly ignored in the spec/support/all_routes_spec_support.yml file.

Expand Down
Loading

0 comments on commit ff7e3a5

Please sign in to comment.