v3: full request create implementation and specs
This commit is contained in:
parent
714f365e15
commit
7c6dc9a54c
|
@ -30,6 +30,7 @@ module Travis
|
|||
WrongCredentials = ClientError .create('access denied', status: 403)
|
||||
LoginRequired = ClientError .create('login required', status: 403)
|
||||
InsufficientAccess = ClientError .create(status: 403)
|
||||
PushAccessRequired = InsufficientAccess .create('push access required')
|
||||
WrongParams = ClientError .create('wrong parameters')
|
||||
ServerError = Error .create(status: 500)
|
||||
NotImplemented = ServerError .create('request not (yet) implemented', status: 501)
|
||||
|
|
|
@ -16,8 +16,6 @@ module Travis::API::V3
|
|||
full_access? or !!user
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def full_access?
|
||||
config.full_access
|
||||
end
|
||||
|
|
|
@ -11,6 +11,10 @@ module Travis::API::V3
|
|||
full_access? or dispatch(object)
|
||||
end
|
||||
|
||||
def writable?(object)
|
||||
full_access? or dispatch(object)
|
||||
end
|
||||
|
||||
def user
|
||||
end
|
||||
|
||||
|
@ -18,6 +22,10 @@ module Travis::API::V3
|
|||
false
|
||||
end
|
||||
|
||||
def full_access?
|
||||
false
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def build_visible?(build)
|
||||
|
@ -41,10 +49,6 @@ module Travis::API::V3
|
|||
false
|
||||
end
|
||||
|
||||
def full_access?
|
||||
false
|
||||
end
|
||||
|
||||
def public_api?
|
||||
!Travis.config.private_api
|
||||
end
|
||||
|
|
|
@ -17,6 +17,10 @@ module Travis::API::V3
|
|||
|
||||
protected
|
||||
|
||||
def repository_writable?(repository)
|
||||
permission?(:push, repository)
|
||||
end
|
||||
|
||||
def private_repository_visible?(repository)
|
||||
permission?(:pull, repository)
|
||||
end
|
||||
|
|
18
lib/travis/api/v3/queries/request.rb
Normal file
18
lib/travis/api/v3/queries/request.rb
Normal file
|
@ -0,0 +1,18 @@
|
|||
module Travis::API::V3
|
||||
class Queries::Request < Query
|
||||
params :message, :branch, :config, prefix: :request
|
||||
|
||||
def schedule(repository, user)
|
||||
raise ServerError, 'repository does not have a github_id'.freeze unless repository.github_id
|
||||
raise WrongParams, 'missing user'.freeze unless user and user.id
|
||||
|
||||
perform_async(:build_request, type: 'api'.freeze, credentials: {}, payload: {
|
||||
repository: { id: repository.github_id },
|
||||
user: { id: user.id },
|
||||
message: message,
|
||||
branch: branch || repository.default_branch_name,
|
||||
config: config || {}
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,14 +1,6 @@
|
|||
module Travis::API::V3
|
||||
class Queries::Requests < Query
|
||||
def schedule_for(repository)
|
||||
perform_async(:build_request, type: 'api'.freeze, payload: payload, credentials: {})
|
||||
end
|
||||
|
||||
def find(repository)
|
||||
end
|
||||
|
||||
def payload
|
||||
raise NotImplementedError
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
10
lib/travis/api/v3/queries/user.rb
Normal file
10
lib/travis/api/v3/queries/user.rb
Normal file
|
@ -0,0 +1,10 @@
|
|||
module Travis::API::V3
|
||||
class Queries::User < Query
|
||||
params :id
|
||||
|
||||
def find
|
||||
return Models::User.find_by_id(id) if id
|
||||
raise WrongParams, 'missing user.id'.freeze
|
||||
end
|
||||
end
|
||||
end
|
|
@ -10,12 +10,12 @@ module Travis::API::V3
|
|||
return @%<name>s if defined? @%<name>s
|
||||
return @%<name>s = @params['%<prefix>s.%<name>s'.freeze] if @params.include? '%<prefix>s.%<name>s'.freeze
|
||||
return @%<name>s = @params['%<prefix>s'.freeze]['%<name>s'.freeze] if @params.include? '%<prefix>s'.freeze and @params['%<prefix>s'.freeze].is_a? Hash
|
||||
return @%<name>s = @params['%<name>s'.freeze] if @params['@type'.freeze].nil? or @params['@type'.freeze] == '%<prefix>s'.freeze
|
||||
return @%<name>s = @params['%<name>s'.freeze] if (@params['@type'.freeze] || @main_type) == '%<prefix>s'.freeze
|
||||
@%<name>s = nil
|
||||
end
|
||||
|
||||
def %<name>s!
|
||||
%<name>s or raise WrongParams, 'missing %<prefix>s.%<name>s'.freeze, missing_field: '%<prefix>s.%<name>s'.freeze
|
||||
%<name>s or raise WrongParams, 'missing %<prefix>s.%<name>s'.freeze
|
||||
end
|
||||
RUBY
|
||||
|
||||
|
@ -24,13 +24,14 @@ module Travis::API::V3
|
|||
list.each { |e| class_eval(@@params_accessor % { name: e, prefix: prefix }) }
|
||||
end
|
||||
|
||||
attr_reader :params
|
||||
attr_reader :params, :main_type
|
||||
|
||||
def initialize(params)
|
||||
def initialize(params, main_type)
|
||||
@params = params
|
||||
@main_type = main_type.to_s
|
||||
end
|
||||
|
||||
def perform_async(worker, *args)
|
||||
def perform_async(identifier, *args)
|
||||
class_name, queue, client = @@sidekiq_cache[identifier] ||= [
|
||||
"Travis::Sidekiq::#{identifier.to_s.camelcase}".freeze,
|
||||
identifier.to_s.pluralize.freeze
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
module Travis::API::V3
|
||||
module Renderer
|
||||
PRIMITIVE = [String, Symbol, Numeric, true, false, nil]
|
||||
private_constant :PRIMITIVE
|
||||
|
||||
EXPANDER_CACHE = Tool::ThreadLocal.new
|
||||
private_constant :EXPANDER_CACHE
|
||||
|
||||
|
@ -31,6 +34,21 @@ module Travis::API::V3
|
|||
expander.call(args)
|
||||
end
|
||||
|
||||
def render_model(model, type: model.class.name[/[^:]+$/].to_sym, mode: :minimal, **options)
|
||||
Renderer[type].render(model, mode, **options)
|
||||
end
|
||||
|
||||
def render_value(value, **options)
|
||||
case value
|
||||
when Hash then value.map { |k, v| [k, render_value(v)] }.to_h
|
||||
when Array then value.map { |v | render_value(v) }
|
||||
when *PRIMITIVE then value
|
||||
when Time then value.strftime('%Y-%m-%dT%H:%M:%SZ')
|
||||
when Model then render_model(value, **options)
|
||||
else raise ArgumentError, 'cannot render %p (%p)' % [value.class, value]
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def generate_expander(route, key_mapping)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
module Travis::API::V3
|
||||
module Renderer::Error
|
||||
module Renderer::Accepted
|
||||
extend self
|
||||
|
||||
def render(type, **)
|
||||
|
|
|
@ -7,7 +7,7 @@ module Travis::API::V3
|
|||
:@type => 'error'.freeze,
|
||||
:error_type => error.type,
|
||||
:error_message => error.message,
|
||||
**error.payload
|
||||
**Renderer.render_value(error.payload)
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
module Travis::API::V3
|
||||
class Renderer::ModelRenderer
|
||||
PRIMITIVE = [String, Symbol, Numeric, true, false, nil]
|
||||
private_constant :PRIMITIVE
|
||||
|
||||
def self.type(type = nil)
|
||||
@type = type if type
|
||||
@type = name[/[^:]+$/].underscore.to_sym unless defined? @type # allows setting type to nil
|
||||
|
@ -43,23 +40,8 @@ module Travis::API::V3
|
|||
result[:@href] = href if href
|
||||
fields = self.class.representations.fetch(representation)
|
||||
|
||||
fields.each { |field| result[field] = render_value(send(field)) }
|
||||
fields.each { |field| result[field] = Renderer.render_value(send(field), script_name: script_name) }
|
||||
result
|
||||
end
|
||||
|
||||
def render_model(model, type: model.class.name[/[^:]+$/].to_sym, mode: :minimal, **options)
|
||||
Renderer[type].render(model, mode, script_name: script_name, **options)
|
||||
end
|
||||
|
||||
def render_value(value)
|
||||
case value
|
||||
when Hash then value.map { |k, v| [k, render_value(v)] }.to_h
|
||||
when Array then value.map { |v | render_value(v) }
|
||||
when *PRIMITIVE then value
|
||||
when Time then value.strftime('%Y-%m-%dT%H:%M:%SZ')
|
||||
when Model then render_model(value)
|
||||
else raise ArgumentError, 'cannot render %p (%p)' % [value.class, value]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -16,7 +16,7 @@ module Travis::API::V3
|
|||
end
|
||||
|
||||
def query(type = self.class.result_type)
|
||||
@queries[type] ||= Queries[type].new(params)
|
||||
@queries[type] ||= Queries[type].new(params, self.class.result_type)
|
||||
end
|
||||
|
||||
def find(type = self.class.result_type, *args)
|
||||
|
@ -41,6 +41,12 @@ module Travis::API::V3
|
|||
result
|
||||
end
|
||||
|
||||
def params_for?(prefix)
|
||||
return true if params['@type'.freeze] == prefix
|
||||
return true if params[prefix].is_a? Hash
|
||||
params.keys.any? { |key| key.start_with? "#{prefix}." }
|
||||
end
|
||||
|
||||
def accepted(type = self.class.result_type)
|
||||
Result.new(:accepted, type, status: 202)
|
||||
end
|
||||
|
|
|
@ -1,9 +1,17 @@
|
|||
module Travis::API::V3
|
||||
class Services::Requests::Create < Service
|
||||
result_type :request
|
||||
|
||||
def run
|
||||
not_implemented
|
||||
query.schedule_for(find(:repository))
|
||||
accepted
|
||||
raise LoginRequired unless access_control.logged_in? or access_control.full_access?
|
||||
raise NotFound unless repository = find(:repository)
|
||||
raise PushAccessRequired, repository: repository unless access_control.writable?(repository)
|
||||
|
||||
user = find(:user) if access_control.full_access? and params_for? 'user'.freeze
|
||||
user ||= access_control.user
|
||||
|
||||
query.schedule(repository, user)
|
||||
accepted(:request)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -88,7 +88,7 @@ describe Travis::API::V3::Services::Repository::Find do
|
|||
before { Permission.create(repository: repo, user: repo.owner, pull: true) }
|
||||
before { repo.update_attribute(:private, true) }
|
||||
before { get("/v3/repo/#{repo.id}", {}, headers) }
|
||||
before { repo.update_attribute(:private, false) }
|
||||
after { repo.update_attribute(:private, false) }
|
||||
example { expect(last_response).to be_ok }
|
||||
example { expect(JSON.load(body)).to be == {
|
||||
"@type" => "repository",
|
||||
|
|
255
spec/v3/services/requests/create_spec.rb
Normal file
255
spec/v3/services/requests/create_spec.rb
Normal file
|
@ -0,0 +1,255 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Travis::API::V3::Services::Requests::Create do
|
||||
let(:repo) { Travis::API::V3::Models::Repository.where(owner_name: 'svenfuchs', name: 'minimal').first }
|
||||
let(:sidekiq_payload) { Sidekiq::Client.last['args'].last[:payload] }
|
||||
|
||||
before do
|
||||
@original_sidekiq = Sidekiq::Client
|
||||
Sidekiq.send(:remove_const, :Client) # to avoid a warning
|
||||
Sidekiq::Client = []
|
||||
end
|
||||
|
||||
after do
|
||||
Sidekiq.send(:remove_const, :Client) # to avoid a warning
|
||||
Sidekiq::Client = @original_sidekiq
|
||||
end
|
||||
|
||||
describe "not authenticated" do
|
||||
before { post("/v3/repo/#{repo.id}/requests") }
|
||||
example { expect(last_response.status).to be == 403 }
|
||||
example { expect(JSON.load(body)).to be == {
|
||||
"@type" => "error",
|
||||
"error_type" => "login_required",
|
||||
"error_message" => "login required"
|
||||
}}
|
||||
end
|
||||
|
||||
describe "missing repository, authenticated" do
|
||||
let(:token) { Travis::Api::App::AccessToken.create(user: repo.owner, app_id: 1) }
|
||||
let(:headers) {{ 'HTTP_AUTHORIZATION' => "token #{token}" }}
|
||||
before { post("/v3/repo/9999999999/requests", {}, headers) }
|
||||
|
||||
example { expect(last_response.status).to be == 404 }
|
||||
example { expect(JSON.load(body)).to be == {
|
||||
"@type" => "error",
|
||||
"error_type" => "not_found",
|
||||
"error_message" => "repository not found (or insufficient access)",
|
||||
"resource_type" => "repository"
|
||||
}}
|
||||
end
|
||||
|
||||
describe "existing repository, no push access" do
|
||||
let(:token) { Travis::Api::App::AccessToken.create(user: repo.owner, app_id: 1) }
|
||||
let(:headers) {{ 'HTTP_AUTHORIZATION' => "token #{token}" }}
|
||||
before { post("/v3/repo/#{repo.id}/requests", {}, headers) }
|
||||
|
||||
example { expect(last_response.status).to be == 403 }
|
||||
example { expect(JSON.load(body)).to be == {
|
||||
"@type" => "error",
|
||||
"error_type" => "push_access_required",
|
||||
"error_message" => "push access required",
|
||||
"repository" => {
|
||||
"@type" => "repository",
|
||||
"@href" => "/repo/#{repo.id}",
|
||||
"id" => repo.id,
|
||||
"slug" => "svenfuchs/minimal"}
|
||||
}}
|
||||
end
|
||||
|
||||
describe "private repository, no access" do
|
||||
let(:token) { Travis::Api::App::AccessToken.create(user: repo.owner, app_id: 1) }
|
||||
let(:headers) {{ 'HTTP_AUTHORIZATION' => "token #{token}" }}
|
||||
before { repo.update_attribute(:private, true) }
|
||||
before { post("/v3/repo/#{repo.id}/requests", {}, headers) }
|
||||
after { repo.update_attribute(:private, false) }
|
||||
|
||||
example { expect(last_response.status).to be == 404 }
|
||||
example { expect(JSON.load(body)).to be == {
|
||||
"@type" => "error",
|
||||
"error_type" => "not_found",
|
||||
"error_message" => "repository not found (or insufficient access)",
|
||||
"resource_type" => "repository"
|
||||
}}
|
||||
end
|
||||
|
||||
describe "existing repository, push access" do
|
||||
let(:params) {{}}
|
||||
let(:token) { Travis::Api::App::AccessToken.create(user: repo.owner, app_id: 1) }
|
||||
let(:headers) {{ 'HTTP_AUTHORIZATION' => "token #{token}" }}
|
||||
before { Travis::API::V3::Models::Permission.create(repository: repo, user: repo.owner, push: true) }
|
||||
before { post("/v3/repo/#{repo.id}/requests", params, headers) }
|
||||
|
||||
example { expect(last_response.status).to be == 202 }
|
||||
example { expect(JSON.load(body)).to be == {
|
||||
"@type" => "pending",
|
||||
"resource_type" => "request"
|
||||
}}
|
||||
|
||||
example { expect(sidekiq_payload).to be == {
|
||||
repository: { id: repo.id },
|
||||
user: { id: repo.owner.id },
|
||||
message: nil,
|
||||
branch: 'master',
|
||||
config: {}
|
||||
}}
|
||||
|
||||
example { expect(Sidekiq::Client.last['queue']).to be == 'build_requests' }
|
||||
example { expect(Sidekiq::Client.last['class']).to be == 'Travis::Sidekiq::BuildRequest' }
|
||||
|
||||
describe "setting id has no effect" do
|
||||
let(:params) {{ id: 42 }}
|
||||
example { expect(sidekiq_payload).to be == {
|
||||
repository: { id: repo.id },
|
||||
user: { id: repo.owner.id },
|
||||
message: nil,
|
||||
branch: 'master',
|
||||
config: {}
|
||||
}}
|
||||
end
|
||||
|
||||
describe "setting repository has no effect" do
|
||||
let(:params) {{ repository: { id: 42 } }}
|
||||
example { expect(sidekiq_payload).to be == {
|
||||
repository: { id: repo.id },
|
||||
user: { id: repo.owner.id },
|
||||
message: nil,
|
||||
branch: 'master',
|
||||
config: {}
|
||||
}}
|
||||
end
|
||||
|
||||
describe "setting user has no effect" do
|
||||
let(:params) {{ user: { id: 42 } }}
|
||||
example { expect(sidekiq_payload).to be == {
|
||||
repository: { id: repo.id },
|
||||
user: { id: repo.owner.id },
|
||||
message: nil,
|
||||
branch: 'master',
|
||||
config: {}
|
||||
}}
|
||||
end
|
||||
|
||||
describe "overriding config" do
|
||||
let(:params) {{ config: { script: 'true' } }}
|
||||
example { expect(sidekiq_payload).to be == {
|
||||
repository: { id: repo.id },
|
||||
user: { id: repo.owner.id },
|
||||
message: nil,
|
||||
branch: 'master',
|
||||
config: { 'script' => 'true' }
|
||||
}}
|
||||
end
|
||||
|
||||
describe "overriding message" do
|
||||
let(:params) {{ message: 'example' }}
|
||||
example { expect(sidekiq_payload).to be == {
|
||||
repository: { id: repo.id },
|
||||
user: { id: repo.owner.id },
|
||||
message: 'example',
|
||||
branch: 'master',
|
||||
config: {}
|
||||
}}
|
||||
end
|
||||
|
||||
describe "overriding branch" do
|
||||
let(:params) {{ branch: 'example' }}
|
||||
example { expect(sidekiq_payload).to be == {
|
||||
repository: { id: repo.id },
|
||||
user: { id: repo.owner.id },
|
||||
message: nil,
|
||||
branch: 'example',
|
||||
config: {}
|
||||
}}
|
||||
end
|
||||
|
||||
describe "overriding branch (in request)" do
|
||||
let(:params) {{ request: { branch: 'example' } }}
|
||||
example { expect(sidekiq_payload).to be == {
|
||||
repository: { id: repo.id },
|
||||
user: { id: repo.owner.id },
|
||||
message: nil,
|
||||
branch: 'example',
|
||||
config: {}
|
||||
}}
|
||||
end
|
||||
|
||||
describe "overriding branch (with request prefix)" do
|
||||
let(:params) {{ "request.branch" => 'example' }}
|
||||
example { expect(sidekiq_payload).to be == {
|
||||
repository: { id: repo.id },
|
||||
user: { id: repo.owner.id },
|
||||
message: nil,
|
||||
branch: 'example',
|
||||
config: {}
|
||||
}}
|
||||
end
|
||||
|
||||
describe "overriding branch (with request type)" do
|
||||
let(:params) {{ "@type" => "request", "branch" => 'example' }}
|
||||
example { expect(sidekiq_payload).to be == {
|
||||
repository: { id: repo.id },
|
||||
user: { id: repo.owner.id },
|
||||
message: nil,
|
||||
branch: 'example',
|
||||
config: {}
|
||||
}}
|
||||
end
|
||||
|
||||
describe "overriding branch (with wrong type)" do
|
||||
let(:params) {{ "@type" => "repository", "branch" => 'example' }}
|
||||
example { expect(sidekiq_payload).to be == {
|
||||
repository: { id: repo.id },
|
||||
user: { id: repo.owner.id },
|
||||
message: nil,
|
||||
branch: 'master',
|
||||
config: {}
|
||||
}}
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
describe "existing repository, application with full access" do
|
||||
let(:app_name) { 'travis-example' }
|
||||
let(:app_secret) { '12345678' }
|
||||
let(:sign_opts) { "a=#{app_name}" }
|
||||
let(:signature) { OpenSSL::HMAC.hexdigest('sha256', app_secret, sign_opts) }
|
||||
let(:headers) {{ 'HTTP_AUTHORIZATION' => "signature #{sign_opts}:#{signature}" }}
|
||||
before { Travis.config.applications = { app_name => { full_access: true, secret: app_secret }}}
|
||||
before { post("/v3/repo/#{repo.id}/requests", params, headers) }
|
||||
|
||||
describe 'without setting user' do
|
||||
let(:params) {{}}
|
||||
example { expect(last_response.status).to be == 400 }
|
||||
example { expect(JSON.load(body)).to be == {
|
||||
"@type" => "error",
|
||||
"error_type" => "wrong_params",
|
||||
"error_message" => "missing user"
|
||||
}}
|
||||
end
|
||||
|
||||
describe 'setting user' do
|
||||
let(:params) {{ user: { id: repo.owner.id } }}
|
||||
example { expect(last_response.status).to be == 202 }
|
||||
example { expect(sidekiq_payload).to be == {
|
||||
repository: { id: repo.id },
|
||||
user: { id: repo.owner.id },
|
||||
message: nil,
|
||||
branch: 'master',
|
||||
config: {}
|
||||
}}
|
||||
end
|
||||
|
||||
describe 'setting branch' do
|
||||
let(:params) {{ user: { id: repo.owner.id }, branch: 'example' }}
|
||||
example { expect(last_response.status).to be == 202 }
|
||||
example { expect(sidekiq_payload).to be == {
|
||||
repository: { id: repo.id },
|
||||
user: { id: repo.owner.id },
|
||||
message: nil,
|
||||
branch: 'example',
|
||||
config: {}
|
||||
}}
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue
Block a user