diff --git a/changelogs/unreleased/14492-change-fork-endpoint.yml b/changelogs/unreleased/14492-change-fork-endpoint.yml new file mode 100644 index 00000000000..39024b51b54 --- /dev/null +++ b/changelogs/unreleased/14492-change-fork-endpoint.yml @@ -0,0 +1,4 @@ +--- +title: Move /projects/fork/:id to /projects/:id/fork +merge_request: 8940 +author: diff --git a/doc/api/projects.md b/doc/api/projects.md index bad238f57d7..e579b89d836 100644 --- a/doc/api/projects.md +++ b/doc/api/projects.md @@ -705,7 +705,7 @@ Parameters: Forks a project into the user namespace of the authenticated user or the one provided. ``` -POST /projects/fork/:id +POST /projects/:id/fork ``` Parameters: diff --git a/doc/api/v3_to_v4.md b/doc/api/v3_to_v4.md index 0ae07b5d3de..7811025f34e 100644 --- a/doc/api/v3_to_v4.md +++ b/doc/api/v3_to_v4.md @@ -22,4 +22,5 @@ changes are in V4: - `/gitignores/:key` - `/gitlab_ci_ymls/:key` - `/dockerfiles/:key` +- Moved `/projects/fork/:id` to `/projects/:id/fork` diff --git a/lib/api/projects.rb b/lib/api/projects.rb index bd4b23195ac..2cacb246db8 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -220,7 +220,7 @@ module API params do optional :namespace, type: String, desc: 'The ID or name of the namespace that the project will be forked into' end - post 'fork/:id' do + post ':id/fork' do fork_params = declared_params(include_missing: false) namespace_id = fork_params[:namespace] diff --git a/spec/requests/api/fork_spec.rb b/spec/requests/api/fork_spec.rb deleted file mode 100644 index 92ac4fd334d..00000000000 --- a/spec/requests/api/fork_spec.rb +++ /dev/null @@ -1,134 +0,0 @@ -require 'spec_helper' - -describe API::Projects, api: true do - include ApiHelpers - let(:user) { create(:user) } - let(:user2) { create(:user) } - let(:admin) { create(:admin) } - let(:group) { create(:group) } - let(:group2) do - group = create(:group, name: 'group2_name') - group.add_owner(user2) - group - end - - describe 'POST /projects/fork/:id' do - let(:project) do - create(:project, :repository, creator: user, namespace: user.namespace) - end - - before do - project.add_reporter(user2) - end - - context 'when authenticated' do - it 'forks if user has sufficient access to project' do - post api("/projects/fork/#{project.id}", user2) - - expect(response).to have_http_status(201) - expect(json_response['name']).to eq(project.name) - expect(json_response['path']).to eq(project.path) - expect(json_response['owner']['id']).to eq(user2.id) - expect(json_response['namespace']['id']).to eq(user2.namespace.id) - expect(json_response['forked_from_project']['id']).to eq(project.id) - end - - it 'forks if user is admin' do - post api("/projects/fork/#{project.id}", admin) - - expect(response).to have_http_status(201) - expect(json_response['name']).to eq(project.name) - expect(json_response['path']).to eq(project.path) - expect(json_response['owner']['id']).to eq(admin.id) - expect(json_response['namespace']['id']).to eq(admin.namespace.id) - expect(json_response['forked_from_project']['id']).to eq(project.id) - end - - it 'fails on missing project access for the project to fork' do - new_user = create(:user) - post api("/projects/fork/#{project.id}", new_user) - - expect(response).to have_http_status(404) - expect(json_response['message']).to eq('404 Project Not Found') - end - - it 'fails if forked project exists in the user namespace' do - post api("/projects/fork/#{project.id}", user) - - expect(response).to have_http_status(409) - expect(json_response['message']['name']).to eq(['has already been taken']) - expect(json_response['message']['path']).to eq(['has already been taken']) - end - - it 'fails if project to fork from does not exist' do - post api('/projects/fork/424242', user) - - expect(response).to have_http_status(404) - expect(json_response['message']).to eq('404 Project Not Found') - end - - it 'forks with explicit own user namespace id' do - post api("/projects/fork/#{project.id}", user2), namespace: user2.namespace.id - - expect(response).to have_http_status(201) - expect(json_response['owner']['id']).to eq(user2.id) - end - - it 'forks with explicit own user name as namespace' do - post api("/projects/fork/#{project.id}", user2), namespace: user2.username - - expect(response).to have_http_status(201) - expect(json_response['owner']['id']).to eq(user2.id) - end - - it 'forks to another user when admin' do - post api("/projects/fork/#{project.id}", admin), namespace: user2.username - - expect(response).to have_http_status(201) - expect(json_response['owner']['id']).to eq(user2.id) - end - - it 'fails if trying to fork to another user when not admin' do - post api("/projects/fork/#{project.id}", user2), namespace: admin.namespace.id - - expect(response).to have_http_status(404) - end - - it 'fails if trying to fork to non-existent namespace' do - post api("/projects/fork/#{project.id}", user2), namespace: 42424242 - - expect(response).to have_http_status(404) - expect(json_response['message']).to eq('404 Target Namespace Not Found') - end - - it 'forks to owned group' do - post api("/projects/fork/#{project.id}", user2), namespace: group2.name - - expect(response).to have_http_status(201) - expect(json_response['namespace']['name']).to eq(group2.name) - end - - it 'fails to fork to not owned group' do - post api("/projects/fork/#{project.id}", user2), namespace: group.name - - expect(response).to have_http_status(404) - end - - it 'forks to not owned group when admin' do - post api("/projects/fork/#{project.id}", admin), namespace: group.name - - expect(response).to have_http_status(201) - expect(json_response['namespace']['name']).to eq(group.name) - end - end - - context 'when unauthenticated' do - it 'returns authentication error' do - post api("/projects/fork/#{project.id}") - - expect(response).to have_http_status(401) - expect(json_response['message']).to eq('401 Unauthorized') - end - end - end -end diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index ac0bbec44e0..17b5e372bdc 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -1332,4 +1332,130 @@ describe API::Projects, api: true do end end end + + describe 'POST /projects/:id/fork' do + let(:project) do + create(:project, :repository, creator: user, namespace: user.namespace) + end + let(:group) { create(:group) } + let(:group2) do + group = create(:group, name: 'group2_name') + group.add_owner(user2) + group + end + + before do + project.add_reporter(user2) + end + + context 'when authenticated' do + it 'forks if user has sufficient access to project' do + post api("/projects/#{project.id}/fork", user2) + + expect(response).to have_http_status(201) + expect(json_response['name']).to eq(project.name) + expect(json_response['path']).to eq(project.path) + expect(json_response['owner']['id']).to eq(user2.id) + expect(json_response['namespace']['id']).to eq(user2.namespace.id) + expect(json_response['forked_from_project']['id']).to eq(project.id) + end + + it 'forks if user is admin' do + post api("/projects/#{project.id}/fork", admin) + + expect(response).to have_http_status(201) + expect(json_response['name']).to eq(project.name) + expect(json_response['path']).to eq(project.path) + expect(json_response['owner']['id']).to eq(admin.id) + expect(json_response['namespace']['id']).to eq(admin.namespace.id) + expect(json_response['forked_from_project']['id']).to eq(project.id) + end + + it 'fails on missing project access for the project to fork' do + new_user = create(:user) + post api("/projects/#{project.id}/fork", new_user) + + expect(response).to have_http_status(404) + expect(json_response['message']).to eq('404 Project Not Found') + end + + it 'fails if forked project exists in the user namespace' do + post api("/projects/#{project.id}/fork", user) + + expect(response).to have_http_status(409) + expect(json_response['message']['name']).to eq(['has already been taken']) + expect(json_response['message']['path']).to eq(['has already been taken']) + end + + it 'fails if project to fork from does not exist' do + post api('/projects/424242/fork', user) + + expect(response).to have_http_status(404) + expect(json_response['message']).to eq('404 Project Not Found') + end + + it 'forks with explicit own user namespace id' do + post api("/projects/#{project.id}/fork", user2), namespace: user2.namespace.id + + expect(response).to have_http_status(201) + expect(json_response['owner']['id']).to eq(user2.id) + end + + it 'forks with explicit own user name as namespace' do + post api("/projects/#{project.id}/fork", user2), namespace: user2.username + + expect(response).to have_http_status(201) + expect(json_response['owner']['id']).to eq(user2.id) + end + + it 'forks to another user when admin' do + post api("/projects/#{project.id}/fork", admin), namespace: user2.username + + expect(response).to have_http_status(201) + expect(json_response['owner']['id']).to eq(user2.id) + end + + it 'fails if trying to fork to another user when not admin' do + post api("/projects/#{project.id}/fork", user2), namespace: admin.namespace.id + + expect(response).to have_http_status(404) + end + + it 'fails if trying to fork to non-existent namespace' do + post api("/projects/#{project.id}/fork", user2), namespace: 42424242 + + expect(response).to have_http_status(404) + expect(json_response['message']).to eq('404 Target Namespace Not Found') + end + + it 'forks to owned group' do + post api("/projects/#{project.id}/fork", user2), namespace: group2.name + + expect(response).to have_http_status(201) + expect(json_response['namespace']['name']).to eq(group2.name) + end + + it 'fails to fork to not owned group' do + post api("/projects/#{project.id}/fork", user2), namespace: group.name + + expect(response).to have_http_status(404) + end + + it 'forks to not owned group when admin' do + post api("/projects/#{project.id}/fork", admin), namespace: group.name + + expect(response).to have_http_status(201) + expect(json_response['namespace']['name']).to eq(group.name) + end + end + + context 'when unauthenticated' do + it 'returns authentication error' do + post api("/projects/#{project.id}/fork") + + expect(response).to have_http_status(401) + expect(json_response['message']).to eq('401 Unauthorized') + end + end + end end