summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG1
-rw-r--r--doc/api/projects.md25
-rw-r--r--lib/api/projects.rb43
-rw-r--r--spec/requests/api/projects_spec.rb131
4 files changed, 200 insertions, 0 deletions
diff --git a/CHANGELOG b/CHANGELOG
index f1346885ab4..8468c9a7aef 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -181,6 +181,7 @@ v 7.4.0
- Fail harder in the backup script
- Changes to Slack service structure, only webhook url needed
- Zen mode for wiki and milestones (Robert Schilling)
+ - API: Add support for editing an existing project (Mika Mäenpää)
- Move Emoji parsing to html-pipeline-gitlab (Robert Schilling)
- Font Awesome 4.2 integration (Sullivan Senechal)
- Add Pushover service integration (Sullivan Senechal)
diff --git a/doc/api/projects.md b/doc/api/projects.md
index 027a8ec2e7f..d7804689c25 100644
--- a/doc/api/projects.md
+++ b/doc/api/projects.md
@@ -287,6 +287,31 @@ Parameters:
- `visibility_level` (optional)
- `import_url` (optional)
+### Edit project
+
+Updates an existing project
+
+```
+PUT /projects/:id
+```
+
+Parameters:
+
+- `id` (required) - The ID of a project
+- `name` (optional) - project name
+- `path` (optional) - repository name for project
+- `description` (optional) - short project description
+- `default_branch` (optional)
+- `issues_enabled` (optional)
+- `merge_requests_enabled` (optional)
+- `wiki_enabled` (optional)
+- `snippets_enabled` (optional)
+- `public` (optional) - if `true` same as setting visibility_level = 20
+- `visibility_level` (optional)
+
+On success, method returns 200 with the updated project. If parameters are
+invalid, 400 is returned.
+
### Fork project
Forks a project into the user namespace of the authenticated user.
diff --git a/lib/api/projects.rb b/lib/api/projects.rb
index 5b0c31f1898..d96288bb982 100644
--- a/lib/api/projects.rb
+++ b/lib/api/projects.rb
@@ -200,6 +200,49 @@ module API
end
end
+ # Update an existing project
+ #
+ # Parameters:
+ # id (required) - the id of a project
+ # name (optional) - name of a project
+ # path (optional) - path of a project
+ # description (optional) - short project description
+ # issues_enabled (optional)
+ # merge_requests_enabled (optional)
+ # wiki_enabled (optional)
+ # snippets_enabled (optional)
+ # public (optional) - if true same as setting visibility_level = 20
+ # visibility_level (optional) - visibility level of a project
+ # Example Request
+ # PUT /projects/:id
+ put ':id' do
+ attrs = attributes_for_keys [:name,
+ :path,
+ :description,
+ :default_branch,
+ :issues_enabled,
+ :merge_requests_enabled,
+ :wiki_enabled,
+ :snippets_enabled,
+ :public,
+ :visibility_level]
+ attrs = map_public_to_visibility_level(attrs)
+ authorize_admin_project
+ authorize! :rename_project, user_project if attrs[:name].present?
+ if attrs[:visibility_level].present?
+ authorize! :change_visibility_level, user_project
+ end
+
+ ::Projects::UpdateService.new(user_project,
+ current_user, attrs).execute
+
+ if user_project.valid?
+ present user_project, with: Entities::Project
+ else
+ render_validation_error!(user_project)
+ end
+ end
+
# Remove project
#
# Parameters:
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index 3098b0f77f9..26d1a8d193e 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -1,3 +1,4 @@
+# -*- coding: utf-8 -*-
require 'spec_helper'
describe API::API, api: true do
@@ -12,6 +13,24 @@ describe API::API, api: true do
let(:snippet) { create(:project_snippet, author: user, project: project, title: 'example') }
let(:project_member) { create(:project_member, user: user, project: project, access_level: ProjectMember::MASTER) }
let(:project_member2) { create(:project_member, user: user3, project: project, access_level: ProjectMember::DEVELOPER) }
+ let(:user4) { create(:user) }
+ let(:project3) { create(:project,
+ name: 'second_project',
+ path: 'second_project',
+ creator_id: user.id,
+ namespace: user.namespace,
+ merge_requests_enabled: false,
+ issues_enabled: false, wiki_enabled: false,
+ snippets_enabled: false, visibility_level: 0) }
+ let(:project_member3) { create(:project_member,
+ user: user4,
+ project: project3,
+ access_level: ProjectMember::MASTER) }
+ let(:project4) { create(:project,
+ name: 'third_project',
+ path: 'third_project',
+ creator_id: user4.id,
+ namespace: user4.namespace) }
describe "GET /projects" do
before { project }
@@ -650,6 +669,118 @@ describe API::API, api: true do
end
end
+ describe 'PUT /projects/:id̈́' do
+ before { project }
+ before { user }
+ before { user3 }
+ before { user4 }
+ before { project3 }
+ before { project4 }
+ before { project_member3 }
+ before { project_member2 }
+
+ context 'when unauthenticated' do
+ it 'should return authentication error' do
+ project_param = { name: 'bar' }
+ put api("/projects/#{project.id}"), project_param
+ response.status.should == 401
+ end
+ end
+
+ context 'when authenticated as project owner' do
+ it 'should update name' do
+ project_param = { name: 'bar' }
+ put api("/projects/#{project.id}", user), project_param
+ response.status.should == 200
+ project_param.each_pair do |k, v|
+ json_response[k.to_s].should == v
+ end
+ end
+
+ it 'should update visibility_level' do
+ project_param = { visibility_level: 20 }
+ put api("/projects/#{project3.id}", user), project_param
+ response.status.should == 200
+ project_param.each_pair do |k, v|
+ json_response[k.to_s].should == v
+ end
+ end
+
+ it 'should not update name to existing name' do
+ project_param = { name: project3.name }
+ put api("/projects/#{project.id}", user), project_param
+ response.status.should == 400
+ json_response['message']['name'].should == ['has already been taken']
+ end
+
+ it 'should update path & name to existing path & name in different namespace' do
+ project_param = { path: project4.path, name: project4.name }
+ put api("/projects/#{project3.id}", user), project_param
+ response.status.should == 200
+ project_param.each_pair do |k, v|
+ json_response[k.to_s].should == v
+ end
+ end
+ end
+
+ context 'when authenticated as project master' do
+ it 'should update path' do
+ project_param = { path: 'bar' }
+ put api("/projects/#{project3.id}", user4), project_param
+ response.status.should == 200
+ project_param.each_pair do |k, v|
+ json_response[k.to_s].should == v
+ end
+ end
+
+ it 'should update other attributes' do
+ project_param = { issues_enabled: true,
+ wiki_enabled: true,
+ snippets_enabled: true,
+ merge_requests_enabled: true,
+ description: 'new description' }
+
+ put api("/projects/#{project3.id}", user4), project_param
+ response.status.should == 200
+ project_param.each_pair do |k, v|
+ json_response[k.to_s].should == v
+ end
+ end
+
+ it 'should not update path to existing path' do
+ project_param = { path: project.path }
+ put api("/projects/#{project3.id}", user4), project_param
+ response.status.should == 400
+ json_response['message']['path'].should == ['has already been taken']
+ end
+
+ it 'should not update name' do
+ project_param = { name: 'bar' }
+ put api("/projects/#{project3.id}", user4), project_param
+ response.status.should == 403
+ end
+
+ it 'should not update visibility_level' do
+ project_param = { visibility_level: 20 }
+ put api("/projects/#{project3.id}", user4), project_param
+ response.status.should == 403
+ end
+ end
+
+ context 'when authenticated as project developer' do
+ it 'should not update other attributes' do
+ project_param = { path: 'bar',
+ issues_enabled: true,
+ wiki_enabled: true,
+ snippets_enabled: true,
+ merge_requests_enabled: true,
+ description: 'new description' }
+ put api("/projects/#{project.id}", user3), project_param
+ response.status.should == 403
+ end
+ end
+ end
+
describe "DELETE /projects/:id" do
context "when authenticated as user" do
it "should remove project" do