diff options
Diffstat (limited to 'spec/requests/api')
32 files changed, 1362 insertions, 721 deletions
diff --git a/spec/requests/api/branches_spec.rb b/spec/requests/api/branches_spec.rb index c64499fc8c0..5a2e1b2cf2d 100644 --- a/spec/requests/api/branches_spec.rb +++ b/spec/requests/api/branches_spec.rb @@ -1,25 +1,31 @@ require 'spec_helper' -require 'mime/types' describe API::Branches do let(:user) { create(:user) } - let!(:project) { create(:project, :repository, creator: user) } - let!(:master) { create(:project_member, :master, user: user, project: project) } - let(:guest) { create(:user).tap { |u| create(:project_member, :guest, user: u, project: project) } } - let!(:branch_name) { 'feature' } - let!(:branch_sha) { '0b4bc9a49b562e85de7cc9e834518ea6828729b9' } - let(:branch_with_dot) { CreateBranchService.new(project, user).execute("with.1.2.3", "master")[:branch] } + let(:guest) { create(:user).tap { |u| project.add_guest(u) } } + let(:project) { create(:project, :repository, creator: user, path: 'my.project') } + let(:branch_name) { 'feature' } + let(:branch_sha) { '0b4bc9a49b562e85de7cc9e834518ea6828729b9' } + let(:branch_with_dot) { project.repository.find_branch('ends-with.json') } + let(:branch_with_slash) { project.repository.find_branch('improve/awesome') } + + let(:project_id) { project.id } + let(:current_user) { nil } + + before do + project.add_master(user) + end describe "GET /projects/:id/repository/branches" do - let(:route) { "/projects/#{project.id}/repository/branches" } + let(:route) { "/projects/#{project_id}/repository/branches" } shared_examples_for 'repository branches' do it 'returns the repository branches' do get api(route, current_user), per_page: 100 - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) + expect(response).to match_response_schema('public_api/v4/branches') expect(response).to include_pagination_headers - expect(json_response).to be_an Array branch_names = json_response.map { |x| x['name'] } expect(branch_names).to match_array(project.repository.branch_names) end @@ -34,10 +40,9 @@ describe API::Branches do end context 'when unauthenticated', 'and project is public' do - it_behaves_like 'repository branches' do - let(:project) { create(:project, :public, :repository) } - let(:current_user) { nil } - end + let(:project) { create(:project, :public, :repository) } + + it_behaves_like 'repository branches' end context 'when unauthenticated', 'and project is private' do @@ -47,9 +52,15 @@ describe API::Branches do end end - context 'when authenticated', 'as a developer' do - it_behaves_like 'repository branches' do - let(:current_user) { user } + context 'when authenticated', 'as a master' do + let(:current_user) { user } + + it_behaves_like 'repository branches' + + context 'requesting with the escaped project full path' do + let(:project_id) { CGI.escape(project.full_path) } + + it_behaves_like 'repository branches' end end @@ -61,31 +72,15 @@ describe API::Branches do end describe "GET /projects/:id/repository/branches/:branch" do - let(:route) { "/projects/#{project.id}/repository/branches/#{branch_name}" } + let(:route) { "/projects/#{project_id}/repository/branches/#{branch_name}" } - shared_examples_for 'repository branch' do |merged: false| + shared_examples_for 'repository branch' do it 'returns the repository branch' do get api(route, current_user) - expect(response).to have_http_status(200) - expect(json_response['name']).to eq(branch_name) - expect(json_response['merged']).to eq(merged) - expect(json_response['protected']).to eq(false) - expect(json_response['developers_can_push']).to eq(false) - expect(json_response['developers_can_merge']).to eq(false) - - json_commit = json_response['commit'] - expect(json_commit['id']).to eq(branch_sha) - expect(json_commit).to have_key('short_id') - expect(json_commit).to have_key('title') - expect(json_commit).to have_key('message') - expect(json_commit).to have_key('author_name') - expect(json_commit).to have_key('author_email') - expect(json_commit).to have_key('authored_date') - expect(json_commit).to have_key('committer_name') - expect(json_commit).to have_key('committer_email') - expect(json_commit).to have_key('committed_date') - expect(json_commit).to have_key('parent_ids') + expect(response).to have_gitlab_http_status(200) + expect(response).to match_response_schema('public_api/v4/branch') + expect(json_response['name']).to eq(CGI.unescape(branch_name)) end context 'when branch does not exist' do @@ -107,10 +102,9 @@ describe API::Branches do end context 'when unauthenticated', 'and project is public' do - it_behaves_like 'repository branch' do - let(:project) { create(:project, :public, :repository) } - let(:current_user) { nil } - end + let(:project) { create(:project, :public, :repository) } + + it_behaves_like 'repository branch' end context 'when unauthenticated', 'and project is private' do @@ -120,22 +114,41 @@ describe API::Branches do end end - context 'when authenticated', 'as a developer' do + context 'when authenticated', 'as a master' do let(:current_user) { user } + it_behaves_like 'repository branch' context 'when branch contains a dot' do let(:branch_name) { branch_with_dot.name } - let(:branch_sha) { project.commit('master').sha } it_behaves_like 'repository branch' end - context 'when branch is merged' do - let(:branch_name) { 'merge-test' } - let(:branch_sha) { project.commit('merge-test').sha } + context 'when branch contains a slash' do + let(:branch_name) { branch_with_slash.name } + + it_behaves_like '404 response' do + let(:request) { get api(route, current_user) } + end + end + + context 'when branch contains an escaped slash' do + let(:branch_name) { CGI.escape(branch_with_slash.name) } + + it_behaves_like 'repository branch' + end + + context 'requesting with the escaped project full path' do + let(:project_id) { CGI.escape(project.full_path) } + + it_behaves_like 'repository branch' - it_behaves_like 'repository branch', merged: true + context 'when branch contains a dot' do + let(:branch_name) { branch_with_dot.name } + + it_behaves_like 'repository branch' + end end end @@ -147,268 +160,348 @@ describe API::Branches do end describe 'PUT /projects/:id/repository/branches/:branch/protect' do - context "when a protected branch doesn't already exist" do - it 'protects a single branch' do - put api("/projects/#{project.id}/repository/branches/#{branch_name}/protect", user) + let(:route) { "/projects/#{project_id}/repository/branches/#{branch_name}/protect" } - expect(response).to have_http_status(200) - expect(json_response['name']).to eq(branch_name) - expect(json_response['commit']['id']).to eq(branch_sha) - expect(json_response['protected']).to eq(true) - expect(json_response['developers_can_push']).to eq(false) - expect(json_response['developers_can_merge']).to eq(false) - end - - it "protects a single branch with dots in the name" do - put api("/projects/#{project.id}/repository/branches/#{branch_with_dot.name}/protect", user) + shared_examples_for 'repository new protected branch' do + it 'protects a single branch' do + put api(route, current_user) - expect(response).to have_http_status(200) - expect(json_response['name']).to eq(branch_with_dot.name) + expect(response).to have_gitlab_http_status(200) + expect(response).to match_response_schema('public_api/v4/branch') + expect(json_response['name']).to eq(CGI.unescape(branch_name)) expect(json_response['protected']).to eq(true) end it 'protects a single branch and developers can push' do - put api("/projects/#{project.id}/repository/branches/#{branch_name}/protect", user), - developers_can_push: true + put api(route, current_user), developers_can_push: true - expect(response).to have_http_status(200) - expect(json_response['name']).to eq(branch_name) - expect(json_response['commit']['id']).to eq(branch_sha) + expect(response).to have_gitlab_http_status(200) + expect(response).to match_response_schema('public_api/v4/branch') + expect(json_response['name']).to eq(CGI.unescape(branch_name)) expect(json_response['protected']).to eq(true) expect(json_response['developers_can_push']).to eq(true) expect(json_response['developers_can_merge']).to eq(false) end it 'protects a single branch and developers can merge' do - put api("/projects/#{project.id}/repository/branches/#{branch_name}/protect", user), - developers_can_merge: true + put api(route, current_user), developers_can_merge: true - expect(response).to have_http_status(200) - expect(json_response['name']).to eq(branch_name) - expect(json_response['commit']['id']).to eq(branch_sha) + expect(response).to have_gitlab_http_status(200) + expect(response).to match_response_schema('public_api/v4/branch') + expect(json_response['name']).to eq(CGI.unescape(branch_name)) expect(json_response['protected']).to eq(true) expect(json_response['developers_can_push']).to eq(false) expect(json_response['developers_can_merge']).to eq(true) end it 'protects a single branch and developers can push and merge' do - put api("/projects/#{project.id}/repository/branches/#{branch_name}/protect", user), - developers_can_push: true, developers_can_merge: true + put api(route, current_user), developers_can_push: true, developers_can_merge: true - expect(response).to have_http_status(200) - expect(json_response['name']).to eq(branch_name) - expect(json_response['commit']['id']).to eq(branch_sha) + expect(response).to have_gitlab_http_status(200) + expect(response).to match_response_schema('public_api/v4/branch') + expect(json_response['name']).to eq(CGI.unescape(branch_name)) expect(json_response['protected']).to eq(true) expect(json_response['developers_can_push']).to eq(true) expect(json_response['developers_can_merge']).to eq(true) end + + context 'when branch does not exist' do + let(:branch_name) { 'unknown' } + + it_behaves_like '404 response' do + let(:request) { put api(route, current_user) } + let(:message) { '404 Branch Not Found' } + end + end + + context 'when repository is disabled' do + include_context 'disabled repository' + + it_behaves_like '403 response' do + let(:request) { put api(route, current_user) } + end + end end - context 'for an existing protected branch' do - before do - project.repository.add_branch(user, protected_branch.name, 'master') + context 'when unauthenticated', 'and project is private' do + it_behaves_like '404 response' do + let(:request) { put api(route) } + let(:message) { '404 Project Not Found' } end + end + + context 'when authenticated', 'as a guest' do + it_behaves_like '403 response' do + let(:request) { put api(route, guest) } + end + end + + context 'when authenticated', 'as a master' do + let(:current_user) { user } - context "when developers can push and merge" do - let(:protected_branch) { create(:protected_branch, :developers_can_push, :developers_can_merge, project: project, name: 'protected_branch') } + context "when a protected branch doesn't already exist" do + it_behaves_like 'repository new protected branch' - it 'updates that a developer cannot push or merge' do - put api("/projects/#{project.id}/repository/branches/#{protected_branch.name}/protect", user), - developers_can_push: false, developers_can_merge: false + context 'when branch contains a dot' do + let(:branch_name) { branch_with_dot.name } - expect(response).to have_http_status(200) - expect(json_response['name']).to eq(protected_branch.name) - expect(json_response['protected']).to eq(true) - expect(json_response['developers_can_push']).to eq(false) - expect(json_response['developers_can_merge']).to eq(false) + it_behaves_like 'repository new protected branch' end - it "doesn't result in 0 access levels when 'developers_can_push' is switched off" do - put api("/projects/#{project.id}/repository/branches/#{protected_branch.name}/protect", user), - developers_can_push: false + context 'when branch contains a slash' do + let(:branch_name) { branch_with_slash.name } - expect(response).to have_http_status(200) - expect(json_response['name']).to eq(protected_branch.name) - expect(protected_branch.reload.push_access_levels.first).to be_present - expect(protected_branch.reload.push_access_levels.first.access_level).to eq(Gitlab::Access::MASTER) + it_behaves_like '404 response' do + let(:request) { put api(route, current_user) } + end end - it "doesn't result in 0 access levels when 'developers_can_merge' is switched off" do - put api("/projects/#{project.id}/repository/branches/#{protected_branch.name}/protect", user), - developers_can_merge: false + context 'when branch contains an escaped slash' do + let(:branch_name) { CGI.escape(branch_with_slash.name) } - expect(response).to have_http_status(200) - expect(json_response['name']).to eq(protected_branch.name) - expect(protected_branch.reload.merge_access_levels.first).to be_present - expect(protected_branch.reload.merge_access_levels.first.access_level).to eq(Gitlab::Access::MASTER) + it_behaves_like 'repository new protected branch' + end + + context 'requesting with the escaped project full path' do + let(:project_id) { CGI.escape(project.full_path) } + + it_behaves_like 'repository new protected branch' + + context 'when branch contains a dot' do + let(:branch_name) { branch_with_dot.name } + + it_behaves_like 'repository new protected branch' + end end end - context "when developers cannot push or merge" do - let(:protected_branch) { create(:protected_branch, project: project, name: 'protected_branch') } + context 'when protected branch already exists' do + before do + project.repository.add_branch(user, protected_branch.name, 'master') + end + + context 'when developers can push and merge' do + let(:protected_branch) { create(:protected_branch, :developers_can_push, :developers_can_merge, project: project, name: 'protected_branch') } + + it 'updates that a developer cannot push or merge' do + put api("/projects/#{project.id}/repository/branches/#{protected_branch.name}/protect", user), + developers_can_push: false, developers_can_merge: false + + expect(response).to have_gitlab_http_status(200) + expect(response).to match_response_schema('public_api/v4/branch') + expect(json_response['name']).to eq(protected_branch.name) + expect(json_response['protected']).to eq(true) + expect(json_response['developers_can_push']).to eq(false) + expect(json_response['developers_can_merge']).to eq(false) + expect(protected_branch.reload.push_access_levels.first.access_level).to eq(Gitlab::Access::MASTER) + expect(protected_branch.reload.merge_access_levels.first.access_level).to eq(Gitlab::Access::MASTER) + end + end + + context 'when developers cannot push or merge' do + let(:protected_branch) { create(:protected_branch, project: project, name: 'protected_branch') } - it 'updates that a developer can push and merge' do - put api("/projects/#{project.id}/repository/branches/#{protected_branch.name}/protect", user), - developers_can_push: true, developers_can_merge: true + it 'updates that a developer can push and merge' do + put api("/projects/#{project.id}/repository/branches/#{protected_branch.name}/protect", user), + developers_can_push: true, developers_can_merge: true - expect(response).to have_http_status(200) - expect(json_response['name']).to eq(protected_branch.name) - expect(json_response['protected']).to eq(true) - expect(json_response['developers_can_push']).to eq(true) - expect(json_response['developers_can_merge']).to eq(true) + expect(response).to have_gitlab_http_status(200) + expect(response).to match_response_schema('public_api/v4/branch') + expect(json_response['name']).to eq(protected_branch.name) + expect(json_response['protected']).to eq(true) + expect(json_response['developers_can_push']).to eq(true) + expect(json_response['developers_can_merge']).to eq(true) + end end end end + end - context "multiple API calls" do - it "returns success when `protect` is called twice" do - put api("/projects/#{project.id}/repository/branches/#{branch_name}/protect", user) - put api("/projects/#{project.id}/repository/branches/#{branch_name}/protect", user) + describe 'PUT /projects/:id/repository/branches/:branch/unprotect' do + let(:route) { "/projects/#{project_id}/repository/branches/#{branch_name}/unprotect" } - expect(response).to have_http_status(200) - expect(json_response['name']).to eq(branch_name) - expect(json_response['protected']).to eq(true) - expect(json_response['developers_can_push']).to eq(false) - expect(json_response['developers_can_merge']).to eq(false) + shared_examples_for 'repository unprotected branch' do + it 'unprotects a single branch' do + put api(route, current_user) + + expect(response).to have_gitlab_http_status(200) + expect(response).to match_response_schema('public_api/v4/branch') + expect(json_response['name']).to eq(CGI.unescape(branch_name)) + expect(json_response['protected']).to eq(false) end - it "returns success when `protect` is called twice with `developers_can_push` turned on" do - put api("/projects/#{project.id}/repository/branches/#{branch_name}/protect", user), developers_can_push: true - put api("/projects/#{project.id}/repository/branches/#{branch_name}/protect", user), developers_can_push: true + context 'when branch does not exist' do + let(:branch_name) { 'unknown' } - expect(response).to have_http_status(200) - expect(json_response['name']).to eq(branch_name) - expect(json_response['protected']).to eq(true) - expect(json_response['developers_can_push']).to eq(true) - expect(json_response['developers_can_merge']).to eq(false) + it_behaves_like '404 response' do + let(:request) { put api(route, current_user) } + let(:message) { '404 Branch Not Found' } + end end - it "returns success when `protect` is called twice with `developers_can_merge` turned on" do - put api("/projects/#{project.id}/repository/branches/#{branch_name}/protect", user), developers_can_merge: true - put api("/projects/#{project.id}/repository/branches/#{branch_name}/protect", user), developers_can_merge: true + context 'when repository is disabled' do + include_context 'disabled repository' - expect(response).to have_http_status(200) - expect(json_response['name']).to eq(branch_name) - expect(json_response['protected']).to eq(true) - expect(json_response['developers_can_push']).to eq(false) - expect(json_response['developers_can_merge']).to eq(true) + it_behaves_like '403 response' do + let(:request) { put api(route, current_user) } + end end end - it "returns a 404 error if branch not found" do - put api("/projects/#{project.id}/repository/branches/unknown/protect", user) - expect(response).to have_http_status(404) + context 'when unauthenticated', 'and project is private' do + it_behaves_like '404 response' do + let(:request) { put api(route) } + let(:message) { '404 Project Not Found' } + end end - it "returns a 403 error if guest" do - put api("/projects/#{project.id}/repository/branches/#{branch_name}/protect", guest) - expect(response).to have_http_status(403) + context 'when authenticated', 'as a guest' do + it_behaves_like '403 response' do + let(:request) { put api(route, guest) } + end end - end - describe "PUT /projects/:id/repository/branches/:branch/unprotect" do - it "unprotects a single branch" do - put api("/projects/#{project.id}/repository/branches/#{branch_name}/unprotect", user) - expect(response).to have_http_status(200) + context 'when authenticated', 'as a master' do + let(:current_user) { user } + + context "when a protected branch doesn't already exist" do + it_behaves_like 'repository unprotected branch' + + context 'when branch contains a dot' do + let(:branch_name) { branch_with_dot.name } + + it_behaves_like 'repository unprotected branch' + end + + context 'when branch contains a slash' do + let(:branch_name) { branch_with_slash.name } + + it_behaves_like '404 response' do + let(:request) { put api(route, current_user) } + end + end + + context 'when branch contains an escaped slash' do + let(:branch_name) { CGI.escape(branch_with_slash.name) } - expect(json_response['name']).to eq(branch_name) - expect(json_response['commit']['id']).to eq(branch_sha) - expect(json_response['protected']).to eq(false) + it_behaves_like 'repository unprotected branch' + end + + context 'requesting with the escaped project full path' do + let(:project_id) { CGI.escape(project.full_path) } + + it_behaves_like 'repository unprotected branch' + + context 'when branch contains a dot' do + let(:branch_name) { branch_with_dot.name } + + it_behaves_like 'repository unprotected branch' + end + end + end end + end - it "update branches with dots in branch name" do - put api("/projects/#{project.id}/repository/branches/#{branch_with_dot.name}/unprotect", user) + describe 'POST /projects/:id/repository/branches' do + let(:route) { "/projects/#{project_id}/repository/branches" } - expect(response).to have_http_status(200) - expect(json_response['name']).to eq(branch_with_dot.name) - expect(json_response['protected']).to eq(false) + shared_examples_for 'repository new branch' do + it 'creates a new branch' do + post api(route, current_user), branch: 'feature1', ref: branch_sha + + expect(response).to have_gitlab_http_status(201) + expect(response).to match_response_schema('public_api/v4/branch') + expect(json_response['name']).to eq('feature1') + expect(json_response['commit']['id']).to eq(branch_sha) + end + + context 'when repository is disabled' do + include_context 'disabled repository' + + it_behaves_like '403 response' do + let(:request) { post api(route, current_user) } + end + end end - it "returns success when unprotect branch" do - put api("/projects/#{project.id}/repository/branches/unknown/unprotect", user) - expect(response).to have_http_status(404) + context 'when unauthenticated', 'and project is private' do + it_behaves_like '404 response' do + let(:request) { post api(route) } + let(:message) { '404 Project Not Found' } + end end - it "returns success when unprotect branch again" do - put api("/projects/#{project.id}/repository/branches/#{branch_name}/unprotect", user) - put api("/projects/#{project.id}/repository/branches/#{branch_name}/unprotect", user) - expect(response).to have_http_status(200) + context 'when authenticated', 'as a guest' do + it_behaves_like '403 response' do + let(:request) { post api(route, guest) } + end end - end - describe "POST /projects/:id/repository/branches" do - it "creates a new branch" do - post api("/projects/#{project.id}/repository/branches", user), - branch: 'feature1', - ref: branch_sha + context 'when authenticated', 'as a master' do + let(:current_user) { user } - expect(response).to have_http_status(201) + context "when a protected branch doesn't already exist" do + it_behaves_like 'repository new branch' - expect(json_response['name']).to eq('feature1') - expect(json_response['commit']['id']).to eq(branch_sha) - end + context 'requesting with the escaped project full path' do + let(:project_id) { CGI.escape(project.full_path) } - it "denies for user without push access" do - post api("/projects/#{project.id}/repository/branches", guest), - branch: branch_name, - ref: branch_sha - expect(response).to have_http_status(403) + it_behaves_like 'repository new branch' + end + end end it 'returns 400 if branch name is invalid' do - post api("/projects/#{project.id}/repository/branches", user), - branch: 'new design', - ref: branch_sha - expect(response).to have_http_status(400) + post api(route, user), branch: 'new design', ref: branch_sha + + expect(response).to have_gitlab_http_status(400) expect(json_response['message']).to eq('Branch name is invalid') end it 'returns 400 if branch already exists' do - post api("/projects/#{project.id}/repository/branches", user), - branch: 'new_design1', - ref: branch_sha - expect(response).to have_http_status(201) - - post api("/projects/#{project.id}/repository/branches", user), - branch: 'new_design1', - ref: branch_sha - expect(response).to have_http_status(400) + post api(route, user), branch: 'new_design1', ref: branch_sha + + expect(response).to have_gitlab_http_status(201) + + post api(route, user), branch: 'new_design1', ref: branch_sha + + expect(response).to have_gitlab_http_status(400) expect(json_response['message']).to eq('Branch already exists') end it 'returns 400 if ref name is invalid' do - post api("/projects/#{project.id}/repository/branches", user), - branch: 'new_design3', - ref: 'foo' - expect(response).to have_http_status(400) + post api(route, user), branch: 'new_design3', ref: 'foo' + + expect(response).to have_gitlab_http_status(400) expect(json_response['message']).to eq('Invalid reference name') end end - describe "DELETE /projects/:id/repository/branches/:branch" do + describe 'DELETE /projects/:id/repository/branches/:branch' do before do allow_any_instance_of(Repository).to receive(:rm_branch).and_return(true) end - it "removes branch" do + it 'removes branch' do delete api("/projects/#{project.id}/repository/branches/#{branch_name}", user) - expect(response).to have_http_status(204) + expect(response).to have_gitlab_http_status(204) end - it "removes a branch with dots in the branch name" do + it 'removes a branch with dots in the branch name' do delete api("/projects/#{project.id}/repository/branches/#{branch_with_dot.name}", user) - expect(response).to have_http_status(204) + expect(response).to have_gitlab_http_status(204) end it 'returns 404 if branch not exists' do delete api("/projects/#{project.id}/repository/branches/foobar", user) - expect(response).to have_http_status(404) + + expect(response).to have_gitlab_http_status(404) end end - describe "DELETE /projects/:id/repository/merged_branches" do + describe 'DELETE /projects/:id/repository/merged_branches' do before do allow_any_instance_of(Repository).to receive(:rm_branch).and_return(true) end @@ -416,13 +509,14 @@ describe API::Branches do it 'returns 202 with json body' do delete api("/projects/#{project.id}/repository/merged_branches", user) - expect(response).to have_http_status(202) + expect(response).to have_gitlab_http_status(202) expect(json_response['message']).to eql('202 Accepted') end it 'returns a 403 error if guest' do delete api("/projects/#{project.id}/repository/merged_branches", guest) - expect(response).to have_http_status(403) + + expect(response).to have_gitlab_http_status(403) end end end diff --git a/spec/requests/api/commit_statuses_spec.rb b/spec/requests/api/commit_statuses_spec.rb index b8ca73c321c..8b62aa268d9 100644 --- a/spec/requests/api/commit_statuses_spec.rb +++ b/spec/requests/api/commit_statuses_spec.rb @@ -164,25 +164,40 @@ describe API::CommitStatuses do context 'with all optional parameters' do context 'when creating a commit status' do - it 'creates commit status' do + subject do post api(post_url, developer), { state: 'success', context: 'coverage', - ref: 'develop', + ref: 'master', description: 'test', coverage: 80.0, target_url: 'http://gitlab.com/status' } + end + + it 'creates commit status' do + subject expect(response).to have_http_status(201) expect(json_response['sha']).to eq(commit.id) expect(json_response['status']).to eq('success') expect(json_response['name']).to eq('coverage') - expect(json_response['ref']).to eq('develop') + expect(json_response['ref']).to eq('master') expect(json_response['coverage']).to eq(80.0) expect(json_response['description']).to eq('test') expect(json_response['target_url']).to eq('http://gitlab.com/status') end + + context 'when merge request exists for given branch' do + let!(:merge_request) { create(:merge_request, source_project: project, source_branch: 'master', target_branch: 'develop') } + + it 'sets head pipeline' do + subject + + expect(response).to have_http_status(201) + expect(merge_request.reload.head_pipeline).not_to be_nil + end + end end context 'when updatig a commit status' do @@ -190,7 +205,7 @@ describe API::CommitStatuses do post api(post_url, developer), { state: 'running', context: 'coverage', - ref: 'develop', + ref: 'master', description: 'coverage test', coverage: 0.0, target_url: 'http://gitlab.com/status' @@ -199,7 +214,7 @@ describe API::CommitStatuses do post api(post_url, developer), { state: 'success', name: 'coverage', - ref: 'develop', + ref: 'master', description: 'new description', coverage: 90.0 } @@ -210,7 +225,7 @@ describe API::CommitStatuses do expect(json_response['sha']).to eq(commit.id) expect(json_response['status']).to eq('success') expect(json_response['name']).to eq('coverage') - expect(json_response['ref']).to eq('develop') + expect(json_response['ref']).to eq('master') expect(json_response['coverage']).to eq(90.0) expect(json_response['description']).to eq('new description') expect(json_response['target_url']).to eq('http://gitlab.com/status') @@ -222,6 +237,28 @@ describe API::CommitStatuses do end end + context 'when retrying a commit status' do + before do + post api(post_url, developer), + { state: 'failed', name: 'test', ref: 'master' } + + post api(post_url, developer), + { state: 'success', name: 'test', ref: 'master' } + end + + it 'correctly posts a new commit status' do + expect(response).to have_http_status(201) + expect(json_response['sha']).to eq(commit.id) + expect(json_response['status']).to eq('success') + end + + it 'retries a commit status' do + expect(CommitStatus.count).to eq 2 + expect(CommitStatus.first).to be_retried + expect(CommitStatus.last.pipeline).to be_success + end + end + context 'when status is invalid' do before do post api(post_url, developer), state: 'invalid' diff --git a/spec/requests/api/events_spec.rb b/spec/requests/api/events_spec.rb index a19870a95e8..1754ba66a96 100644 --- a/spec/requests/api/events_spec.rb +++ b/spec/requests/api/events_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe API::Events, api: true do +describe API::Events do include ApiHelpers let(:user) { create(:user) } let(:non_member) { create(:user) } diff --git a/spec/requests/api/features_spec.rb b/spec/requests/api/features_spec.rb index f169e6661d1..7e21006b254 100644 --- a/spec/requests/api/features_spec.rb +++ b/spec/requests/api/features_spec.rb @@ -4,6 +4,13 @@ describe API::Features do let(:user) { create(:user) } let(:admin) { create(:admin) } + before do + Flipper.unregister_groups + Flipper.register(:perf_team) do |actor| + actor.respond_to?(:admin) && actor.admin? + end + end + describe 'GET /features' do let(:expected_features) do [ @@ -16,6 +23,14 @@ describe API::Features do 'name' => 'feature_2', 'state' => 'off', 'gates' => [{ 'key' => 'boolean', 'value' => false }] + }, + { + 'name' => 'feature_3', + 'state' => 'conditional', + 'gates' => [ + { 'key' => 'boolean', 'value' => false }, + { 'key' => 'groups', 'value' => ['perf_team'] } + ] } ] end @@ -23,6 +38,7 @@ describe API::Features do before do Feature.get('feature_1').enable Feature.get('feature_2').disable + Feature.get('feature_3').enable Feature.group(:perf_team) end it 'returns a 401 for anonymous users' do @@ -47,30 +63,84 @@ describe API::Features do describe 'POST /feature' do let(:feature_name) { 'my_feature' } - it 'returns a 401 for anonymous users' do - post api("/features/#{feature_name}") - expect(response).to have_http_status(401) - end + context 'when the feature does not exist' do + it 'returns a 401 for anonymous users' do + post api("/features/#{feature_name}") - it 'returns a 403 for users' do - post api("/features/#{feature_name}", user) + expect(response).to have_http_status(401) + end - expect(response).to have_http_status(403) - end + it 'returns a 403 for users' do + post api("/features/#{feature_name}", user) - it 'creates an enabled feature if passed true' do - post api("/features/#{feature_name}", admin), value: 'true' + expect(response).to have_http_status(403) + end - expect(response).to have_http_status(201) - expect(Feature.get(feature_name)).to be_enabled - end + context 'when passed value=true' do + it 'creates an enabled feature' do + post api("/features/#{feature_name}", admin), value: 'true' + + expect(response).to have_http_status(201) + expect(json_response).to eq( + 'name' => 'my_feature', + 'state' => 'on', + 'gates' => [{ 'key' => 'boolean', 'value' => true }]) + end + + it 'creates an enabled feature for the given Flipper group when passed feature_group=perf_team' do + post api("/features/#{feature_name}", admin), value: 'true', feature_group: 'perf_team' + + expect(response).to have_http_status(201) + expect(json_response).to eq( + 'name' => 'my_feature', + 'state' => 'conditional', + 'gates' => [ + { 'key' => 'boolean', 'value' => false }, + { 'key' => 'groups', 'value' => ['perf_team'] } + ]) + end + + it 'creates an enabled feature for the given user when passed user=username' do + post api("/features/#{feature_name}", admin), value: 'true', user: user.username + + expect(response).to have_http_status(201) + expect(json_response).to eq( + 'name' => 'my_feature', + 'state' => 'conditional', + 'gates' => [ + { 'key' => 'boolean', 'value' => false }, + { 'key' => 'actors', 'value' => ["User:#{user.id}"] } + ]) + end + + it 'creates an enabled feature for the given user and feature group when passed user=username and feature_group=perf_team' do + post api("/features/#{feature_name}", admin), value: 'true', user: user.username, feature_group: 'perf_team' + + expect(response).to have_http_status(201) + expect(json_response).to eq( + 'name' => 'my_feature', + 'state' => 'conditional', + 'gates' => [ + { 'key' => 'boolean', 'value' => false }, + { 'key' => 'groups', 'value' => ['perf_team'] }, + { 'key' => 'actors', 'value' => ["User:#{user.id}"] } + ]) + end + end - it 'creates a feature with the given percentage if passed an integer' do - post api("/features/#{feature_name}", admin), value: '50' + it 'creates a feature with the given percentage if passed an integer' do + post api("/features/#{feature_name}", admin), value: '50' - expect(response).to have_http_status(201) - expect(Feature.get(feature_name).percentage_of_time_value).to be(50) + expect(response).to have_http_status(201) + expect(json_response).to eq( + 'name' => 'my_feature', + 'state' => 'conditional', + 'gates' => [ + { 'key' => 'boolean', 'value' => false }, + { 'key' => 'percentage_of_time', 'value' => 50 } + ]) + end end context 'when the feature exists' do @@ -80,11 +150,83 @@ describe API::Features do feature.disable # This also persists the feature on the DB end - it 'enables the feature if passed true' do - post api("/features/#{feature_name}", admin), value: 'true' + context 'when passed value=true' do + it 'enables the feature' do + post api("/features/#{feature_name}", admin), value: 'true' - expect(response).to have_http_status(201) - expect(feature).to be_enabled + expect(response).to have_http_status(201) + expect(json_response).to eq( + 'name' => 'my_feature', + 'state' => 'on', + 'gates' => [{ 'key' => 'boolean', 'value' => true }]) + end + + it 'enables the feature for the given Flipper group when passed feature_group=perf_team' do + post api("/features/#{feature_name}", admin), value: 'true', feature_group: 'perf_team' + + expect(response).to have_http_status(201) + expect(json_response).to eq( + 'name' => 'my_feature', + 'state' => 'conditional', + 'gates' => [ + { 'key' => 'boolean', 'value' => false }, + { 'key' => 'groups', 'value' => ['perf_team'] } + ]) + end + + it 'enables the feature for the given user when passed user=username' do + post api("/features/#{feature_name}", admin), value: 'true', user: user.username + + expect(response).to have_http_status(201) + expect(json_response).to eq( + 'name' => 'my_feature', + 'state' => 'conditional', + 'gates' => [ + { 'key' => 'boolean', 'value' => false }, + { 'key' => 'actors', 'value' => ["User:#{user.id}"] } + ]) + end + end + + context 'when feature is enabled and value=false is passed' do + it 'disables the feature' do + feature.enable + expect(feature).to be_enabled + + post api("/features/#{feature_name}", admin), value: 'false' + + expect(response).to have_http_status(201) + expect(json_response).to eq( + 'name' => 'my_feature', + 'state' => 'off', + 'gates' => [{ 'key' => 'boolean', 'value' => false }]) + end + + it 'disables the feature for the given Flipper group when passed feature_group=perf_team' do + feature.enable(Feature.group(:perf_team)) + expect(Feature.get(feature_name).enabled?(admin)).to be_truthy + + post api("/features/#{feature_name}", admin), value: 'false', feature_group: 'perf_team' + + expect(response).to have_http_status(201) + expect(json_response).to eq( + 'name' => 'my_feature', + 'state' => 'off', + 'gates' => [{ 'key' => 'boolean', 'value' => false }]) + end + + it 'disables the feature for the given user when passed user=username' do + feature.enable(user) + expect(Feature.get(feature_name).enabled?(user)).to be_truthy + + post api("/features/#{feature_name}", admin), value: 'false', user: user.username + + expect(response).to have_http_status(201) + expect(json_response).to eq( + 'name' => 'my_feature', + 'state' => 'off', + 'gates' => [{ 'key' => 'boolean', 'value' => false }]) + end end context 'with a pre-existing percentage value' do @@ -96,7 +238,13 @@ describe API::Features do post api("/features/#{feature_name}", admin), value: '30' expect(response).to have_http_status(201) - expect(Feature.get(feature_name).percentage_of_time_value).to be(30) + expect(json_response).to eq( + 'name' => 'my_feature', + 'state' => 'conditional', + 'gates' => [ + { 'key' => 'boolean', 'value' => false }, + { 'key' => 'percentage_of_time', 'value' => 30 } + ]) end end end diff --git a/spec/requests/api/group_milestones_spec.rb b/spec/requests/api/group_milestones_spec.rb new file mode 100644 index 00000000000..9b24658771f --- /dev/null +++ b/spec/requests/api/group_milestones_spec.rb @@ -0,0 +1,21 @@ +require 'spec_helper' + +describe API::GroupMilestones do + let(:user) { create(:user) } + let(:group) { create(:group, :private) } + let(:project) { create(:empty_project, namespace: group) } + let!(:group_member) { create(:group_member, group: group, user: user) } + let!(:closed_milestone) { create(:closed_milestone, group: group, title: 'version1', description: 'closed milestone') } + let!(:milestone) { create(:milestone, group: group, title: 'version2', description: 'open milestone') } + + it_behaves_like 'group and project milestones', "/groups/:id/milestones" do + let(:route) { "/groups/#{group.id}/milestones" } + end + + def setup_for_group + context_group.update(visibility_level: Gitlab::VisibilityLevel::PUBLIC) + context_group.add_developer(user) + public_project.update(namespace: context_group) + context_group.reload + end +end diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb index 656f098aea8..1d7adc6ac45 100644 --- a/spec/requests/api/groups_spec.rb +++ b/spec/requests/api/groups_spec.rb @@ -510,7 +510,7 @@ describe API::Groups do describe "POST /groups/:id/projects/:project_id" do let(:project) { create(:empty_project) } - let(:project_path) { project.full_path.gsub('/', '%2F') } + let(:project_path) { CGI.escape(project.full_path) } before(:each) do allow_any_instance_of(Projects::TransferService) diff --git a/spec/requests/api/helpers_spec.rb b/spec/requests/api/helpers_spec.rb index 191c60aba31..7a1bd76af7a 100644 --- a/spec/requests/api/helpers_spec.rb +++ b/spec/requests/api/helpers_spec.rb @@ -10,10 +10,22 @@ describe API::Helpers do let(:key) { create(:key, user: user) } let(:params) { {} } - let(:env) { { 'REQUEST_METHOD' => 'GET' } } - let(:request) { Rack::Request.new(env) } + let(:csrf_token) { SecureRandom.base64(ActionController::RequestForgeryProtection::AUTHENTICITY_TOKEN_LENGTH) } + let(:env) do + { + 'rack.input' => '', + 'rack.session' => { + _csrf_token: csrf_token + }, + 'REQUEST_METHOD' => 'GET' + } + end let(:header) { } + before do + allow_any_instance_of(self.class).to receive(:options).and_return({}) + end + def set_env(user_or_token, identifier) clear_env clear_param @@ -54,7 +66,7 @@ describe API::Helpers do describe ".current_user" do subject { current_user } - describe "Warden authentication" do + describe "Warden authentication", :allow_forgery_protection do before do doorkeeper_guard_returns false end @@ -95,7 +107,17 @@ describe API::Helpers do env['REQUEST_METHOD'] = 'PUT' end - it { is_expected.to be_nil } + context 'without CSRF token' do + it { is_expected.to be_nil } + end + + context 'with CSRF token' do + before do + env['HTTP_X_CSRF_TOKEN'] = csrf_token + end + + it { is_expected.to eq(user) } + end end context "POST request" do @@ -103,7 +125,17 @@ describe API::Helpers do env['REQUEST_METHOD'] = 'POST' end - it { is_expected.to be_nil } + context 'without CSRF token' do + it { is_expected.to be_nil } + end + + context 'with CSRF token' do + before do + env['HTTP_X_CSRF_TOKEN'] = csrf_token + end + + it { is_expected.to eq(user) } + end end context "DELETE request" do @@ -111,7 +143,17 @@ describe API::Helpers do env['REQUEST_METHOD'] = 'DELETE' end - it { is_expected.to be_nil } + context 'without CSRF token' do + it { is_expected.to be_nil } + end + + context 'with CSRF token' do + before do + env['HTTP_X_CSRF_TOKEN'] = csrf_token + end + + it { is_expected.to eq(user) } + end end end end @@ -167,7 +209,6 @@ describe API::Helpers do it "returns nil for a token without the appropriate scope" do personal_access_token = create(:personal_access_token, user: user, scopes: ['read_user']) env[API::APIGuard::PRIVATE_TOKEN_HEADER] = personal_access_token.token - allow_access_with_scope('write_user') expect(current_user).to be_nil end diff --git a/spec/requests/api/internal_spec.rb b/spec/requests/api/internal_spec.rb index 6deaea956e0..ce9b9ac1eb3 100644 --- a/spec/requests/api/internal_spec.rb +++ b/spec/requests/api/internal_spec.rb @@ -6,7 +6,7 @@ describe API::Internal do let(:project) { create(:project, :repository) } let(:secret_token) { Gitlab::Shell.secret_token } - describe "GET /internal/check", no_db: true do + describe "GET /internal/check" do it do get api("/internal/check"), secret_token: secret_token @@ -35,6 +35,17 @@ describe API::Internal do expect(json_response).to be_empty end end + + context 'nil broadcast message' do + it 'returns nothing' do + allow(BroadcastMessage).to receive(:current).and_return(nil) + + get api('/internal/broadcast_message'), secret_token: secret_token + + expect(response).to have_http_status(200) + expect(json_response).to be_empty + end + end end describe 'GET /internal/broadcast_messages' do @@ -168,7 +179,7 @@ describe API::Internal do end end - describe "POST /internal/allowed", :redis do + describe "POST /internal/allowed", :clean_gitlab_redis_shared_state do context "access granted" do before do project.team << [user, :developer] @@ -220,26 +231,72 @@ describe API::Internal do end context "git pull" do - it do - pull(key, project) + context "gitaly disabled" do + it "has the correct payload" do + allow(Gitlab::GitalyClient).to receive(:feature_enabled?).with(:ssh_upload_pack).and_return(false) + pull(key, project) - expect(response).to have_http_status(200) - expect(json_response["status"]).to be_truthy - expect(json_response["repository_path"]).to eq(project.repository.path_to_repo) - expect(json_response["gl_repository"]).to eq("project-#{project.id}") - expect(user).to have_an_activity_record + expect(response).to have_http_status(200) + expect(json_response["status"]).to be_truthy + expect(json_response["repository_path"]).to eq(project.repository.path_to_repo) + expect(json_response["gl_repository"]).to eq("project-#{project.id}") + expect(json_response["gitaly"]).to be_nil + expect(user).to have_an_activity_record + end + end + + context "gitaly enabled" do + it "has the correct payload" do + allow(Gitlab::GitalyClient).to receive(:feature_enabled?).with(:ssh_upload_pack).and_return(true) + pull(key, project) + + expect(response).to have_http_status(200) + expect(json_response["status"]).to be_truthy + expect(json_response["repository_path"]).to eq(project.repository.path_to_repo) + expect(json_response["gl_repository"]).to eq("project-#{project.id}") + expect(json_response["gitaly"]).not_to be_nil + expect(json_response["gitaly"]["repository"]).not_to be_nil + expect(json_response["gitaly"]["repository"]["storage_name"]).to eq(project.repository.gitaly_repository.storage_name) + expect(json_response["gitaly"]["repository"]["relative_path"]).to eq(project.repository.gitaly_repository.relative_path) + expect(json_response["gitaly"]["address"]).to eq(Gitlab::GitalyClient.address(project.repository_storage)) + expect(json_response["gitaly"]["token"]).to eq(Gitlab::GitalyClient.token(project.repository_storage)) + expect(user).to have_an_activity_record + end end end context "git push" do - it do - push(key, project) + context "gitaly disabled" do + it "has the correct payload" do + allow(Gitlab::GitalyClient).to receive(:feature_enabled?).with(:ssh_receive_pack).and_return(false) + push(key, project) - expect(response).to have_http_status(200) - expect(json_response["status"]).to be_truthy - expect(json_response["repository_path"]).to eq(project.repository.path_to_repo) - expect(json_response["gl_repository"]).to eq("project-#{project.id}") - expect(user).not_to have_an_activity_record + expect(response).to have_http_status(200) + expect(json_response["status"]).to be_truthy + expect(json_response["repository_path"]).to eq(project.repository.path_to_repo) + expect(json_response["gl_repository"]).to eq("project-#{project.id}") + expect(json_response["gitaly"]).to be_nil + expect(user).not_to have_an_activity_record + end + end + + context "gitaly enabled" do + it "has the correct payload" do + allow(Gitlab::GitalyClient).to receive(:feature_enabled?).with(:ssh_receive_pack).and_return(true) + push(key, project) + + expect(response).to have_http_status(200) + expect(json_response["status"]).to be_truthy + expect(json_response["repository_path"]).to eq(project.repository.path_to_repo) + expect(json_response["gl_repository"]).to eq("project-#{project.id}") + expect(json_response["gitaly"]).not_to be_nil + expect(json_response["gitaly"]["repository"]).not_to be_nil + expect(json_response["gitaly"]["repository"]["storage_name"]).to eq(project.repository.gitaly_repository.storage_name) + expect(json_response["gitaly"]["repository"]["relative_path"]).to eq(project.repository.gitaly_repository.relative_path) + expect(json_response["gitaly"]["address"]).to eq(Gitlab::GitalyClient.address(project.repository_storage)) + expect(json_response["gitaly"]["token"]).to eq(Gitlab::GitalyClient.token(project.repository_storage)) + expect(user).not_to have_an_activity_record + end end context 'project as /namespace/project' do @@ -537,10 +594,10 @@ describe API::Internal do # end # # it "calls the Gitaly client with the project's repository" do - # expect(Gitlab::GitalyClient::Notifications). + # expect(Gitlab::GitalyClient::NotificationService). # to receive(:new).with(gitlab_git_repository_with(path: project.repository.path)). # and_call_original - # expect_any_instance_of(Gitlab::GitalyClient::Notifications). + # expect_any_instance_of(Gitlab::GitalyClient::NotificationService). # to receive(:post_receive) # # post api("/internal/notify_post_receive"), valid_params @@ -549,10 +606,10 @@ describe API::Internal do # end # # it "calls the Gitaly client with the wiki's repository if it's a wiki" do - # expect(Gitlab::GitalyClient::Notifications). + # expect(Gitlab::GitalyClient::NotificationService). # to receive(:new).with(gitlab_git_repository_with(path: project.wiki.repository.path)). # and_call_original - # expect_any_instance_of(Gitlab::GitalyClient::Notifications). + # expect_any_instance_of(Gitlab::GitalyClient::NotificationService). # to receive(:post_receive) # # post api("/internal/notify_post_receive"), valid_wiki_params @@ -561,7 +618,7 @@ describe API::Internal do # end # # it "returns 500 if the gitaly call fails" do - # expect_any_instance_of(Gitlab::GitalyClient::Notifications). + # expect_any_instance_of(Gitlab::GitalyClient::NotificationService). # to receive(:post_receive).and_raise(GRPC::Unavailable) # # post api("/internal/notify_post_receive"), valid_params @@ -579,10 +636,10 @@ describe API::Internal do # end # # it "calls the Gitaly client with the project's repository" do - # expect(Gitlab::GitalyClient::Notifications). + # expect(Gitlab::GitalyClient::NotificationService). # to receive(:new).with(gitlab_git_repository_with(path: project.repository.path)). # and_call_original - # expect_any_instance_of(Gitlab::GitalyClient::Notifications). + # expect_any_instance_of(Gitlab::GitalyClient::NotificationService). # to receive(:post_receive) # # post api("/internal/notify_post_receive"), valid_params @@ -591,10 +648,10 @@ describe API::Internal do # end # # it "calls the Gitaly client with the wiki's repository if it's a wiki" do - # expect(Gitlab::GitalyClient::Notifications). + # expect(Gitlab::GitalyClient::NotificationService). # to receive(:new).with(gitlab_git_repository_with(path: project.wiki.repository.path)). # and_call_original - # expect_any_instance_of(Gitlab::GitalyClient::Notifications). + # expect_any_instance_of(Gitlab::GitalyClient::NotificationService). # to receive(:post_receive) # # post api("/internal/notify_post_receive"), valid_wiki_params diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb index 79cac721202..2c44be4e447 100644 --- a/spec/requests/api/issues_spec.rb +++ b/spec/requests/api/issues_spec.rb @@ -71,7 +71,6 @@ describe API::Issues do expect(response).to have_http_status(401) end end - context "when authenticated" do let(:first_issue) { json_response.first } @@ -105,6 +104,42 @@ describe API::Issues do expect(json_response.second['id']).to eq(closed_issue.id) end + it 'returns issues assigned to me' do + issue2 = create(:issue, assignees: [user2], project: project) + + get api('/issues', user2), scope: 'assigned-to-me' + + expect_paginated_array_response(size: 1) + expect(first_issue['id']).to eq(issue2.id) + end + + it 'returns issues authored by the given author id' do + issue2 = create(:issue, author: user2, project: project) + + get api('/issues', user), author_id: user2.id, scope: 'all' + + expect_paginated_array_response(size: 1) + expect(first_issue['id']).to eq(issue2.id) + end + + it 'returns issues assigned to the given assignee id' do + issue2 = create(:issue, assignees: [user2], project: project) + + get api('/issues', user), assignee_id: user2.id, scope: 'all' + + expect_paginated_array_response(size: 1) + expect(first_issue['id']).to eq(issue2.id) + end + + it 'returns issues authored by the given author id and assigned to the given assignee id' do + issue2 = create(:issue, author: user2, assignees: [user2], project: project) + + get api('/issues', user), author_id: user2.id, assignee_id: user2.id, scope: 'all' + + expect_paginated_array_response(size: 1) + expect(first_issue['id']).to eq(issue2.id) + end + it 'returns issues matching given search string for title' do get api("/issues", user), search: issue.title @@ -693,6 +728,19 @@ describe API::Issues do expect(json_response['confidential']).to be_falsy end + context 'links exposure' do + it 'exposes related resources full URIs' do + get api("/projects/#{project.id}/issues/#{issue.iid}", user) + + links = json_response['_links'] + + expect(links['self']).to end_with("/api/v4/projects/#{project.id}/issues/#{issue.iid}") + expect(links['notes']).to end_with("/api/v4/projects/#{project.id}/issues/#{issue.iid}/notes") + expect(links['award_emoji']).to end_with("/api/v4/projects/#{project.id}/issues/#{issue.iid}/award_emoji") + expect(links['project']).to end_with("/api/v4/projects/#{project.id}") + end + end + it "returns a project issue by internal id" do get api("/projects/#{project.id}/issues/#{issue.iid}", user) @@ -772,7 +820,7 @@ describe API::Issues do end end - context 'CE restrictions' do + context 'single assignee restrictions' do it 'creates a new project issue with no more than one assignee' do post api("/projects/#{project.id}/issues", user), title: 'new issue', assignee_ids: [user2.id, guest.id] @@ -1123,7 +1171,7 @@ describe API::Issues do expect(json_response['assignees'].first['name']).to eq(user2.name) end - context 'CE restrictions' do + context 'single assignee restrictions' do it 'updates an issue with several assignees but only one has been applied' do put api("/projects/#{project.id}/issues/#{issue.iid}", user), assignee_ids: [user2.id, guest.id] @@ -1211,7 +1259,7 @@ describe API::Issues do put api("/projects/#{project.id}/issues/#{closed_issue.iid}", user), state_event: 'reopen' expect(response).to have_http_status(200) - expect(json_response['state']).to eq 'reopened' + expect(json_response['state']).to eq 'opened' end context 'when an admin or owner makes the request' do @@ -1462,6 +1510,25 @@ describe API::Issues do end end + describe "GET /projects/:id/issues/:issue_iid/user_agent_detail" do + let!(:user_agent_detail) { create(:user_agent_detail, subject: issue) } + + it 'exposes known attributes' do + get api("/projects/#{project.id}/issues/#{issue.iid}/user_agent_detail", admin) + + expect(response).to have_http_status(200) + expect(json_response['user_agent']).to eq(user_agent_detail.user_agent) + expect(json_response['ip_address']).to eq(user_agent_detail.ip_address) + expect(json_response['akismet_submitted']).to eq(user_agent_detail.submitted) + end + + it "returns unautorized for non-admin users" do + get api("/projects/#{project.id}/issues/#{issue.iid}/user_agent_detail", user) + + expect(response).to have_http_status(403) + end + end + def expect_paginated_array_response(size: nil) expect(response).to have_http_status(200) expect(response).to include_pagination_headers diff --git a/spec/requests/api/jobs_spec.rb b/spec/requests/api/jobs_spec.rb index 8d647eb1c7e..f56baf9663d 100644 --- a/spec/requests/api/jobs_spec.rb +++ b/spec/requests/api/jobs_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe API::Jobs, :api do +describe API::Jobs do let!(:project) do create(:project, :repository, public_builds: false) end diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb index 4d0bd67c571..2760c4ffde2 100644 --- a/spec/requests/api/merge_requests_spec.rb +++ b/spec/requests/api/merge_requests_spec.rb @@ -16,12 +16,110 @@ describe API::MergeRequests do let!(:label) do create(:label, title: 'label', color: '#FFAABB', project: project) end + let!(:label2) { create(:label, title: 'a-test', color: '#FFFFFF', project: project) } let!(:label_link) { create(:label_link, label: label, target: merge_request) } + let!(:label_link2) { create(:label_link, label: label2, target: merge_request) } + let!(:downvote) { create(:award_emoji, :downvote, awardable: merge_request) } + let!(:upvote) { create(:award_emoji, :upvote, awardable: merge_request) } before do project.team << [user, :reporter] end + describe 'GET /merge_requests' do + context 'when unauthenticated' do + it 'returns authentication error' do + get api('/merge_requests') + + expect(response).to have_http_status(401) + end + end + + context 'when authenticated' do + let!(:project2) { create(:empty_project, :public, namespace: user.namespace) } + let!(:merge_request2) { create(:merge_request, :simple, author: user, assignee: user, source_project: project2, target_project: project2) } + let(:user2) { create(:user) } + + it 'returns an array of all merge requests' do + get api('/merge_requests', user), scope: :all + + expect(response).to have_http_status(200) + expect(response).to include_pagination_headers + expect(json_response).to be_an Array + expect(json_response.map { |mr| mr['id'] }) + .to contain_exactly(merge_request.id, merge_request_closed.id, merge_request_merged.id, merge_request2.id) + end + + it 'does not return unauthorized merge requests' do + private_project = create(:empty_project, :private) + merge_request3 = create(:merge_request, :simple, source_project: private_project, target_project: private_project, source_branch: 'other-branch') + + get api('/merge_requests', user), scope: :all + + expect(response).to have_http_status(200) + expect(response).to include_pagination_headers + expect(json_response).to be_an Array + expect(json_response.map { |mr| mr['id'] }) + .not_to include(merge_request3.id) + end + + it 'returns an array of merge requests created by current user if no scope is given' do + merge_request3 = create(:merge_request, :simple, author: user2, assignee: user, source_project: project2, target_project: project2, source_branch: 'other-branch') + + get api('/merge_requests', user2) + + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(json_response.length).to eq(1) + expect(json_response.first['id']).to eq(merge_request3.id) + end + + it 'returns an array of merge requests authored by the given user' do + merge_request3 = create(:merge_request, :simple, author: user2, assignee: user, source_project: project2, target_project: project2, source_branch: 'other-branch') + + get api('/merge_requests', user), author_id: user2.id, scope: :all + + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(json_response.length).to eq(1) + expect(json_response.first['id']).to eq(merge_request3.id) + end + + it 'returns an array of merge requests assigned to the given user' do + merge_request3 = create(:merge_request, :simple, author: user, assignee: user2, source_project: project2, target_project: project2, source_branch: 'other-branch') + + get api('/merge_requests', user), assignee_id: user2.id, scope: :all + + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(json_response.length).to eq(1) + expect(json_response.first['id']).to eq(merge_request3.id) + end + + it 'returns an array of merge requests assigned to me' do + merge_request3 = create(:merge_request, :simple, author: user, assignee: user2, source_project: project2, target_project: project2, source_branch: 'other-branch') + + get api('/merge_requests', user2), scope: 'assigned-to-me' + + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(json_response.length).to eq(1) + expect(json_response.first['id']).to eq(merge_request3.id) + end + + it 'returns an array of merge requests created by me' do + merge_request3 = create(:merge_request, :simple, author: user2, assignee: user, source_project: project2, target_project: project2, source_branch: 'other-branch') + + get api('/merge_requests', user2), scope: 'created-by-me' + + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(json_response.length).to eq(1) + expect(json_response.first['id']).to eq(merge_request3.id) + end + end + end + describe "GET /projects/:id/merge_requests" do context "when unauthenticated" do it "returns authentication error" do @@ -32,6 +130,18 @@ describe API::MergeRequests do end context "when authenticated" do + it 'avoids N+1 queries' do + control_count = ActiveRecord::QueryRecorder.new do + get api("/projects/#{project.id}/merge_requests", user) + end.count + + create(:merge_request, state: 'closed', milestone: milestone1, author: user, assignee: user, source_project: project, target_project: project, title: "Test", created_at: base_time) + + expect do + get api("/projects/#{project.id}/merge_requests", user) + end.not_to exceed_query_limit(control_count) + end + it "returns an array of all merge_requests" do get api("/projects/#{project.id}/merge_requests", user) @@ -44,12 +154,31 @@ describe API::MergeRequests do expect(json_response.last['sha']).to eq(merge_request.diff_head_sha) expect(json_response.last['merge_commit_sha']).to be_nil expect(json_response.last['merge_commit_sha']).to eq(merge_request.merge_commit_sha) + expect(json_response.last['downvotes']).to eq(1) + expect(json_response.last['upvotes']).to eq(1) + expect(json_response.last['labels']).to eq([label2.title, label.title]) expect(json_response.first['title']).to eq(merge_request_merged.title) expect(json_response.first['sha']).to eq(merge_request_merged.diff_head_sha) expect(json_response.first['merge_commit_sha']).not_to be_nil expect(json_response.first['merge_commit_sha']).to eq(merge_request_merged.merge_commit_sha) end + it "returns an array of all merge_requests using simple mode" do + get api("/projects/#{project.id}/merge_requests?view=simple", user) + + expect(response).to have_http_status(200) + expect(response).to include_pagination_headers + expect(json_response.last.keys).to match_array(%w(id iid title web_url created_at description project_id state updated_at)) + expect(json_response).to be_an Array + expect(json_response.length).to eq(3) + expect(json_response.last['iid']).to eq(merge_request.iid) + expect(json_response.last['title']).to eq(merge_request.title) + expect(json_response.last).to have_key('web_url') + expect(json_response.first['iid']).to eq(merge_request_merged.iid) + expect(json_response.first['title']).to eq(merge_request_merged.title) + expect(json_response.first).to have_key('web_url') + end + it "returns an array of all merge_requests" do get api("/projects/#{project.id}/merge_requests?state", user) @@ -145,7 +274,7 @@ describe API::MergeRequests do expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(1) - expect(json_response.first['labels']).to eq([label.title]) + expect(json_response.first['labels']).to eq([label2.title, label.title]) end it 'returns an array of labeled merge requests where all labels match' do @@ -236,8 +365,8 @@ describe API::MergeRequests do expect(json_response['author']).to be_a Hash expect(json_response['target_branch']).to eq(merge_request.target_branch) expect(json_response['source_branch']).to eq(merge_request.source_branch) - expect(json_response['upvotes']).to eq(0) - expect(json_response['downvotes']).to eq(0) + expect(json_response['upvotes']).to eq(1) + expect(json_response['downvotes']).to eq(1) expect(json_response['source_project_id']).to eq(merge_request.source_project.id) expect(json_response['target_project_id']).to eq(merge_request.target_project.id) expect(json_response['work_in_progress']).to be_falsy @@ -759,18 +888,24 @@ describe API::MergeRequests do it 'handles external issues' do jira_project = create(:jira_project, :public, name: 'JIR_EXT1') - issue = ExternalIssue.new("#{jira_project.name}-123", jira_project) - merge_request = create(:merge_request, :simple, author: user, assignee: user, source_project: jira_project) - merge_request.update_attribute(:description, "Closes #{issue.to_reference(jira_project)}") + ext_issue = ExternalIssue.new("#{jira_project.name}-123", jira_project) + issue = create(:issue, project: jira_project) + description = "Closes #{ext_issue.to_reference(jira_project)}\ncloses #{issue.to_reference}" + merge_request = create(:merge_request, + :simple, author: user, assignee: user, source_project: jira_project, description: description) get api("/projects/#{jira_project.id}/merge_requests/#{merge_request.iid}/closes_issues", user) expect(response).to have_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array - expect(json_response.length).to eq(1) + expect(json_response.length).to eq(2) + expect(json_response.second['title']).to eq(ext_issue.title) + expect(json_response.second['id']).to eq(ext_issue.id) + expect(json_response.second['confidential']).to be_nil expect(json_response.first['title']).to eq(issue.title) expect(json_response.first['id']).to eq(issue.id) + expect(json_response.first['confidential']).not_to be_nil end it 'returns 403 if the user has no access to the merge request' do diff --git a/spec/requests/api/milestones_spec.rb b/spec/requests/api/milestones_spec.rb deleted file mode 100644 index ab5ea3e8f2c..00000000000 --- a/spec/requests/api/milestones_spec.rb +++ /dev/null @@ -1,385 +0,0 @@ -require 'spec_helper' - -describe API::Milestones do - let(:user) { create(:user) } - let!(:project) { create(:empty_project, namespace: user.namespace ) } - let!(:closed_milestone) { create(:closed_milestone, project: project, title: 'version1', description: 'closed milestone') } - let!(:milestone) { create(:milestone, project: project, title: 'version2', description: 'open milestone') } - let(:label_1) { create(:label, title: 'label_1', project: project, priority: 1) } - let(:label_2) { create(:label, title: 'label_2', project: project, priority: 2) } - let(:label_3) { create(:label, title: 'label_3', project: project) } - - before do - project.team << [user, :developer] - end - - describe 'GET /projects/:id/milestones' do - it 'returns project milestones' do - get api("/projects/#{project.id}/milestones", user) - - expect(response).to have_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array - expect(json_response.first['title']).to eq(milestone.title) - end - - it 'returns a 401 error if user not authenticated' do - get api("/projects/#{project.id}/milestones") - - expect(response).to have_http_status(401) - end - - it 'returns an array of active milestones' do - get api("/projects/#{project.id}/milestones?state=active", user) - - expect(response).to have_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array - expect(json_response.length).to eq(1) - expect(json_response.first['id']).to eq(milestone.id) - end - - it 'returns an array of closed milestones' do - get api("/projects/#{project.id}/milestones?state=closed", user) - - expect(response).to have_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array - expect(json_response.length).to eq(1) - expect(json_response.first['id']).to eq(closed_milestone.id) - end - - it 'returns an array of milestones specified by iids' do - other_milestone = create(:milestone, project: project) - - get api("/projects/#{project.id}/milestones", user), iids: [closed_milestone.iid, other_milestone.iid] - - expect(response).to have_http_status(200) - expect(json_response).to be_an Array - expect(json_response.length).to eq(2) - expect(json_response.map{ |m| m['id'] }).to match_array([closed_milestone.id, other_milestone.id]) - end - - it 'does not return any milestone if none found' do - get api("/projects/#{project.id}/milestones", user), iids: [Milestone.maximum(:iid).succ] - - expect(response).to have_http_status(200) - expect(json_response).to be_an Array - expect(json_response.length).to eq(0) - end - end - - describe 'GET /projects/:id/milestones/:milestone_id' do - it 'returns a project milestone by id' do - get api("/projects/#{project.id}/milestones/#{milestone.id}", user) - - expect(response).to have_http_status(200) - expect(json_response['title']).to eq(milestone.title) - expect(json_response['iid']).to eq(milestone.iid) - end - - it 'returns a project milestone by iids array' do - get api("/projects/#{project.id}/milestones?iids=#{closed_milestone.iid}", user) - - expect(response.status).to eq 200 - expect(response).to include_pagination_headers - expect(json_response.size).to eq(1) - expect(json_response.size).to eq(1) - expect(json_response.first['title']).to eq closed_milestone.title - expect(json_response.first['id']).to eq closed_milestone.id - end - - it 'returns a project milestone by searching for title' do - get api("/projects/#{project.id}/milestones", user), search: 'version2' - - expect(response).to have_http_status(200) - expect(response).to include_pagination_headers - expect(json_response.size).to eq(1) - expect(json_response.first['title']).to eq milestone.title - expect(json_response.first['id']).to eq milestone.id - end - - it 'returns a project milestones by searching for description' do - get api("/projects/#{project.id}/milestones", user), search: 'open' - - expect(response).to have_http_status(200) - expect(response).to include_pagination_headers - expect(json_response.size).to eq(1) - expect(json_response.first['title']).to eq milestone.title - expect(json_response.first['id']).to eq milestone.id - end - end - - describe 'GET /projects/:id/milestones/:milestone_id' do - it 'returns a project milestone by id' do - get api("/projects/#{project.id}/milestones/#{milestone.id}", user) - - expect(response).to have_http_status(200) - expect(json_response['title']).to eq(milestone.title) - expect(json_response['iid']).to eq(milestone.iid) - end - - it 'returns 401 error if user not authenticated' do - get api("/projects/#{project.id}/milestones/#{milestone.id}") - - expect(response).to have_http_status(401) - end - - it 'returns a 404 error if milestone id not found' do - get api("/projects/#{project.id}/milestones/1234", user) - - expect(response).to have_http_status(404) - end - end - - describe 'POST /projects/:id/milestones' do - it 'creates a new project milestone' do - post api("/projects/#{project.id}/milestones", user), title: 'new milestone' - - expect(response).to have_http_status(201) - expect(json_response['title']).to eq('new milestone') - expect(json_response['description']).to be_nil - end - - it 'creates a new project milestone with description and dates' do - post api("/projects/#{project.id}/milestones", user), - title: 'new milestone', description: 'release', due_date: '2013-03-02', start_date: '2013-02-02' - - expect(response).to have_http_status(201) - expect(json_response['description']).to eq('release') - expect(json_response['due_date']).to eq('2013-03-02') - expect(json_response['start_date']).to eq('2013-02-02') - end - - it 'returns a 400 error if title is missing' do - post api("/projects/#{project.id}/milestones", user) - - expect(response).to have_http_status(400) - end - - it 'returns a 400 error if params are invalid (duplicate title)' do - post api("/projects/#{project.id}/milestones", user), - title: milestone.title, description: 'release', due_date: '2013-03-02' - - expect(response).to have_http_status(400) - end - - it 'creates a new project with reserved html characters' do - post api("/projects/#{project.id}/milestones", user), title: 'foo & bar 1.1 -> 2.2' - - expect(response).to have_http_status(201) - expect(json_response['title']).to eq('foo & bar 1.1 -> 2.2') - expect(json_response['description']).to be_nil - end - end - - describe 'PUT /projects/:id/milestones/:milestone_id' do - it 'updates a project milestone' do - put api("/projects/#{project.id}/milestones/#{milestone.id}", user), - title: 'updated title' - - expect(response).to have_http_status(200) - expect(json_response['title']).to eq('updated title') - end - - it 'removes a due date if nil is passed' do - milestone.update!(due_date: "2016-08-05") - - put api("/projects/#{project.id}/milestones/#{milestone.id}", user), due_date: nil - - expect(response).to have_http_status(200) - expect(json_response['due_date']).to be_nil - end - - it 'returns a 404 error if milestone id not found' do - put api("/projects/#{project.id}/milestones/1234", user), - title: 'updated title' - - expect(response).to have_http_status(404) - end - end - - describe 'PUT /projects/:id/milestones/:milestone_id to close milestone' do - it 'updates a project milestone' do - put api("/projects/#{project.id}/milestones/#{milestone.id}", user), - state_event: 'close' - expect(response).to have_http_status(200) - - expect(json_response['state']).to eq('closed') - end - end - - describe 'PUT /projects/:id/milestones/:milestone_id to test observer on close' do - it 'creates an activity event when an milestone is closed' do - expect(Event).to receive(:create) - - put api("/projects/#{project.id}/milestones/#{milestone.id}", user), - state_event: 'close' - end - end - - describe 'GET /projects/:id/milestones/:milestone_id/issues' do - before do - milestone.issues << create(:issue, project: project) - end - it 'returns project issues for a particular milestone' do - get api("/projects/#{project.id}/milestones/#{milestone.id}/issues", user) - - expect(response).to have_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array - expect(json_response.first['milestone']['title']).to eq(milestone.title) - end - - it 'returns project issues sorted by label priority' do - issue_1 = create(:labeled_issue, project: project, milestone: milestone, labels: [label_3]) - issue_2 = create(:labeled_issue, project: project, milestone: milestone, labels: [label_1]) - issue_3 = create(:labeled_issue, project: project, milestone: milestone, labels: [label_2]) - - get api("/projects/#{project.id}/milestones/#{milestone.id}/issues", user) - - expect(json_response.first['id']).to eq(issue_2.id) - expect(json_response.second['id']).to eq(issue_3.id) - expect(json_response.third['id']).to eq(issue_1.id) - end - - it 'matches V4 response schema for a list of issues' do - get api("/projects/#{project.id}/milestones/#{milestone.id}/issues", user) - - expect(response).to have_http_status(200) - expect(response).to match_response_schema('public_api/v4/issues') - end - - it 'returns a 401 error if user not authenticated' do - get api("/projects/#{project.id}/milestones/#{milestone.id}/issues") - - expect(response).to have_http_status(401) - end - - describe 'confidential issues' do - let(:public_project) { create(:empty_project, :public) } - let(:milestone) { create(:milestone, project: public_project) } - let(:issue) { create(:issue, project: public_project) } - let(:confidential_issue) { create(:issue, confidential: true, project: public_project) } - - before do - public_project.team << [user, :developer] - milestone.issues << issue << confidential_issue - end - - it 'returns confidential issues to team members' do - get api("/projects/#{public_project.id}/milestones/#{milestone.id}/issues", user) - - expect(response).to have_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array - expect(json_response.size).to eq(2) - expect(json_response.map { |issue| issue['id'] }).to include(issue.id, confidential_issue.id) - end - - it 'does not return confidential issues to team members with guest role' do - member = create(:user) - project.team << [member, :guest] - - get api("/projects/#{public_project.id}/milestones/#{milestone.id}/issues", member) - - expect(response).to have_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array - expect(json_response.size).to eq(1) - expect(json_response.map { |issue| issue['id'] }).to include(issue.id) - end - - it 'does not return confidential issues to regular users' do - get api("/projects/#{public_project.id}/milestones/#{milestone.id}/issues", create(:user)) - - expect(response).to have_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array - expect(json_response.size).to eq(1) - expect(json_response.map { |issue| issue['id'] }).to include(issue.id) - end - - it 'returns issues ordered by label priority' do - issue.labels << label_2 - confidential_issue.labels << label_1 - - get api("/projects/#{public_project.id}/milestones/#{milestone.id}/issues", user) - - expect(response).to have_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array - expect(json_response.size).to eq(2) - expect(json_response.first['id']).to eq(confidential_issue.id) - expect(json_response.second['id']).to eq(issue.id) - end - end - end - - describe 'GET /projects/:id/milestones/:milestone_id/merge_requests' do - let(:merge_request) { create(:merge_request, source_project: project) } - let(:another_merge_request) { create(:merge_request, :simple, source_project: project) } - - before do - milestone.merge_requests << merge_request - end - - it 'returns project merge_requests for a particular milestone' do - # eager-load another_merge_request - another_merge_request - get api("/projects/#{project.id}/milestones/#{milestone.id}/merge_requests", user) - - expect(response).to have_http_status(200) - expect(json_response).to be_an Array - expect(json_response.size).to eq(1) - expect(json_response.first['title']).to eq(merge_request.title) - expect(json_response.first['milestone']['title']).to eq(milestone.title) - end - - it 'returns project merge_requests sorted by label priority' do - merge_request_1 = create(:labeled_merge_request, source_branch: 'branch_1', source_project: project, milestone: milestone, labels: [label_2]) - merge_request_2 = create(:labeled_merge_request, source_branch: 'branch_2', source_project: project, milestone: milestone, labels: [label_1]) - merge_request_3 = create(:labeled_merge_request, source_branch: 'branch_3', source_project: project, milestone: milestone, labels: [label_3]) - - get api("/projects/#{project.id}/milestones/#{milestone.id}/merge_requests", user) - - expect(json_response.first['id']).to eq(merge_request_2.id) - expect(json_response.second['id']).to eq(merge_request_1.id) - expect(json_response.third['id']).to eq(merge_request_3.id) - end - - it 'returns a 404 error if milestone id not found' do - get api("/projects/#{project.id}/milestones/1234/merge_requests", user) - - expect(response).to have_http_status(404) - end - - it 'returns a 404 if the user has no access to the milestone' do - new_user = create :user - get api("/projects/#{project.id}/milestones/#{milestone.id}/merge_requests", new_user) - - expect(response).to have_http_status(404) - end - - it 'returns a 401 error if user not authenticated' do - get api("/projects/#{project.id}/milestones/#{milestone.id}/merge_requests") - - expect(response).to have_http_status(401) - end - - it 'returns merge_requests ordered by position asc' do - milestone.merge_requests << another_merge_request - another_merge_request.labels << label_1 - merge_request.labels << label_2 - - get api("/projects/#{project.id}/milestones/#{milestone.id}/merge_requests", user) - - expect(response).to have_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array - expect(json_response.size).to eq(2) - expect(json_response.first['id']).to eq(another_merge_request.id) - expect(json_response.second['id']).to eq(merge_request.id) - end - end -end diff --git a/spec/requests/api/namespaces_spec.rb b/spec/requests/api/namespaces_spec.rb index 3bf16a3ae27..26cf653ca8e 100644 --- a/spec/requests/api/namespaces_spec.rb +++ b/spec/requests/api/namespaces_spec.rb @@ -15,6 +15,20 @@ describe API::Namespaces do end context "when authenticated as admin" do + it "returns correct attributes" do + get api("/namespaces", admin) + + group_kind_json_response = json_response.find { |resource| resource['kind'] == 'group' } + user_kind_json_response = json_response.find { |resource| resource['kind'] == 'user' } + + expect(response).to have_http_status(200) + expect(response).to include_pagination_headers + expect(group_kind_json_response.keys).to contain_exactly('id', 'kind', 'name', 'path', 'full_path', + 'parent_id', 'members_count_with_descendants') + + expect(user_kind_json_response.keys).to contain_exactly('id', 'kind', 'name', 'path', 'full_path', 'parent_id') + end + it "admin: returns an array of all namespaces" do get api("/namespaces", admin) @@ -37,6 +51,27 @@ describe API::Namespaces do end context "when authenticated as a regular user" do + it "returns correct attributes when user can admin group" do + group1.add_owner(user) + + get api("/namespaces", user) + + owned_group_response = json_response.find { |resource| resource['id'] == group1.id } + + expect(owned_group_response.keys).to contain_exactly('id', 'kind', 'name', 'path', 'full_path', + 'parent_id', 'members_count_with_descendants') + end + + it "returns correct attributes when user cannot admin group" do + group1.add_guest(user) + + get api("/namespaces", user) + + guest_group_response = json_response.find { |resource| resource['id'] == group1.id } + + expect(guest_group_response.keys).to contain_exactly('id', 'kind', 'name', 'path', 'full_path', 'parent_id') + end + it "user: returns an array of namespaces" do get api("/namespaces", user) diff --git a/spec/requests/api/pipeline_schedules_spec.rb b/spec/requests/api/pipeline_schedules_spec.rb index 85d11deb26f..b34555d2815 100644 --- a/spec/requests/api/pipeline_schedules_spec.rb +++ b/spec/requests/api/pipeline_schedules_spec.rb @@ -279,6 +279,8 @@ describe API::PipelineSchedules do end context 'authenticated user with invalid permissions' do + let!(:pipeline_schedule) { create(:ci_pipeline_schedule, project: project, owner: master) } + it 'does not delete pipeline_schedule' do delete api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}", developer) diff --git a/spec/requests/api/project_milestones_spec.rb b/spec/requests/api/project_milestones_spec.rb new file mode 100644 index 00000000000..fe8fdbfd7e4 --- /dev/null +++ b/spec/requests/api/project_milestones_spec.rb @@ -0,0 +1,25 @@ +require 'spec_helper' + +describe API::ProjectMilestones do + let(:user) { create(:user) } + let!(:project) { create(:empty_project, namespace: user.namespace ) } + let!(:closed_milestone) { create(:closed_milestone, project: project, title: 'version1', description: 'closed milestone') } + let!(:milestone) { create(:milestone, project: project, title: 'version2', description: 'open milestone') } + + before do + project.team << [user, :developer] + end + + it_behaves_like 'group and project milestones', "/projects/:id/milestones" do + let(:route) { "/projects/#{project.id}/milestones" } + end + + describe 'PUT /projects/:id/milestones/:milestone_id to test observer on close' do + it 'creates an activity event when an milestone is closed' do + expect(Event).to receive(:create) + + put api("/projects/#{project.id}/milestones/#{milestone.id}", user), + state_event: 'close' + end + end +end diff --git a/spec/requests/api/project_snippets_spec.rb b/spec/requests/api/project_snippets_spec.rb index 518639f45a2..f220972bae3 100644 --- a/spec/requests/api/project_snippets_spec.rb +++ b/spec/requests/api/project_snippets_spec.rb @@ -5,6 +5,26 @@ describe API::ProjectSnippets do let(:user) { create(:user) } let(:admin) { create(:admin) } + describe "GET /projects/:project_id/snippets/:id/user_agent_detail" do + let(:snippet) { create(:project_snippet, :public, project: project) } + let!(:user_agent_detail) { create(:user_agent_detail, subject: snippet) } + + it 'exposes known attributes' do + get api("/projects/#{project.id}/snippets/#{snippet.id}/user_agent_detail", admin) + + expect(response).to have_http_status(200) + expect(json_response['user_agent']).to eq(user_agent_detail.user_agent) + expect(json_response['ip_address']).to eq(user_agent_detail.ip_address) + expect(json_response['akismet_submitted']).to eq(user_agent_detail.submitted) + end + + it "returns unautorized for non-admin users" do + get api("/projects/#{snippet.project.id}/snippets/#{snippet.id}/user_agent_detail", user) + + expect(response).to have_http_status(403) + end + end + describe 'GET /projects/:project_id/snippets/' do let(:user) { create(:user) } @@ -20,7 +40,7 @@ describe API::ProjectSnippets do expect(response).to include_pagination_headers expect(json_response).to be_an Array expect(json_response.size).to eq(3) - expect(json_response.map{ |snippet| snippet['id']} ).to include(public_snippet.id, internal_snippet.id, private_snippet.id) + expect(json_response.map { |snippet| snippet['id'] }).to include(public_snippet.id, internal_snippet.id, private_snippet.id) expect(json_response.last).to have_key('web_url') end @@ -38,7 +58,7 @@ describe API::ProjectSnippets do describe 'GET /projects/:project_id/snippets/:id' do let(:user) { create(:user) } - let(:snippet) { create(:project_snippet, :public, project: project) } + let(:snippet) { create(:project_snippet, :public, project: project) } it 'returns snippet json' do get api("/projects/#{project.id}/snippets/#{snippet.id}", user) diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index fd7ff0b9cff..6ed68fcff09 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -52,6 +52,24 @@ describe API::Projects do end end + shared_examples_for 'projects response without N + 1 queries' do + it 'avoids N + 1 queries' do + control_count = ActiveRecord::QueryRecorder.new do + get api('/projects', current_user) + end.count + + if defined?(additional_project) + additional_project + else + create(:empty_project, :public) + end + + expect do + get api('/projects', current_user) + end.not_to exceed_query_limit(control_count + 8) + end + end + let!(:public_project) { create(:empty_project, :public, name: 'public_project') } before do project @@ -62,9 +80,13 @@ describe API::Projects do context 'when unauthenticated' do it_behaves_like 'projects response' do - let(:filter) { {} } + let(:filter) { { search: project.name } } + let(:current_user) { user } + let(:projects) { [project] } + end + + it_behaves_like 'projects response without N + 1 queries' do let(:current_user) { nil } - let(:projects) { [public_project] } end end @@ -75,6 +97,21 @@ describe API::Projects do let(:projects) { [public_project, project, project2, project3] } end + it_behaves_like 'projects response without N + 1 queries' do + let(:current_user) { user } + end + + context 'when some projects are in a group' do + before do + create(:empty_project, :public, group: create(:group)) + end + + it_behaves_like 'projects response without N + 1 queries' do + let(:current_user) { user } + let(:additional_project) { create(:empty_project, :public, group: create(:group)) } + end + end + it 'includes the project labels as the tag_list' do get api('/projects', user) @@ -122,6 +159,31 @@ describe API::Projects do expect(json_response.first).to include 'statistics' end + context 'when external issue tracker is enabled' do + let!(:jira_service) { create(:jira_service, project: project) } + + it 'includes open_issues_count' do + get api('/projects', user) + + expect(response.status).to eq 200 + expect(response).to include_pagination_headers + expect(json_response).to be_an Array + expect(json_response.first.keys).to include('open_issues_count') + expect(json_response.find { |hash| hash['id'] == project.id }.keys).to include('open_issues_count') + end + + it 'does not include open_issues_count if issues are disabled' do + project.project_feature.update_attribute(:issues_access_level, ProjectFeature::DISABLED) + + get api('/projects', user) + + expect(response.status).to eq 200 + expect(response).to include_pagination_headers + expect(json_response).to be_an Array + expect(json_response.find { |hash| hash['id'] == project.id }.keys).not_to include('open_issues_count') + end + end + context 'and with simple=true' do it 'returns a simplified version of all the projects' do expected_keys = %w(id http_url_to_repo web_url name name_with_namespace path path_with_namespace) @@ -347,7 +409,8 @@ describe API::Projects do wiki_enabled: false, only_allow_merge_if_pipeline_succeeds: false, request_access_enabled: true, - only_allow_merge_if_all_discussions_are_resolved: false + only_allow_merge_if_all_discussions_are_resolved: false, + ci_config_path: 'a/custom/path' }) post api('/projects', user), project @@ -404,7 +467,7 @@ describe API::Projects do post api('/projects', user), project project_id = json_response['id'] - expect(json_response['avatar_url']).to eq("http://localhost/uploads/system/project/avatar/#{project_id}/banana_sample.gif") + expect(json_response['avatar_url']).to eq("http://localhost/uploads/-/system/project/avatar/#{project_id}/banana_sample.gif") end it 'sets a project as allowing merge even if build fails' do @@ -475,6 +538,26 @@ describe API::Projects do end end + describe 'GET /users/:user_id/projects/' do + let!(:public_project) { create(:empty_project, :public, name: 'public_project', creator_id: user4.id, namespace: user4.namespace) } + + it 'returns error when user not found' do + get api('/users/9999/projects/') + + expect(response).to have_http_status(404) + expect(json_response['message']).to eq('404 User Not Found') + end + + it 'returns projects filtered by user' do + get api("/users/#{user4.id}/projects/", user) + + expect(response).to have_http_status(200) + expect(response).to include_pagination_headers + expect(json_response).to be_an Array + expect(json_response.map { |project| project['id'] }).to contain_exactly(public_project.id) + end + end + describe 'POST /projects/user/:id' do before do expect(project).to be_persisted @@ -653,6 +736,7 @@ describe API::Projects do expect(json_response['star_count']).to be_present expect(json_response['forks_count']).to be_present expect(json_response['public_jobs']).to be_present + expect(json_response['ci_config_path']).to be_nil expect(json_response['shared_with_groups']).to be_an Array expect(json_response['shared_with_groups'].length).to eq(1) expect(json_response['shared_with_groups'][0]['group_id']).to eq(group.id) @@ -684,7 +768,7 @@ describe API::Projects do dot_user = create(:user, username: 'dot.user') project = create(:empty_project, creator_id: dot_user.id, namespace: dot_user.namespace) - get api("/projects/#{dot_user.namespace.name}%2F#{project.path}", dot_user) + get api("/projects/#{CGI.escape(project.full_path)}", dot_user) expect(response).to have_http_status(200) expect(json_response['name']).to eq(project.name) end @@ -698,7 +782,8 @@ describe API::Projects do 'name' => user.namespace.name, 'path' => user.namespace.path, 'kind' => user.namespace.kind, - 'full_path' => user.namespace.full_path + 'full_path' => user.namespace.full_path, + 'parent_id' => nil }) end @@ -730,6 +815,38 @@ describe API::Projects do expect(json_response).not_to include("import_error") end + context 'links exposure' do + it 'exposes related resources full URIs' do + get api("/projects/#{project.id}", user) + + links = json_response['_links'] + + expect(links['self']).to end_with("/api/v4/projects/#{project.id}") + expect(links['issues']).to end_with("/api/v4/projects/#{project.id}/issues") + expect(links['merge_requests']).to end_with("/api/v4/projects/#{project.id}/merge_requests") + expect(links['repo_branches']).to end_with("/api/v4/projects/#{project.id}/repository/branches") + expect(links['labels']).to end_with("/api/v4/projects/#{project.id}/labels") + expect(links['events']).to end_with("/api/v4/projects/#{project.id}/events") + expect(links['members']).to end_with("/api/v4/projects/#{project.id}/members") + end + + it 'filters related URIs when their feature is not enabled' do + project = create(:empty_project, :public, + :merge_requests_disabled, + :issues_disabled, + creator_id: user.id, + namespace: user.namespace) + + get api("/projects/#{project.id}", user) + + links = json_response['_links'] + + expect(links.has_key?('merge_requests')).to be_falsy + expect(links.has_key?('issues')).to be_falsy + expect(links['self']).to end_with("/api/v4/projects/#{project.id}") + end + end + describe 'permissions' do context 'all projects' do before do diff --git a/spec/requests/api/runner_spec.rb b/spec/requests/api/runner_spec.rb index 339a57a1f20..ca5d98c78ef 100644 --- a/spec/requests/api/runner_spec.rb +++ b/spec/requests/api/runner_spec.rb @@ -351,7 +351,8 @@ describe API::Runner do let(:expected_cache) do [{ 'key' => 'cache_key', 'untracked' => false, - 'paths' => ['vendor/*'] }] + 'paths' => ['vendor/*'], + 'policy' => 'pull-push' }] end it 'picks a job' do diff --git a/spec/requests/api/settings_spec.rb b/spec/requests/api/settings_spec.rb index ede48b1c888..c3ed5cd8ece 100644 --- a/spec/requests/api/settings_spec.rb +++ b/spec/requests/api/settings_spec.rb @@ -10,8 +10,8 @@ describe API::Settings, 'Settings' do expect(response).to have_http_status(200) expect(json_response).to be_an Hash expect(json_response['default_projects_limit']).to eq(42) - expect(json_response['signin_enabled']).to be_truthy - expect(json_response['repository_storage']).to eq('default') + expect(json_response['password_authentication_enabled']).to be_truthy + expect(json_response['repository_storages']).to eq(['default']) expect(json_response['koding_enabled']).to be_falsey expect(json_response['koding_url']).to be_nil expect(json_response['plantuml_enabled']).to be_falsey @@ -32,8 +32,8 @@ describe API::Settings, 'Settings' do it "updates application settings" do put api("/application/settings", admin), default_projects_limit: 3, - signin_enabled: false, - repository_storage: 'custom', + password_authentication_enabled: false, + repository_storages: ['custom'], koding_enabled: true, koding_url: 'http://koding.example.com', plantuml_enabled: true, @@ -46,8 +46,7 @@ describe API::Settings, 'Settings' do help_page_support_url: 'http://example.com/help' expect(response).to have_http_status(200) expect(json_response['default_projects_limit']).to eq(3) - expect(json_response['signin_enabled']).to be_falsey - expect(json_response['repository_storage']).to eq('custom') + expect(json_response['password_authentication_enabled']).to be_falsey expect(json_response['repository_storages']).to eq(['custom']) expect(json_response['koding_enabled']).to be_truthy expect(json_response['koding_url']).to eq('http://koding.example.com') diff --git a/spec/requests/api/snippets_spec.rb b/spec/requests/api/snippets_spec.rb index b20a187acfe..373fab4d98a 100644 --- a/spec/requests/api/snippets_spec.rb +++ b/spec/requests/api/snippets_spec.rb @@ -271,4 +271,25 @@ describe API::Snippets do expect(json_response['message']).to eq('404 Snippet Not Found') end end + + describe "GET /snippets/:id/user_agent_detail" do + let(:admin) { create(:admin) } + let(:snippet) { create(:personal_snippet, :public, author: user) } + let!(:user_agent_detail) { create(:user_agent_detail, subject: snippet) } + + it 'exposes known attributes' do + get api("/snippets/#{snippet.id}/user_agent_detail", admin) + + expect(response).to have_http_status(200) + expect(json_response['user_agent']).to eq(user_agent_detail.user_agent) + expect(json_response['ip_address']).to eq(user_agent_detail.ip_address) + expect(json_response['akismet_submitted']).to eq(user_agent_detail.submitted) + end + + it "returns unautorized for non-admin users" do + get api("/snippets/#{snippet.id}/user_agent_detail", user) + + expect(response).to have_http_status(403) + end + end end diff --git a/spec/requests/api/todos_spec.rb b/spec/requests/api/todos_spec.rb index 92533f4dfea..9fc73c6e092 100644 --- a/spec/requests/api/todos_spec.rb +++ b/spec/requests/api/todos_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe API::Todos do - let(:project_1) { create(:empty_project, :test_repo) } + let(:project_1) { create(:project) } let(:project_2) { create(:empty_project) } let(:author_1) { create(:user) } let(:author_2) { create(:user) } diff --git a/spec/requests/api/triggers_spec.rb b/spec/requests/api/triggers_spec.rb index 16ddade27d9..153596c2975 100644 --- a/spec/requests/api/triggers_spec.rb +++ b/spec/requests/api/triggers_spec.rb @@ -22,6 +22,7 @@ describe API::Triggers do before do stub_ci_pipeline_to_return_yaml_file + trigger.update(owner: user) end context 'Handles errors' do @@ -36,12 +37,6 @@ describe API::Triggers do expect(response).to have_http_status(404) end - - it 'returns unauthorized if token is for different project' do - post api("/projects/#{project2.id}/trigger/pipeline"), options.merge(ref: 'master') - - expect(response).to have_http_status(401) - end end context 'Have a commit' do @@ -61,7 +56,7 @@ describe API::Triggers do post api("/projects/#{project.id}/trigger/pipeline"), options.merge(ref: 'other-branch') expect(response).to have_http_status(400) - expect(json_response['message']).to eq('No pipeline created') + expect(json_response['message']).to eq('base' => ["Reference not found"]) end context 'Validates variables' do @@ -87,12 +82,18 @@ describe API::Triggers do post api("/projects/#{project.id}/trigger/pipeline"), options.merge(variables: variables, ref: 'master') expect(response).to have_http_status(201) - expect(pipeline.builds.reload.first.trigger_request.variables).to eq(variables) + expect(pipeline.variables.map { |v| { v.key => v.value } }.last).to eq(variables) end end end context 'when triggering a pipeline from a trigger token' do + it 'does not leak the presence of project when token is for different project' do + post api("/projects/#{project2.id}/ref/master/trigger/pipeline?token=#{trigger_token}"), { ref: 'refs/heads/other-branch' } + + expect(response).to have_http_status(404) + end + it 'creates builds from the ref given in the URL, not in the body' do expect do post api("/projects/#{project.id}/ref/master/trigger/pipeline?token=#{trigger_token}"), { ref: 'refs/heads/other-branch' } diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb index 18000d91795..66b165b438b 100644 --- a/spec/requests/api/users_spec.rb +++ b/spec/requests/api/users_spec.rb @@ -13,9 +13,40 @@ describe API::Users do describe 'GET /users' do context "when unauthenticated" do - it "returns authentication error" do + it "returns authorization error when the `username` parameter is not passed" do get api("/users") - expect(response).to have_http_status(401) + + expect(response).to have_http_status(403) + end + + it "returns the user when a valid `username` parameter is passed" do + user = create(:user) + + get api("/users"), username: user.username + + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(json_response.size).to eq(1) + expect(json_response[0]['id']).to eq(user.id) + expect(json_response[0]['username']).to eq(user.username) + end + + it "returns authorization error when the `username` parameter refers to an inaccessible user" do + user = create(:user) + + stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC]) + + get api("/users"), username: user.username + + expect(response).to have_http_status(403) + end + + it "returns an empty response when an invalid `username` parameter is passed" do + get api("/users"), username: 'invalid' + + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(json_response.size).to eq(0) end end @@ -24,17 +55,22 @@ describe API::Users do context "when public level is restricted" do before do stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC]) - allow_any_instance_of(API::Helpers).to receive(:authenticate!).and_return(true) end - it "renders 403" do - get api("/users") - expect(response).to have_http_status(403) + context 'when authenticate as a regular user' do + it "renders 403" do + get api("/users", user) + + expect(response).to have_gitlab_http_status(403) + end end - it "renders 404" do - get api("/users/#{user.id}") - expect(response).to have_http_status(404) + context 'when authenticate as an admin' do + it "renders 200" do + get api("/users", admin) + + expect(response).to have_gitlab_http_status(200) + end end end @@ -132,12 +168,42 @@ describe API::Users do expect(response).to have_http_status(400) end + + it "returns a user created before a specific date" do + user = create(:user, created_at: Date.new(2000, 1, 1)) + + get api("/users?created_before=2000-01-02T00:00:00.060Z", admin) + + expect(response).to have_http_status(200) + expect(json_response.size).to eq(1) + expect(json_response.first['username']).to eq(user.username) + end + + it "returns no users created before a specific date" do + create(:user, created_at: Date.new(2001, 1, 1)) + + get api("/users?created_before=2000-01-02T00:00:00.060Z", admin) + + expect(response).to have_http_status(200) + expect(json_response.size).to eq(0) + end + + it "returns users created before and after a specific date" do + user = create(:user, created_at: Date.new(2001, 1, 1)) + + get api("/users?created_before=2001-01-02T00:00:00.060Z&created_after=1999-01-02T00:00:00.060", admin) + + expect(response).to have_http_status(200) + expect(json_response.size).to eq(1) + expect(json_response.first['username']).to eq(user.username) + end end end describe "GET /users/:id" do it "returns a user by id" do get api("/users/#{user.id}", user) + expect(response).to have_http_status(200) expect(json_response['username']).to eq(user.username) end @@ -148,9 +214,22 @@ describe API::Users do expect(json_response['is_admin']).to be_nil end - it "returns a 401 if unauthenticated" do - get api("/users/9998") - expect(response).to have_http_status(401) + context 'for an anonymous user' do + it "returns a user by id" do + get api("/users/#{user.id}") + + expect(response).to have_http_status(200) + expect(json_response['username']).to eq(user.username) + end + + it "returns a 404 if the target user is present but inaccessible" do + allow(Ability).to receive(:allowed?).and_call_original + allow(Ability).to receive(:allowed?).with(nil, :read_user, user).and_return(false) + + get api("/users/#{user.id}") + + expect(response).to have_http_status(404) + end end it "returns a 404 error if user id not found" do @@ -345,6 +424,14 @@ describe API::Users do expect(json_response['identities'].first['provider']).to eq('github') end end + + context "scopes" do + let(:user) { admin } + let(:path) { '/users' } + let(:api_call) { method(:api) } + + include_examples 'does not allow the "read_user" scope' + end end describe "GET /users/sign_up" do @@ -364,6 +451,7 @@ describe API::Users do it "updates user with new bio" do put api("/users/#{user.id}", admin), { bio: 'new test bio' } + expect(response).to have_http_status(200) expect(json_response['bio']).to eq('new test bio') expect(user.reload.bio).to eq('new test bio') @@ -396,13 +484,22 @@ describe API::Users do it 'updates user with his own email' do put api("/users/#{user.id}", admin), email: user.email + expect(response).to have_http_status(200) expect(json_response['email']).to eq(user.email) expect(user.reload.email).to eq(user.email) end + it 'updates user with a new email' do + put api("/users/#{user.id}", admin), email: 'new@email.com' + + expect(response).to have_http_status(200) + expect(user.reload.notification_email).to eq('new@email.com') + end + it 'updates user with his own username' do put api("/users/#{user.id}", admin), username: user.username + expect(response).to have_http_status(200) expect(json_response['username']).to eq(user.username) expect(user.reload.username).to eq(user.username) @@ -410,12 +507,14 @@ describe API::Users do it "updates user's existing identity" do put api("/users/#{omniauth_user.id}", admin), provider: 'ldapmain', extern_uid: '654321' + expect(response).to have_http_status(200) expect(omniauth_user.reload.identities.first.extern_uid).to eq('654321') end it 'updates user with new identity' do put api("/users/#{user.id}", admin), provider: 'github', extern_uid: 'john' + expect(response).to have_http_status(200) expect(user.reload.identities.first.extern_uid).to eq('john') expect(user.reload.identities.first.provider).to eq('github') @@ -423,12 +522,14 @@ describe API::Users do it "updates admin status" do put api("/users/#{user.id}", admin), { admin: true } + expect(response).to have_http_status(200) expect(user.reload.admin).to eq(true) end it "updates external status" do put api("/users/#{user.id}", admin), { external: true } + expect(response.status).to eq 200 expect(json_response['external']).to eq(true) expect(user.reload.external?).to be_truthy @@ -436,6 +537,7 @@ describe API::Users do it "does not update admin status" do put api("/users/#{admin_user.id}", admin), { can_create_group: false } + expect(response).to have_http_status(200) expect(admin_user.reload.admin).to eq(true) expect(admin_user.can_create_group).to eq(false) @@ -443,6 +545,7 @@ describe API::Users do it "does not allow invalid update" do put api("/users/#{user.id}", admin), { email: 'invalid email' } + expect(response).to have_http_status(400) expect(user.reload.email).not_to eq('invalid email') end @@ -459,6 +562,7 @@ describe API::Users do it "returns 404 for non-existing user" do put api("/users/999999", admin), { bio: 'update should fail' } + expect(response).to have_http_status(404) expect(json_response['message']).to eq('404 User Not Found') end @@ -509,6 +613,7 @@ describe API::Users do it 'returns 409 conflict error if email address exists' do put api("/users/#{@user.id}", admin), email: 'test@example.com' + expect(response).to have_http_status(409) expect(@user.reload.email).to eq(@user.email) end @@ -516,6 +621,7 @@ describe API::Users do it 'returns 409 conflict error if username taken' do @user_id = User.all.last.id put api("/users/#{@user.id}", admin), username: 'test' + expect(response).to have_http_status(409) expect(@user.reload.username).to eq(@user.username) end @@ -823,6 +929,13 @@ describe API::Users do expect(response).to match_response_schema('public_api/v4/user/public') expect(json_response['id']).to eq(user.id) end + + context "scopes" do + let(:path) { "/user" } + let(:api_call) { method(:api) } + + include_examples 'allows the "read_user" scope' + end end context 'with admin' do @@ -835,11 +948,11 @@ describe API::Users do expect(response).to have_http_status(403) end - it 'returns initial current user without private token when sudo not defined' do + it 'returns initial current user without private token but with is_admin when sudo not defined' do get api("/user?private_token=#{admin_personal_access_token}") expect(response).to have_http_status(200) - expect(response).to match_response_schema('public_api/v4/user/public') + expect(response).to match_response_schema('public_api/v4/user/admin') expect(json_response['id']).to eq(admin.id) end end @@ -853,11 +966,11 @@ describe API::Users do expect(json_response['id']).to eq(user.id) end - it 'returns initial current user without private token when sudo not defined' do + it 'returns initial current user without private token but with is_admin when sudo not defined' do get api("/user?private_token=#{admin.private_token}") expect(response).to have_http_status(200) - expect(response).to match_response_schema('public_api/v4/user/public') + expect(response).to match_response_schema('public_api/v4/user/admin') expect(json_response['id']).to eq(admin.id) end end @@ -892,6 +1005,13 @@ describe API::Users do expect(json_response).to be_an Array expect(json_response.first["title"]).to eq(key.title) end + + context "scopes" do + let(:path) { "/user/keys" } + let(:api_call) { method(:api) } + + include_examples 'allows the "read_user" scope' + end end end @@ -925,6 +1045,13 @@ describe API::Users do expect(response).to have_http_status(404) end + + context "scopes" do + let(:path) { "/user/keys/#{key.id}" } + let(:api_call) { method(:api) } + + include_examples 'allows the "read_user" scope' + end end describe "POST /user/keys" do @@ -1014,6 +1141,13 @@ describe API::Users do expect(json_response).to be_an Array expect(json_response.first["email"]).to eq(email.email) end + + context "scopes" do + let(:path) { "/user/emails" } + let(:api_call) { method(:api) } + + include_examples 'allows the "read_user" scope' + end end end @@ -1046,6 +1180,13 @@ describe API::Users do expect(response).to have_http_status(404) end + + context "scopes" do + let(:path) { "/user/emails/#{email.id}" } + let(:api_call) { method(:api) } + + include_examples 'allows the "read_user" scope' + end end describe "POST /user/emails" do @@ -1177,7 +1318,7 @@ describe API::Users do end end - context "user activities", :redis do + context "user activities", :clean_gitlab_redis_shared_state do let!(:old_active_user) { create(:user, last_activity_on: Time.utc(2000, 1, 1)) } let!(:newly_active_user) { create(:user, last_activity_on: 2.days.ago.midday) } diff --git a/spec/requests/api/v3/groups_spec.rb b/spec/requests/api/v3/groups_spec.rb index 63c5707b2e4..5cdc528e190 100644 --- a/spec/requests/api/v3/groups_spec.rb +++ b/spec/requests/api/v3/groups_spec.rb @@ -502,7 +502,7 @@ describe API::V3::Groups do describe "POST /groups/:id/projects/:project_id" do let(:project) { create(:empty_project) } - let(:project_path) { "#{project.namespace.path}%2F#{project.path}" } + let(:project_path) { CGI.escape(project.full_path) } before(:each) do allow_any_instance_of(Projects::TransferService) diff --git a/spec/requests/api/v3/issues_spec.rb b/spec/requests/api/v3/issues_spec.rb index cc81922697a..4dff09b6df8 100644 --- a/spec/requests/api/v3/issues_spec.rb +++ b/spec/requests/api/v3/issues_spec.rb @@ -1114,7 +1114,7 @@ describe API::V3::Issues do put v3_api("/projects/#{project.id}/issues/#{closed_issue.id}", user), state_event: 'reopen' expect(response).to have_http_status(200) - expect(json_response['state']).to eq 'reopened' + expect(json_response['state']).to eq 'opened' end context 'when an admin or owner makes the request' do diff --git a/spec/requests/api/v3/project_hooks_spec.rb b/spec/requests/api/v3/project_hooks_spec.rb index 1969d1c7f2b..b0eddbb5dd2 100644 --- a/spec/requests/api/v3/project_hooks_spec.rb +++ b/spec/requests/api/v3/project_hooks_spec.rb @@ -87,7 +87,7 @@ describe API::ProjectHooks, 'ProjectHooks' do it "adds hook to project" do expect do post v3_api("/projects/#{project.id}/hooks", user), - url: "http://example.com", issues_events: true, wiki_page_events: true + url: "http://example.com", issues_events: true, wiki_page_events: true, build_events: true end.to change {project.hooks.count}.by(1) expect(response).to have_http_status(201) @@ -97,7 +97,7 @@ describe API::ProjectHooks, 'ProjectHooks' do expect(json_response['merge_requests_events']).to eq(false) expect(json_response['tag_push_events']).to eq(false) expect(json_response['note_events']).to eq(false) - expect(json_response['build_events']).to eq(false) + expect(json_response['build_events']).to eq(true) expect(json_response['pipeline_events']).to eq(false) expect(json_response['wiki_page_events']).to eq(true) expect(json_response['enable_ssl_verification']).to eq(true) @@ -135,7 +135,7 @@ describe API::ProjectHooks, 'ProjectHooks' do describe "PUT /projects/:id/hooks/:hook_id" do it "updates an existing project hook" do put v3_api("/projects/#{project.id}/hooks/#{hook.id}", user), - url: 'http://example.org', push_events: false + url: 'http://example.org', push_events: false, build_events: true expect(response).to have_http_status(200) expect(json_response['url']).to eq('http://example.org') expect(json_response['issues_events']).to eq(hook.issues_events) diff --git a/spec/requests/api/v3/projects_spec.rb b/spec/requests/api/v3/projects_spec.rb index af4b6f92b64..bbfcaab1ea1 100644 --- a/spec/requests/api/v3/projects_spec.rb +++ b/spec/requests/api/v3/projects_spec.rb @@ -124,6 +124,36 @@ describe API::V3::Projects do end end + context 'and using archived' do + let!(:archived_project) { create(:empty_project, creator_id: user.id, namespace: user.namespace, archived: true) } + + it 'returns archived project' do + get v3_api('/projects?archived=true', user) + + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(json_response.length).to eq(1) + expect(json_response.first['id']).to eq(archived_project.id) + end + + it 'returns non-archived project' do + get v3_api('/projects?archived=false', user) + + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(json_response.length).to eq(1) + expect(json_response.first['id']).to eq(project.id) + end + + it 'returns all project' do + get v3_api('/projects', user) + + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(json_response.length).to eq(2) + end + end + context 'and using sorting' do before do project2 @@ -690,7 +720,7 @@ describe API::V3::Projects do dot_user = create(:user, username: 'dot.user') project = create(:empty_project, creator_id: dot_user.id, namespace: dot_user.namespace) - get v3_api("/projects/#{dot_user.namespace.name}%2F#{project.path}", dot_user) + get v3_api("/projects/#{CGI.escape(project.full_path)}", dot_user) expect(response).to have_http_status(200) expect(json_response['name']).to eq(project.name) end @@ -704,7 +734,8 @@ describe API::V3::Projects do 'name' => user.namespace.name, 'path' => user.namespace.path, 'kind' => user.namespace.kind, - 'full_path' => user.namespace.full_path + 'full_path' => user.namespace.full_path, + 'parent_id' => nil }) end diff --git a/spec/requests/api/v3/settings_spec.rb b/spec/requests/api/v3/settings_spec.rb index 41d039b7da0..291f6dcc2aa 100644 --- a/spec/requests/api/v3/settings_spec.rb +++ b/spec/requests/api/v3/settings_spec.rb @@ -10,7 +10,7 @@ describe API::V3::Settings, 'Settings' do expect(response).to have_http_status(200) expect(json_response).to be_an Hash expect(json_response['default_projects_limit']).to eq(42) - expect(json_response['signin_enabled']).to be_truthy + expect(json_response['password_authentication_enabled']).to be_truthy expect(json_response['repository_storage']).to eq('default') expect(json_response['koding_enabled']).to be_falsey expect(json_response['koding_url']).to be_nil @@ -28,11 +28,11 @@ describe API::V3::Settings, 'Settings' do it "updates application settings" do put v3_api("/application/settings", admin), - default_projects_limit: 3, signin_enabled: false, repository_storage: 'custom', koding_enabled: true, koding_url: 'http://koding.example.com', + default_projects_limit: 3, password_authentication_enabled: false, repository_storage: 'custom', koding_enabled: true, koding_url: 'http://koding.example.com', plantuml_enabled: true, plantuml_url: 'http://plantuml.example.com' expect(response).to have_http_status(200) expect(json_response['default_projects_limit']).to eq(3) - expect(json_response['signin_enabled']).to be_falsey + expect(json_response['password_authentication_enabled']).to be_falsey expect(json_response['repository_storage']).to eq('custom') expect(json_response['repository_storages']).to eq(['custom']) expect(json_response['koding_enabled']).to be_truthy diff --git a/spec/requests/api/v3/triggers_spec.rb b/spec/requests/api/v3/triggers_spec.rb index d3de6bf13bc..60212660fb6 100644 --- a/spec/requests/api/v3/triggers_spec.rb +++ b/spec/requests/api/v3/triggers_spec.rb @@ -52,7 +52,8 @@ describe API::V3::Triggers do it 'returns bad request with no builds created if there\'s no commit for that ref' do post v3_api("/projects/#{project.id}/trigger/builds"), options.merge(ref: 'other-branch') expect(response).to have_http_status(400) - expect(json_response['message']).to eq('No builds created') + expect(json_response['message']['base']) + .to contain_exactly('Reference not found') end context 'Validates variables' do diff --git a/spec/requests/api/v3/users_spec.rb b/spec/requests/api/v3/users_spec.rb index 6d7401f9764..de7499a4e43 100644 --- a/spec/requests/api/v3/users_spec.rb +++ b/spec/requests/api/v3/users_spec.rb @@ -67,6 +67,19 @@ describe API::V3::Users do expect(json_response.first['title']).to eq(key.title) end end + + context "scopes" do + let(:user) { admin } + let(:path) { "/users/#{user.id}/keys" } + let(:api_call) { method(:v3_api) } + + before do + user.keys << key + user.save + end + + include_examples 'allows the "read_user" scope' + end end describe 'GET /user/:id/emails' do @@ -287,7 +300,7 @@ describe API::V3::Users do end it 'returns a 404 error if not found' do - get v3_api('/users/42/events', user) + get v3_api('/users/420/events', user) expect(response).to have_http_status(404) expect(json_response['message']).to eq('404 User Not Found') @@ -312,5 +325,13 @@ describe API::V3::Users do expect(json_response['is_admin']).to be_nil end + + context "scopes" do + let(:user) { admin } + let(:path) { '/users' } + let(:api_call) { method(:v3_api) } + + include_examples 'does not allow the "read_user" scope' + end end end diff --git a/spec/requests/api/variables_spec.rb b/spec/requests/api/variables_spec.rb index 83673864fe7..e0975024b80 100644 --- a/spec/requests/api/variables_spec.rb +++ b/spec/requests/api/variables_spec.rb @@ -82,6 +82,17 @@ describe API::Variables do expect(json_response['protected']).to be_truthy end + it 'creates variable with optional attributes' do + expect do + post api("/projects/#{project.id}/variables", user), key: 'TEST_VARIABLE_2', value: 'VALUE_2' + end.to change{project.variables.count}.by(1) + + expect(response).to have_http_status(201) + expect(json_response['key']).to eq('TEST_VARIABLE_2') + expect(json_response['value']).to eq('VALUE_2') + expect(json_response['protected']).to be_falsey + end + it 'does not allow to duplicate variable key' do expect do post api("/projects/#{project.id}/variables", user), key: variable.key, value: 'VALUE_2' diff --git a/spec/requests/api/version_spec.rb b/spec/requests/api/version_spec.rb index 8870d48bbc9..7bbf34422b8 100644 --- a/spec/requests/api/version_spec.rb +++ b/spec/requests/api/version_spec.rb @@ -6,7 +6,7 @@ describe API::Version do it 'returns authentication error' do get api('/version') - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end end @@ -16,7 +16,7 @@ describe API::Version do it 'returns the version information' do get api('/version', user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['version']).to eq(Gitlab::VERSION) expect(json_response['revision']).to eq(Gitlab::REVISION) end |