diff --git a/lib/travis/api/v3/access_control/generic.rb b/lib/travis/api/v3/access_control/generic.rb index 0344b35b..92b68e1e 100644 --- a/lib/travis/api/v3/access_control/generic.rb +++ b/lib/travis/api/v3/access_control/generic.rb @@ -92,6 +92,10 @@ module Travis::API::V3 private_repository_visible?(repository) end + def settings_visible?(settings) + repository_visible?(settings.repository) + end + def private_repository_visible?(repository) false end diff --git a/lib/travis/api/v3/models/settings.rb b/lib/travis/api/v3/models/settings.rb new file mode 100644 index 00000000..626ea097 --- /dev/null +++ b/lib/travis/api/v3/models/settings.rb @@ -0,0 +1,29 @@ +module Travis::API::V3 + class Models::Settings + attr_reader :repository + + def initialize(repository) + @repository = repository + end + + def to_h + defaults.merge(repository.settings || {}) + end + + def update(settings = {}) + settings = defaults.merge(settings) + repository.update_attributes(settings: JSON.generate(settings)) + end + + private + + def defaults + { + 'builds_only_with_travis_yml' => false, + 'build_pushes' => true, + 'build_pull_requests' => true, + 'maximum_number_of_builds' => 0 + } + end + end +end diff --git a/lib/travis/api/v3/queries/settings.rb b/lib/travis/api/v3/queries/settings.rb new file mode 100644 index 00000000..ce5ea94e --- /dev/null +++ b/lib/travis/api/v3/queries/settings.rb @@ -0,0 +1,15 @@ +module Travis::API::V3 + class Queries::Settings < Query + params :builds_only_with_travis_yml, :build_pushes, :build_pull_requests, :maximum_number_of_builds, prefix: :settings + + def find(repository) + Models::Settings.new(repository) + end + + def update(repository) + settings = find(repository) + settings.update(settings_params) + settings + end + end +end diff --git a/lib/travis/api/v3/query.rb b/lib/travis/api/v3/query.rb index a9812e0c..af4e5006 100644 --- a/lib/travis/api/v3/query.rb +++ b/lib/travis/api/v3/query.rb @@ -32,6 +32,15 @@ module Travis::API::V3 end RUBY + @@prefixed_params_accessor = <<-RUBY + def %s_params + @%s ||= begin + params = @params.select { |key, _| key.start_with?('%s.'.freeze) } + Hash[params.map { |key, value| [key.split('.'.freeze).last, value] }] + end + end + RUBY + def self.type name[/[^:]+$/].underscore end @@ -48,6 +57,7 @@ module Travis::API::V3 check_type: check_type }) end + class_eval(@@prefixed_params_accessor % { prefix: prefix }) end def self.prefix(input) diff --git a/lib/travis/api/v3/renderer/settings.rb b/lib/travis/api/v3/renderer/settings.rb new file mode 100644 index 00000000..87ad2b8c --- /dev/null +++ b/lib/travis/api/v3/renderer/settings.rb @@ -0,0 +1,18 @@ +module Travis::API::V3 + module Renderer::Settings + extend self + + AVAILABLE_ATTRIBUTES = [:settings] + + def available_attributes + AVAILABLE_ATTRIBUTES + end + + def render(settings, **) + { + :@type => 'settings'.freeze, + :settings => settings.to_h + } + end + end +end diff --git a/lib/travis/api/v3/routes.rb b/lib/travis/api/v3/routes.rb index d22edce9..585dc1d8 100644 --- a/lib/travis/api/v3/routes.rb +++ b/lib/travis/api/v3/routes.rb @@ -117,6 +117,12 @@ module Travis::API::V3 get :find post :create end + + resource :settings do + route '/settings' + get :find + patch :update + end end resource :user do diff --git a/lib/travis/api/v3/routes/dsl.rb b/lib/travis/api/v3/routes/dsl.rb index 9a98ada0..a0210ce2 100644 --- a/lib/travis/api/v3/routes/dsl.rb +++ b/lib/travis/api/v3/routes/dsl.rb @@ -60,6 +60,10 @@ module Travis::API::V3 current_resource.add_service('POST'.freeze, *args) end + def patch(*args) + current_resource.add_service('PATCH'.freeze, *args) + end + def delete(*args) current_resource.add_service('DELETE'.freeze, *args) end diff --git a/lib/travis/api/v3/services.rb b/lib/travis/api/v3/services.rb index 637ea337..32b30b7c 100644 --- a/lib/travis/api/v3/services.rb +++ b/lib/travis/api/v3/services.rb @@ -20,6 +20,7 @@ module Travis::API::V3 Repositories = Module.new { extend Services } Repository = Module.new { extend Services } Requests = Module.new { extend Services } + Settings = Module.new { extend Services } User = Module.new { extend Services } def result_type diff --git a/lib/travis/api/v3/services/repository/settings.rb b/lib/travis/api/v3/services/repository/settings.rb new file mode 100644 index 00000000..e69de29b diff --git a/lib/travis/api/v3/services/settings/find.rb b/lib/travis/api/v3/services/settings/find.rb new file mode 100644 index 00000000..9ab54fec --- /dev/null +++ b/lib/travis/api/v3/services/settings/find.rb @@ -0,0 +1,9 @@ +module Travis::API::V3 + class Services::Settings::Find < Service + def run! + raise LoginRequired unless access_control.logged_in? or access_control.full_access? + raise NotFound unless repo = find(:repository) + find(:settings, repo) + end + end +end diff --git a/lib/travis/api/v3/services/settings/update.rb b/lib/travis/api/v3/services/settings/update.rb new file mode 100644 index 00000000..c2780e20 --- /dev/null +++ b/lib/travis/api/v3/services/settings/update.rb @@ -0,0 +1,11 @@ +module Travis::API::V3 + class Services::Settings::Update < Service + params :builds_only_with_travis_yml, :build_pushes, :build_pull_requests, :maximum_number_of_builds, prefix: :settings + + def run! + raise LoginRequired unless access_control.logged_in? or access_control.full_access? + raise NotFound unless repository = find(:repository) + query.update(repository) + end + end +end diff --git a/spec/v3/services/settings_spec.rb b/spec/v3/services/settings_spec.rb new file mode 100644 index 00000000..d5a3bd09 --- /dev/null +++ b/spec/v3/services/settings_spec.rb @@ -0,0 +1,127 @@ +require 'spec_helper' + +describe Travis::API::V3::Services::Settings do + let(:repo) { Travis::API::V3::Models::Repository.where(owner_name: 'svenfuchs', name: 'minimal').first_or_create } + let(:token) { Travis::Api::App::AccessToken.create(user: repo.owner, app_id: 1) } + let(:auth_headers) { { 'HTTP_AUTHORIZATION' => "token #{token}" } } + let(:json_headers) { { 'CONTENT_TYPE' => 'application/json' } } + + describe :Find do + describe 'not authenticated' do + before { get("/v3/repo/#{repo.id}/settings") } + example { expect(last_response.status).to eq(403) } + example do + expect(JSON.load(body)).to eq( + '@type' => 'error', + 'error_type' => 'login_required', + 'error_message' => 'login required' + ) + end + end + + describe 'authenticated, missing repo' do + before { get('/v3/repo/9999999999/settings', {}, auth_headers) } + + example { expect(last_response.status).to eq(404) } + example do + expect(JSON.load(body)).to eq( + '@type' => 'error', + 'error_type' => 'not_found', + 'error_message' => 'repository not found (or insufficient access)', + 'resource_type' => 'repository' + ) + end + end + + describe 'authenticated, existing repo, repo has no settings' do + before { get("/v3/repo/#{repo.id}/settings", {}, auth_headers) } + + example { expect(last_response.status).to eq(200) } + example do + expect(JSON.load(body)).to eq( + '@type' => 'settings', + 'settings' => { + 'builds_only_with_travis_yml' => false, + 'build_pushes' => true, + 'build_pull_requests' => true, + 'maximum_number_of_builds' => 0 + } + ) + end + end + + describe 'authenticated, existing repo, repo has some settings' do + before do + repo.update_attributes(settings: JSON.dump('build_pushes' => false)) + get("/v3/repo/#{repo.id}/settings", {}, auth_headers) + end + + example { expect(last_response.status).to eq(200) } + example do + expect(JSON.load(body)).to eq( + '@type' => 'settings', + 'settings' => { + 'builds_only_with_travis_yml' => false, + 'build_pushes' => false, + 'build_pull_requests' => true, + 'maximum_number_of_builds' => 0 + } + ) + end + end + end + + describe :Update do + describe 'not authenticated' do + before do + patch("/v3/repo/#{repo.id}/settings", JSON.dump(build_pushes: false), json_headers) + end + + example { expect(last_response.status).to eq(403) } + example do + expect(JSON.load(body)).to eq( + '@type' => 'error', + 'error_type' => 'login_required', + 'error_message' => 'login required' + ) + end + end + + describe 'authenticated, missing repo' do + before do + patch('/v3/repo/9999999999/settings', JSON.dump(build_pushes: false), json_headers.merge(auth_headers)) + end + + example { expect(last_response.status).to eq(404) } + example do + expect(JSON.load(body)).to eq( + '@type' => 'error', + 'error_type' => 'not_found', + 'error_message' => 'repository not found (or insufficient access)', + 'resource_type' => 'repository' + ) + end + end + + describe 'authenticated, existing repo' do + let(:params) { JSON.dump('settings.build_pushes' => false) } + + before do + patch("/v3/repo/#{repo.id}/settings", params, json_headers.merge(auth_headers)) + end + + example { expect(last_response.status).to eq(200) } + example do + expect(JSON.load(body)).to eq( + '@type' => 'settings', + 'settings' => { + 'builds_only_with_travis_yml' => false, + 'build_pushes' => false, + 'build_pull_requests' => true, + 'maximum_number_of_builds' => 0 + } + ) + end + end + end +end