diff options
author | Kamil Trzcinski <ayufan@ayufan.eu> | 2016-12-18 23:39:51 +0100 |
---|---|---|
committer | Kamil Trzcinski <ayufan@ayufan.eu> | 2016-12-18 23:39:51 +0100 |
commit | 14d47884dff6844625c2e65b247fd773d78f5ea2 (patch) | |
tree | 10fba027e61521df236fd6eec7ba829c5fe2c8ec /spec/requests/api | |
parent | 9fd775def2d89500cf291fe675458b68ead7cd2c (diff) | |
parent | 546fa165ff728bc2d25ed9b55b95dd1d48139d4a (diff) | |
download | gitlab-ce-dockerfile-templates.tar.gz |
Merge remote-tracking branch 'origin/master' into dockerfile-templatesdockerfile-templates
Diffstat (limited to 'spec/requests/api')
42 files changed, 1870 insertions, 656 deletions
diff --git a/spec/requests/api/access_requests_spec.rb b/spec/requests/api/access_requests_spec.rb index b467890a403..1a771b3c87a 100644 --- a/spec/requests/api/access_requests_spec.rb +++ b/spec/requests/api/access_requests_spec.rb @@ -9,19 +9,19 @@ describe API::AccessRequests, api: true do let(:stranger) { create(:user) } let(:project) do - project = create(:project, :public, creator_id: master.id, namespace: master.namespace) - project.team << [developer, :developer] - project.team << [master, :master] - project.request_access(access_requester) - project + create(:project, :public, :access_requestable, creator_id: master.id, namespace: master.namespace) do |project| + project.team << [developer, :developer] + project.team << [master, :master] + project.request_access(access_requester) + end end let(:group) do - group = create(:group, :public) - group.add_developer(developer) - group.add_owner(master) - group.request_access(access_requester) - group + create(:group, :public, :access_requestable) do |group| + group.add_developer(developer) + group.add_owner(master) + group.request_access(access_requester) + end end shared_examples 'GET /:sources/:id/access_requests' do |source_type| @@ -89,7 +89,7 @@ describe API::AccessRequests, api: true do context 'when authenticated as a stranger' do context "when access request is disabled for the #{source_type}" do before do - source.update(request_access_enabled: false) + source.update_attributes(request_access_enabled: false) end it 'returns 403' do diff --git a/spec/requests/api/api_helpers_spec.rb b/spec/requests/api/api_helpers_spec.rb deleted file mode 100644 index 01bb9e955e0..00000000000 --- a/spec/requests/api/api_helpers_spec.rb +++ /dev/null @@ -1,293 +0,0 @@ -require 'spec_helper' - -describe API::Helpers, api: true do - include API::Helpers - include ApiHelpers - include SentryHelper - - let(:user) { create(:user) } - let(:admin) { create(:admin) } - let(:key) { create(:key, user: user) } - - let(:params) { {} } - let(:env) { { 'REQUEST_METHOD' => 'GET' } } - let(:request) { Rack::Request.new(env) } - - def set_env(token_usr, identifier) - clear_env - clear_param - env[API::Helpers::PRIVATE_TOKEN_HEADER] = token_usr.private_token - env[API::Helpers::SUDO_HEADER] = identifier - end - - def set_param(token_usr, identifier) - clear_env - clear_param - params[API::Helpers::PRIVATE_TOKEN_PARAM] = token_usr.private_token - params[API::Helpers::SUDO_PARAM] = identifier - end - - def clear_env - env.delete(API::Helpers::PRIVATE_TOKEN_HEADER) - env.delete(API::Helpers::SUDO_HEADER) - end - - def clear_param - params.delete(API::Helpers::PRIVATE_TOKEN_PARAM) - params.delete(API::Helpers::SUDO_PARAM) - end - - def warden_authenticate_returns(value) - warden = double("warden", authenticate: value) - env['warden'] = warden - end - - def doorkeeper_guard_returns(value) - allow_any_instance_of(self.class).to receive(:doorkeeper_guard){ value } - end - - def error!(message, status) - raise Exception - end - - describe ".current_user" do - subject { current_user } - - describe "Warden authentication" do - before { doorkeeper_guard_returns false } - - context "with invalid credentials" do - context "GET request" do - before { env['REQUEST_METHOD'] = 'GET' } - it { is_expected.to be_nil } - end - end - - context "with valid credentials" do - before { warden_authenticate_returns user } - - context "GET request" do - before { env['REQUEST_METHOD'] = 'GET' } - it { is_expected.to eq(user) } - end - - context "HEAD request" do - before { env['REQUEST_METHOD'] = 'HEAD' } - it { is_expected.to eq(user) } - end - - context "PUT request" do - before { env['REQUEST_METHOD'] = 'PUT' } - it { is_expected.to be_nil } - end - - context "POST request" do - before { env['REQUEST_METHOD'] = 'POST' } - it { is_expected.to be_nil } - end - - context "DELETE request" do - before { env['REQUEST_METHOD'] = 'DELETE' } - it { is_expected.to be_nil } - end - end - end - - describe "when authenticating using a user's private token" do - it "returns nil for an invalid token" do - env[API::Helpers::PRIVATE_TOKEN_HEADER] = 'invalid token' - allow_any_instance_of(self.class).to receive(:doorkeeper_guard){ false } - expect(current_user).to be_nil - end - - it "returns nil for a user without access" do - env[API::Helpers::PRIVATE_TOKEN_HEADER] = user.private_token - allow_any_instance_of(Gitlab::UserAccess).to receive(:allowed?).and_return(false) - expect(current_user).to be_nil - end - - it "leaves user as is when sudo not specified" do - env[API::Helpers::PRIVATE_TOKEN_HEADER] = user.private_token - expect(current_user).to eq(user) - clear_env - params[API::Helpers::PRIVATE_TOKEN_PARAM] = user.private_token - expect(current_user).to eq(user) - end - end - - describe "when authenticating using a user's personal access tokens" do - let(:personal_access_token) { create(:personal_access_token, user: user) } - - it "returns nil for an invalid token" do - env[API::Helpers::PRIVATE_TOKEN_HEADER] = 'invalid token' - allow_any_instance_of(self.class).to receive(:doorkeeper_guard){ false } - expect(current_user).to be_nil - end - - it "returns nil for a user without access" do - env[API::Helpers::PRIVATE_TOKEN_HEADER] = personal_access_token.token - allow_any_instance_of(Gitlab::UserAccess).to receive(:allowed?).and_return(false) - expect(current_user).to be_nil - end - - it "leaves user as is when sudo not specified" do - env[API::Helpers::PRIVATE_TOKEN_HEADER] = personal_access_token.token - expect(current_user).to eq(user) - clear_env - params[API::Helpers::PRIVATE_TOKEN_PARAM] = personal_access_token.token - expect(current_user).to eq(user) - end - - it 'does not allow revoked tokens' do - personal_access_token.revoke! - env[API::Helpers::PRIVATE_TOKEN_HEADER] = personal_access_token.token - allow_any_instance_of(self.class).to receive(:doorkeeper_guard){ false } - expect(current_user).to be_nil - end - - it 'does not allow expired tokens' do - personal_access_token.update_attributes!(expires_at: 1.day.ago) - env[API::Helpers::PRIVATE_TOKEN_HEADER] = personal_access_token.token - allow_any_instance_of(self.class).to receive(:doorkeeper_guard){ false } - expect(current_user).to be_nil - end - end - - it "changes current user to sudo when admin" do - set_env(admin, user.id) - expect(current_user).to eq(user) - set_param(admin, user.id) - expect(current_user).to eq(user) - set_env(admin, user.username) - expect(current_user).to eq(user) - set_param(admin, user.username) - expect(current_user).to eq(user) - end - - it "throws an error when the current user is not an admin and attempting to sudo" do - set_env(user, admin.id) - expect { current_user }.to raise_error(Exception) - set_param(user, admin.id) - expect { current_user }.to raise_error(Exception) - set_env(user, admin.username) - expect { current_user }.to raise_error(Exception) - set_param(user, admin.username) - expect { current_user }.to raise_error(Exception) - end - - it "throws an error when the user cannot be found for a given id" do - id = user.id + admin.id - expect(user.id).not_to eq(id) - expect(admin.id).not_to eq(id) - set_env(admin, id) - expect { current_user }.to raise_error(Exception) - - set_param(admin, id) - expect { current_user }.to raise_error(Exception) - end - - it "throws an error when the user cannot be found for a given username" do - username = "#{user.username}#{admin.username}" - expect(user.username).not_to eq(username) - expect(admin.username).not_to eq(username) - set_env(admin, username) - expect { current_user }.to raise_error(Exception) - - set_param(admin, username) - expect { current_user }.to raise_error(Exception) - end - - it "handles sudo's to oneself" do - set_env(admin, admin.id) - expect(current_user).to eq(admin) - set_param(admin, admin.id) - expect(current_user).to eq(admin) - set_env(admin, admin.username) - expect(current_user).to eq(admin) - set_param(admin, admin.username) - expect(current_user).to eq(admin) - end - - it "handles multiple sudo's to oneself" do - set_env(admin, user.id) - expect(current_user).to eq(user) - expect(current_user).to eq(user) - set_env(admin, user.username) - expect(current_user).to eq(user) - expect(current_user).to eq(user) - - set_param(admin, user.id) - expect(current_user).to eq(user) - expect(current_user).to eq(user) - set_param(admin, user.username) - expect(current_user).to eq(user) - expect(current_user).to eq(user) - end - - it "handles multiple sudo's to oneself using string ids" do - set_env(admin, user.id.to_s) - expect(current_user).to eq(user) - expect(current_user).to eq(user) - - set_param(admin, user.id.to_s) - expect(current_user).to eq(user) - expect(current_user).to eq(user) - end - end - - describe '.sudo_identifier' do - it "returns integers when input is an int" do - set_env(admin, '123') - expect(sudo_identifier).to eq(123) - set_env(admin, '0001234567890') - expect(sudo_identifier).to eq(1234567890) - - set_param(admin, '123') - expect(sudo_identifier).to eq(123) - set_param(admin, '0001234567890') - expect(sudo_identifier).to eq(1234567890) - end - - it "returns string when input is an is not an int" do - set_env(admin, '12.30') - expect(sudo_identifier).to eq("12.30") - set_env(admin, 'hello') - expect(sudo_identifier).to eq('hello') - set_env(admin, ' 123') - expect(sudo_identifier).to eq(' 123') - - set_param(admin, '12.30') - expect(sudo_identifier).to eq("12.30") - set_param(admin, 'hello') - expect(sudo_identifier).to eq('hello') - set_param(admin, ' 123') - expect(sudo_identifier).to eq(' 123') - end - end - - describe '.handle_api_exception' do - before do - allow_any_instance_of(self.class).to receive(:sentry_enabled?).and_return(true) - allow_any_instance_of(self.class).to receive(:rack_response) - end - - it 'does not report a MethodNotAllowed exception to Sentry' do - exception = Grape::Exceptions::MethodNotAllowed.new({ 'X-GitLab-Test' => '1' }) - allow(exception).to receive(:backtrace).and_return(caller) - - expect(Raven).not_to receive(:capture_exception).with(exception) - - handle_api_exception(exception) - end - - it 'does report RuntimeError to Sentry' do - exception = RuntimeError.new('test error') - allow(exception).to receive(:backtrace).and_return(caller) - - expect_any_instance_of(self.class).to receive(:sentry_context) - expect(Raven).to receive(:capture_exception).with(exception) - - handle_api_exception(exception) - end - end -end diff --git a/spec/requests/api/api_internal_helpers_spec.rb b/spec/requests/api/api_internal_helpers_spec.rb new file mode 100644 index 00000000000..be4bc39ada2 --- /dev/null +++ b/spec/requests/api/api_internal_helpers_spec.rb @@ -0,0 +1,32 @@ +require 'spec_helper' + +describe ::API::Helpers::InternalHelpers do + include ::API::Helpers::InternalHelpers + + describe '.clean_project_path' do + project = 'namespace/project' + namespaced = File.join('namespace2', project) + + { + File.join(Dir.pwd, project) => project, + File.join(Dir.pwd, namespaced) => namespaced, + project => project, + namespaced => namespaced, + project + '.git' => project, + namespaced + '.git' => namespaced, + "/" + project => project, + "/" + namespaced => namespaced, + }.each do |project_path, expected| + context project_path do + # Relative and absolute storage paths, with and without trailing / + ['.', './', Dir.pwd, Dir.pwd + '/'].each do |storage_path| + context "storage path is #{storage_path}" do + subject { clean_project_path(project_path, [storage_path]) } + + it { is_expected.to eq(expected) } + end + end + end + end + end +end diff --git a/spec/requests/api/award_emoji_spec.rb b/spec/requests/api/award_emoji_spec.rb index 5ad4fc4865a..c8e8f31cc1f 100644 --- a/spec/requests/api/award_emoji_spec.rb +++ b/spec/requests/api/award_emoji_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe API::API, api: true do +describe API::AwardEmoji, api: true do include ApiHelpers let(:user) { create(:user) } let!(:project) { create(:empty_project) } diff --git a/spec/requests/api/boards_spec.rb b/spec/requests/api/boards_spec.rb index 4f5c09a3029..3019724f52e 100644 --- a/spec/requests/api/boards_spec.rb +++ b/spec/requests/api/boards_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe API::API, api: true do +describe API::Boards, api: true do include ApiHelpers let(:user) { create(:user) } diff --git a/spec/requests/api/branches_spec.rb b/spec/requests/api/branches_spec.rb index 1711096f4bd..2878e0cb59b 100644 --- a/spec/requests/api/branches_spec.rb +++ b/spec/requests/api/branches_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' require 'mime/types' -describe API::API, api: true do +describe API::Branches, api: true do include ApiHelpers let(:user) { create(:user) } @@ -11,10 +11,11 @@ describe API::API, api: true do let!(:guest) { create(:project_member, :guest, user: user2, project: project) } let!(:branch_name) { 'feature' } let!(:branch_sha) { '0b4bc9a49b562e85de7cc9e834518ea6828729b9' } + let!(:branch_with_dot) { CreateBranchService.new(project, user).execute("with.1.2.3", "master") } describe "GET /projects/:id/repository/branches" do it "returns an array of project branches" do - project.repository.expire_cache + project.repository.expire_all_method_caches get api("/projects/#{project.id}/repository/branches", user) expect(response).to have_http_status(200) @@ -31,11 +32,29 @@ describe API::API, api: true do expect(json_response['name']).to eq(branch_name) expect(json_response['commit']['id']).to eq(branch_sha) + expect(json_response['merged']).to eq(false) 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) end + it "returns the branch information for a single branch with dots in the name" do + get api("/projects/#{project.id}/repository/branches/with.1.2.3", user) + + expect(response).to have_http_status(200) + expect(json_response['name']).to eq("with.1.2.3") + end + + context 'on a merged branch' do + it "returns the branch information for a single branch" do + get api("/projects/#{project.id}/repository/branches/merge-test", user) + + expect(response).to have_http_status(200) + expect(json_response['name']).to eq('merge-test') + expect(json_response['merged']).to eq(true) + end + end + it "returns a 403 error if guest" do get api("/projects/#{project.id}/repository/branches", user2) expect(response).to have_http_status(403) @@ -60,6 +79,14 @@ describe API::API, api: true do 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/with.1.2.3/protect", user) + + expect(response).to have_http_status(200) + expect(json_response['name']).to eq("with.1.2.3") + 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 @@ -209,6 +236,14 @@ describe API::API, api: true do expect(json_response['protected']).to eq(false) end + it "update branches with dots in branch name" do + put api("/projects/#{project.id}/repository/branches/with.1.2.3/unprotect", user) + + expect(response).to have_http_status(200) + expect(json_response['name']).to eq("with.1.2.3") + expect(json_response['protected']).to eq(false) + 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) @@ -281,6 +316,13 @@ describe API::API, api: true do expect(json_response['branch_name']).to eq(branch_name) end + it "removes a branch with dots in the branch name" do + delete api("/projects/#{project.id}/repository/branches/with.1.2.3", user) + + expect(response).to have_http_status(200) + expect(json_response['branch_name']).to eq("with.1.2.3") + 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) @@ -299,4 +341,20 @@ describe API::API, api: true do expect(json_response['message']).to eq('Cannot remove HEAD branch') end end + + describe "DELETE /projects/:id/repository/merged_branches" do + before do + allow_any_instance_of(Repository).to receive(:rm_branch).and_return(true) + end + + it 'returns 200' do + delete api("/projects/#{project.id}/repository/merged_branches", user) + expect(response).to have_http_status(200) + end + + it 'returns a 403 error if guest' do + delete api("/projects/#{project.id}/repository/merged_branches", user2) + expect(response).to have_http_status(403) + end + end end diff --git a/spec/requests/api/builds_spec.rb b/spec/requests/api/builds_spec.rb index fc72a44d663..7be7acebb19 100644 --- a/spec/requests/api/builds_spec.rb +++ b/spec/requests/api/builds_spec.rb @@ -1,11 +1,11 @@ require 'spec_helper' -describe API::API, api: true do +describe API::Builds, api: true do include ApiHelpers let(:user) { create(:user) } let(:api_user) { user } - let!(:project) { create(:project, creator_id: user.id) } + let!(:project) { create(:project, creator_id: user.id, public_builds: false) } let!(:developer) { create(:project_member, :developer, user: user, project: project) } let(:reporter) { create(:project_member, :reporter, project: project) } let(:guest) { create(:project_member, :guest, project: project) } diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb index a6e8550fac3..964cded917c 100644 --- a/spec/requests/api/commits_spec.rb +++ b/spec/requests/api/commits_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' require 'mime/types' -describe API::API, api: true do +describe API::Commits, api: true do include ApiHelpers let(:user) { create(:user) } let(:user2) { create(:user) } @@ -18,11 +18,14 @@ describe API::API, api: true do before { project.team << [user2, :reporter] } it "returns project commits" do + commit = project.repository.commit get api("/projects/#{project.id}/repository/commits", user) - expect(response).to have_http_status(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array - expect(json_response.first['id']).to eq(project.repository.commit.id) + expect(json_response.first['id']).to eq(commit.id) + expect(json_response.first['committer_name']).to eq(commit.committer_name) + expect(json_response.first['committer_email']).to eq(commit.committer_email) end end @@ -134,6 +137,8 @@ describe API::API, api: true do expect(response).to have_http_status(201) expect(json_response['title']).to eq(message) + expect(json_response['committer_name']).to eq(user.name) + expect(json_response['committer_email']).to eq(user.email) end it 'returns a 400 bad request if file exists' do @@ -451,6 +456,76 @@ describe API::API, api: true do end end + describe 'POST :id/repository/commits/:sha/cherry_pick' do + let(:master_pickable_commit) { project.commit('7d3b0f7cff5f37573aea97cebfd5692ea1689924') } + + context 'authorized user' do + it 'cherry picks a commit' do + post api("/projects/#{project.id}/repository/commits/#{master_pickable_commit.id}/cherry_pick", user), branch: 'master' + + expect(response).to have_http_status(201) + expect(json_response['title']).to eq(master_pickable_commit.title) + expect(json_response['message']).to eq(master_pickable_commit.message) + expect(json_response['author_name']).to eq(master_pickable_commit.author_name) + expect(json_response['committer_name']).to eq(user.name) + end + + it 'returns 400 if commit is already included in the target branch' do + post api("/projects/#{project.id}/repository/commits/#{master_pickable_commit.id}/cherry_pick", user), branch: 'markdown' + + expect(response).to have_http_status(400) + expect(json_response['message']).to eq('Sorry, we cannot cherry-pick this commit automatically. + A cherry-pick may have already been performed with this commit, or a more recent commit may have updated some of its content.') + end + + it 'returns 400 if you are not allowed to push to the target branch' do + project.team << [user2, :developer] + protected_branch = create(:protected_branch, project: project, name: 'feature') + + post api("/projects/#{project.id}/repository/commits/#{master_pickable_commit.id}/cherry_pick", user2), branch: protected_branch.name + + expect(response).to have_http_status(400) + expect(json_response['message']).to eq('You are not allowed to push into this branch') + end + + it 'returns 400 for missing parameters' do + post api("/projects/#{project.id}/repository/commits/#{master_pickable_commit.id}/cherry_pick", user) + + expect(response).to have_http_status(400) + expect(json_response['error']).to eq('branch is missing') + end + + it 'returns 404 if commit is not found' do + post api("/projects/#{project.id}/repository/commits/abcd0123/cherry_pick", user), branch: 'master' + + expect(response).to have_http_status(404) + expect(json_response['message']).to eq('404 Commit Not Found') + end + + it 'returns 404 if branch is not found' do + post api("/projects/#{project.id}/repository/commits/#{master_pickable_commit.id}/cherry_pick", user), branch: 'foo' + + expect(response).to have_http_status(404) + expect(json_response['message']).to eq('404 Branch Not Found') + end + + it 'returns 400 for missing parameters' do + post api("/projects/#{project.id}/repository/commits/#{master_pickable_commit.id}/cherry_pick", user) + + expect(response).to have_http_status(400) + expect(json_response['error']).to eq('branch is missing') + end + end + + context 'unauthorized user' do + it 'does not cherry pick the commit' do + post api("/projects/#{project.id}/repository/commits/#{master_pickable_commit.id}/cherry_pick"), branch: 'master' + + expect(response).to have_http_status(401) + end + end + end + describe 'Post comment to commit' do context 'authorized user' do it 'returns comment' do diff --git a/spec/requests/api/deploy_keys_spec.rb b/spec/requests/api/deploy_keys_spec.rb index 65897edba7f..aabab8e6ae6 100644 --- a/spec/requests/api/deploy_keys_spec.rb +++ b/spec/requests/api/deploy_keys_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe API::API, api: true do +describe API::DeployKeys, api: true do include ApiHelpers let(:user) { create(:user) } @@ -75,7 +75,6 @@ describe API::API, api: true do expect(response).to have_http_status(400) expect(json_response['message']['key']).to eq([ 'can\'t be blank', - 'is too short (minimum is 0 characters)', 'is invalid' ]) end @@ -85,8 +84,7 @@ describe API::API, api: true do expect(response).to have_http_status(400) expect(json_response['message']['title']).to eq([ - 'can\'t be blank', - 'is too short (minimum is 0 characters)' + 'can\'t be blank' ]) end diff --git a/spec/requests/api/deployments_spec.rb b/spec/requests/api/deployments_spec.rb index 8fa8c66db6c..31e3cfa1b2f 100644 --- a/spec/requests/api/deployments_spec.rb +++ b/spec/requests/api/deployments_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe API::API, api: true do +describe API::Deployments, api: true do include ApiHelpers let(:user) { create(:user) } diff --git a/spec/requests/api/doorkeeper_access_spec.rb b/spec/requests/api/doorkeeper_access_spec.rb index 5262a623761..bd9ecaf2685 100644 --- a/spec/requests/api/doorkeeper_access_spec.rb +++ b/spec/requests/api/doorkeeper_access_spec.rb @@ -5,7 +5,7 @@ describe API::API, api: true do let!(:user) { create(:user) } let!(:application) { Doorkeeper::Application.create!(name: "MyApp", redirect_uri: "https://app.com", owner: user) } - let!(:token) { Doorkeeper::AccessToken.create! application_id: application.id, resource_owner_id: user.id } + let!(:token) { Doorkeeper::AccessToken.create! application_id: application.id, resource_owner_id: user.id, scopes: "api" } describe "when unauthenticated" do it "returns authentication success" do diff --git a/spec/requests/api/environments_spec.rb b/spec/requests/api/environments_spec.rb index 1898b07835d..b9d535bc314 100644 --- a/spec/requests/api/environments_spec.rb +++ b/spec/requests/api/environments_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe API::API, api: true do +describe API::Environments, api: true do include ApiHelpers let(:user) { create(:user) } @@ -46,6 +46,7 @@ describe API::API, api: true do expect(response).to have_http_status(201) expect(json_response['name']).to eq('mepmep') + expect(json_response['slug']).to eq('mepmep') expect(json_response['external']).to be nil end @@ -60,6 +61,13 @@ describe API::API, api: true do expect(response).to have_http_status(400) end + + it 'returns a 400 if slug is specified' do + post api("/projects/#{project.id}/environments", user), name: "foo", slug: "foo" + + expect(response).to have_http_status(400) + expect(json_response["error"]).to eq("slug is automatically generated and cannot be changed") + end end context 'a non member' do @@ -86,6 +94,15 @@ describe API::API, api: true do expect(json_response['external_url']).to eq(url) end + it "won't allow slug to be changed" do + slug = environment.slug + api_url = api("/projects/#{project.id}/environments/#{environment.id}", user) + put api_url, slug: slug + "-foo" + + expect(response).to have_http_status(400) + expect(json_response["error"]).to eq("slug is automatically generated and cannot be changed") + end + it "won't update the external_url if only the name is passed" do url = environment.external_url put api("/projects/#{project.id}/environments/#{environment.id}", user), diff --git a/spec/requests/api/files_spec.rb b/spec/requests/api/files_spec.rb index 050d0dd082d..2081f80ccc1 100644 --- a/spec/requests/api/files_spec.rb +++ b/spec/requests/api/files_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe API::API, api: true do +describe API::Files, api: true do include ApiHelpers let(:user) { create(:user) } let!(:project) { create(:project, namespace: user.namespace ) } diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb index 7b47bf5afc1..cdeb965b413 100644 --- a/spec/requests/api/groups_spec.rb +++ b/spec/requests/api/groups_spec.rb @@ -1,14 +1,14 @@ require 'spec_helper' -describe API::API, api: true do +describe API::Groups, api: true do include ApiHelpers + include UploadHelpers let(:user1) { create(:user, can_create_group: false) } let(:user2) { create(:user) } let(:user3) { create(:user) } let(:admin) { create(:admin) } - let(:avatar_file_path) { File.join(Rails.root, 'spec', 'fixtures', 'banana_sample.gif') } - let!(:group1) { create(:group, avatar: File.open(avatar_file_path)) } + let!(:group1) { create(:group, avatar: File.open(uploaded_image_temp_path)) } let!(:group2) { create(:group, :private) } let!(:project1) { create(:project, namespace: group1) } let!(:project2) { create(:project, namespace: group2) } @@ -57,13 +57,66 @@ describe API::API, api: true do end context "when using all_available in request" do + let(:response_groups) { json_response.map { |group| group['name'] } } + it "returns all groups you have access to" do public_group = create :group, :public get api("/groups", user1), all_available: true expect(response).to have_http_status(200) expect(json_response).to be_an Array - expect(json_response.first['name']).to eq(public_group.name) + expect(response_groups).to contain_exactly(public_group.name, group1.name) + end + end + + context "when using sorting" do + let(:group3) { create(:group, name: "a#{group1.name}", path: "z#{group1.path}") } + let(:response_groups) { json_response.map { |group| group['name'] } } + + before do + group3.add_owner(user1) + end + + it "sorts by name ascending by default" do + get api("/groups", user1) + + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(response_groups).to eq([group3.name, group1.name]) + end + + it "sorts in descending order when passed" do + get api("/groups", user1), sort: "desc" + + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(response_groups).to eq([group1.name, group3.name]) + end + + it "sorts by the order_by param" do + get api("/groups", user1), order_by: "path" + + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(response_groups).to eq([group1.name, group3.name]) + end + end + end + + describe 'GET /groups/owned' do + context 'when unauthenticated' do + it 'returns authentication error' do + get api('/groups/owned') + expect(response).to have_http_status(401) + end + end + + context 'when authenticated as group owner' do + it 'returns an array of groups the user owns' do + get api('/groups/owned', user2) + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(json_response.first['name']).to eq(group2.name) end end end @@ -149,7 +202,7 @@ describe API::API, api: true do end it 'returns 404 for a non existing group' do - put api('/groups/1328', user1) + put api('/groups/1328', user1), name: new_group_name expect(response).to have_http_status(404) end @@ -190,6 +243,28 @@ describe API::API, api: true do expect(json_response.length).to eq(2) project_names = json_response.map { |proj| proj['name' ] } expect(project_names).to match_array([project1.name, project3.name]) + expect(json_response.first['default_branch']).to be_present + end + + it "returns the group's projects with simple representation" do + get api("/groups/#{group1.id}/projects", user1), simple: true + + expect(response).to have_http_status(200) + expect(json_response.length).to eq(2) + project_names = json_response.map { |proj| proj['name' ] } + expect(project_names).to match_array([project1.name, project3.name]) + expect(json_response.first['default_branch']).not_to be_present + end + + it 'filters the groups projects' do + public_project = create(:project, :public, path: 'test1', group: group1) + + get api("/groups/#{group1.id}/projects", user1), visibility: 'public' + + 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['name']).to eq(public_project.name) end it "does not return a non existing group" do diff --git a/spec/requests/api/helpers_spec.rb b/spec/requests/api/helpers_spec.rb new file mode 100644 index 00000000000..c3d7ac3eef8 --- /dev/null +++ b/spec/requests/api/helpers_spec.rb @@ -0,0 +1,446 @@ +require 'spec_helper' + +describe API::Helpers, api: true do + include API::APIGuard::HelperMethods + include API::Helpers + include SentryHelper + + let(:user) { create(:user) } + let(:admin) { create(:admin) } + let(:key) { create(:key, user: user) } + + let(:params) { {} } + let(:env) { { 'REQUEST_METHOD' => 'GET' } } + let(:request) { Rack::Request.new(env) } + + def set_env(user_or_token, identifier) + clear_env + clear_param + env[API::APIGuard::PRIVATE_TOKEN_HEADER] = user_or_token.respond_to?(:private_token) ? user_or_token.private_token : user_or_token + env[API::Helpers::SUDO_HEADER] = identifier.to_s + end + + def set_param(user_or_token, identifier) + clear_env + clear_param + params[API::APIGuard::PRIVATE_TOKEN_PARAM] = user_or_token.respond_to?(:private_token) ? user_or_token.private_token : user_or_token + params[API::Helpers::SUDO_PARAM] = identifier.to_s + end + + def clear_env + env.delete(API::APIGuard::PRIVATE_TOKEN_HEADER) + env.delete(API::Helpers::SUDO_HEADER) + end + + def clear_param + params.delete(API::APIGuard::PRIVATE_TOKEN_PARAM) + params.delete(API::Helpers::SUDO_PARAM) + end + + def warden_authenticate_returns(value) + warden = double("warden", authenticate: value) + env['warden'] = warden + end + + def doorkeeper_guard_returns(value) + allow_any_instance_of(self.class).to receive(:doorkeeper_guard){ value } + end + + def error!(message, status) + raise Exception.new("#{status} - #{message}") + end + + describe ".current_user" do + subject { current_user } + + describe "Warden authentication" do + before { doorkeeper_guard_returns false } + + context "with invalid credentials" do + context "GET request" do + before { env['REQUEST_METHOD'] = 'GET' } + it { is_expected.to be_nil } + end + end + + context "with valid credentials" do + before { warden_authenticate_returns user } + + context "GET request" do + before { env['REQUEST_METHOD'] = 'GET' } + it { is_expected.to eq(user) } + end + + context "HEAD request" do + before { env['REQUEST_METHOD'] = 'HEAD' } + it { is_expected.to eq(user) } + end + + context "PUT request" do + before { env['REQUEST_METHOD'] = 'PUT' } + it { is_expected.to be_nil } + end + + context "POST request" do + before { env['REQUEST_METHOD'] = 'POST' } + it { is_expected.to be_nil } + end + + context "DELETE request" do + before { env['REQUEST_METHOD'] = 'DELETE' } + it { is_expected.to be_nil } + end + end + end + + describe "when authenticating using a user's private token" do + it "returns nil for an invalid token" do + env[API::APIGuard::PRIVATE_TOKEN_HEADER] = 'invalid token' + allow_any_instance_of(self.class).to receive(:doorkeeper_guard){ false } + + expect(current_user).to be_nil + end + + it "returns nil for a user without access" do + env[API::APIGuard::PRIVATE_TOKEN_HEADER] = user.private_token + allow_any_instance_of(Gitlab::UserAccess).to receive(:allowed?).and_return(false) + + expect(current_user).to be_nil + end + + it "leaves user as is when sudo not specified" do + env[API::APIGuard::PRIVATE_TOKEN_HEADER] = user.private_token + + expect(current_user).to eq(user) + + clear_env + + params[API::APIGuard::PRIVATE_TOKEN_PARAM] = user.private_token + + expect(current_user).to eq(user) + end + end + + describe "when authenticating using a user's personal access tokens" do + let(:personal_access_token) { create(:personal_access_token, user: user) } + + before do + allow_any_instance_of(self.class).to receive(:doorkeeper_guard) { false } + end + + it "returns nil for an invalid token" do + env[API::APIGuard::PRIVATE_TOKEN_HEADER] = 'invalid token' + + expect(current_user).to be_nil + end + + it "returns nil for a user without access" do + env[API::APIGuard::PRIVATE_TOKEN_HEADER] = personal_access_token.token + allow_any_instance_of(Gitlab::UserAccess).to receive(:allowed?).and_return(false) + + expect(current_user).to be_nil + end + + 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 + + it "leaves user as is when sudo not specified" do + env[API::APIGuard::PRIVATE_TOKEN_HEADER] = personal_access_token.token + expect(current_user).to eq(user) + clear_env + params[API::APIGuard::PRIVATE_TOKEN_PARAM] = personal_access_token.token + + expect(current_user).to eq(user) + end + + it 'does not allow revoked tokens' do + personal_access_token.revoke! + env[API::APIGuard::PRIVATE_TOKEN_HEADER] = personal_access_token.token + + expect(current_user).to be_nil + end + + it 'does not allow expired tokens' do + personal_access_token.update_attributes!(expires_at: 1.day.ago) + env[API::APIGuard::PRIVATE_TOKEN_HEADER] = personal_access_token.token + + expect(current_user).to be_nil + end + end + + context 'sudo usage' do + context 'with admin' do + context 'with header' do + context 'with id' do + it 'changes current_user to sudo' do + set_env(admin, user.id) + + expect(current_user).to eq(user) + end + + it 'memoize the current_user: sudo permissions are not run against the sudoed user' do + set_env(admin, user.id) + + expect(current_user).to eq(user) + expect(current_user).to eq(user) + end + + it 'handles sudo to oneself' do + set_env(admin, admin.id) + + expect(current_user).to eq(admin) + end + + it 'throws an error when user cannot be found' do + id = user.id + admin.id + expect(user.id).not_to eq(id) + expect(admin.id).not_to eq(id) + + set_env(admin, id) + + expect { current_user }.to raise_error(Exception) + end + end + + context 'with username' do + it 'changes current_user to sudo' do + set_env(admin, user.username) + + expect(current_user).to eq(user) + end + + it 'handles sudo to oneself' do + set_env(admin, admin.username) + + expect(current_user).to eq(admin) + end + + it "throws an error when the user cannot be found for a given username" do + username = "#{user.username}#{admin.username}" + expect(user.username).not_to eq(username) + expect(admin.username).not_to eq(username) + + set_env(admin, username) + + expect { current_user }.to raise_error(Exception) + end + end + end + + context 'with param' do + context 'with id' do + it 'changes current_user to sudo' do + set_param(admin, user.id) + + expect(current_user).to eq(user) + end + + it 'handles sudo to oneself' do + set_param(admin, admin.id) + + expect(current_user).to eq(admin) + end + + it 'handles sudo to oneself using string' do + set_env(admin, user.id.to_s) + + expect(current_user).to eq(user) + end + + it 'throws an error when user cannot be found' do + id = user.id + admin.id + expect(user.id).not_to eq(id) + expect(admin.id).not_to eq(id) + + set_param(admin, id) + + expect { current_user }.to raise_error(Exception) + end + end + + context 'with username' do + it 'changes current_user to sudo' do + set_param(admin, user.username) + + expect(current_user).to eq(user) + end + + it 'handles sudo to oneself' do + set_param(admin, admin.username) + + expect(current_user).to eq(admin) + end + + it "throws an error when the user cannot be found for a given username" do + username = "#{user.username}#{admin.username}" + expect(user.username).not_to eq(username) + expect(admin.username).not_to eq(username) + + set_param(admin, username) + + expect { current_user }.to raise_error(Exception) + end + end + end + end + + context 'with regular user' do + context 'with env' do + it 'changes current_user to sudo when admin and user id' do + set_env(user, admin.id) + + expect { current_user }.to raise_error(Exception) + end + + it 'changes current_user to sudo when admin and user username' do + set_env(user, admin.username) + + expect { current_user }.to raise_error(Exception) + end + end + + context 'with params' do + it 'changes current_user to sudo when admin and user id' do + set_param(user, admin.id) + + expect { current_user }.to raise_error(Exception) + end + + it 'changes current_user to sudo when admin and user username' do + set_param(user, admin.username) + + expect { current_user }.to raise_error(Exception) + end + end + end + end + end + + describe '.sudo?' do + context 'when no sudo env or param is passed' do + before do + doorkeeper_guard_returns(nil) + end + + it 'returns false' do + expect(sudo?).to be_falsy + end + end + + context 'when sudo env or param is passed', 'user is not an admin' do + before do + set_env(user, '123') + end + + it 'returns an 403 Forbidden' do + expect { sudo? }.to raise_error '403 - {"message"=>"403 Forbidden - Must be admin to use sudo"}' + end + end + + context 'when sudo env or param is passed', 'user is admin' do + context 'personal access token is used' do + before do + personal_access_token = create(:personal_access_token, user: admin) + set_env(personal_access_token.token, user.id) + end + + it 'returns an 403 Forbidden' do + expect { sudo? }.to raise_error '403 - {"message"=>"403 Forbidden - Private token must be specified in order to use sudo"}' + end + end + + context 'private access token is used' do + before do + set_env(admin.private_token, user.id) + end + + it 'returns true' do + expect(sudo?).to be_truthy + end + end + end + end + + describe '.handle_api_exception' do + before do + allow_any_instance_of(self.class).to receive(:sentry_enabled?).and_return(true) + allow_any_instance_of(self.class).to receive(:rack_response) + end + + it 'does not report a MethodNotAllowed exception to Sentry' do + exception = Grape::Exceptions::MethodNotAllowed.new({ 'X-GitLab-Test' => '1' }) + allow(exception).to receive(:backtrace).and_return(caller) + + expect(Raven).not_to receive(:capture_exception).with(exception) + + handle_api_exception(exception) + end + + it 'does report RuntimeError to Sentry' do + exception = RuntimeError.new('test error') + allow(exception).to receive(:backtrace).and_return(caller) + + expect_any_instance_of(self.class).to receive(:sentry_context) + expect(Raven).to receive(:capture_exception).with(exception) + + handle_api_exception(exception) + end + end + + describe '.authenticate_non_get!' do + %w[HEAD GET].each do |method_name| + context "method is #{method_name}" do + before do + expect_any_instance_of(self.class).to receive(:route).and_return(double(route_method: method_name)) + end + + it 'does not raise an error' do + expect_any_instance_of(self.class).not_to receive(:authenticate!) + + expect { authenticate_non_get! }.not_to raise_error + end + end + end + + %w[POST PUT PATCH DELETE].each do |method_name| + context "method is #{method_name}" do + before do + expect_any_instance_of(self.class).to receive(:route).and_return(double(route_method: method_name)) + end + + it 'calls authenticate!' do + expect_any_instance_of(self.class).to receive(:authenticate!) + + authenticate_non_get! + end + end + end + end + + describe '.authenticate!' do + context 'current_user is nil' do + before do + expect_any_instance_of(self.class).to receive(:current_user).and_return(nil) + end + + it 'returns a 401 response' do + expect { authenticate! }.to raise_error '401 - {"message"=>"401 Unauthorized"}' + end + end + + context 'current_user is present' do + before do + expect_any_instance_of(self.class).to receive(:current_user).and_return(true) + end + + it 'does not raise an error' do + expect { authenticate! }.not_to raise_error + end + end + end +end diff --git a/spec/requests/api/internal_spec.rb b/spec/requests/api/internal_spec.rb index f0f590b0331..35644bd8cc9 100644 --- a/spec/requests/api/internal_spec.rb +++ b/spec/requests/api/internal_spec.rb @@ -1,11 +1,11 @@ require 'spec_helper' -describe API::API, api: true do +describe API::Internal, api: true do include ApiHelpers let(:user) { create(:user) } let(:key) { create(:key, user: user) } let(:project) { create(:project) } - let(:secret_token) { File.read Gitlab.config.gitlab_shell.secret_file } + let(:secret_token) { Gitlab::Shell.secret_token } describe "GET /internal/check", no_db: true do it do @@ -191,6 +191,26 @@ describe API::API, api: true do expect(json_response["status"]).to be_truthy expect(json_response["repository_path"]).to eq(project.repository.path_to_repo) end + + context 'project as /namespace/project' do + it do + pull(key, project_with_repo_path('/' + project.path_with_namespace)) + + 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) + end + end + + context 'project as namespace/project' do + it do + pull(key, project_with_repo_path(project.path_with_namespace)) + + 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) + end + end end end @@ -299,7 +319,7 @@ describe API::API, api: true do context 'project does not exist' do it do - pull(key, OpenStruct.new(path_with_namespace: 'gitlab/notexists')) + pull(key, project_with_repo_path('gitlab/notexist')) expect(response).to have_http_status(200) expect(json_response["status"]).to be_falsey @@ -386,17 +406,23 @@ describe API::API, api: true do it 'returns link to create new merge request' do expect(json_response).to match [{ "branch_name" => "new_branch", - "url" => "http://localhost/#{project.namespace.name}/#{project.path}/merge_requests/new?merge_request%5Bsource_branch%5D=new_branch", + "url" => "http://#{Gitlab.config.gitlab.host}/#{project.namespace.name}/#{project.path}/merge_requests/new?merge_request%5Bsource_branch%5D=new_branch", "new_merge_request" => true }] end end + def project_with_repo_path(path) + double().tap do |fake_project| + allow(fake_project).to receive_message_chain('repository.path_to_repo' => path) + end + end + def pull(key, project, protocol = 'ssh') post( api("/internal/allowed"), key_id: key.id, - project: project.path_with_namespace, + project: project.repository.path_to_repo, action: 'git-upload-pack', secret_token: secret_token, protocol: protocol @@ -408,7 +434,7 @@ describe API::API, api: true do api("/internal/allowed"), changes: 'd14d6c0abdd253381df51a723d58691b2ee1ab08 570e7b2abdd848b95f2f578043fc23bd6f6fd24d refs/heads/master', key_id: key.id, - project: project.path_with_namespace, + project: project.repository.path_to_repo, action: 'git-receive-pack', secret_token: secret_token, protocol: protocol @@ -420,7 +446,7 @@ describe API::API, api: true do api("/internal/allowed"), ref: 'master', key_id: key.id, - project: project.path_with_namespace, + project: project.repository.path_to_repo, action: 'git-upload-archive', secret_token: secret_token, protocol: 'ssh' @@ -432,7 +458,7 @@ describe API::API, api: true do api("/internal/lfs_authenticate"), key_id: key_id, secret_token: secret_token, - project: project.path_with_namespace + project: project.repository.path_to_repo ) end end diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb index beed53d1e5c..5c80dd98dc7 100644 --- a/spec/requests/api/issues_spec.rb +++ b/spec/requests/api/issues_spec.rb @@ -1,7 +1,8 @@ require 'spec_helper' -describe API::API, api: true do +describe API::Issues, api: true do include ApiHelpers + include EmailHelpers let(:user) { create(:user) } let(:user2) { create(:user) } @@ -71,13 +72,6 @@ describe API::API, api: true do expect(json_response.last).to have_key('web_url') end - it "adds pagination headers and keep query params" do - get api("/issues?state=closed&per_page=3", user) - expect(response.headers['Link']).to eq( - '<http://www.example.com/api/v3/issues?page=1&per_page=3&private_token=%s&state=closed>; rel="first", <http://www.example.com/api/v3/issues?page=1&per_page=3&private_token=%s&state=closed>; rel="last"' % [user.private_token, user.private_token] - ) - end - it 'returns an array of closed issues' do get api('/issues?state=closed', user) expect(response).to have_http_status(200) @@ -365,6 +359,24 @@ describe API::API, api: true do let(:base_url) { "/projects/#{project.id}" } let(:title) { milestone.title } + it "returns 404 on private projects for other users" do + private_project = create(:empty_project, :private) + create(:issue, project: private_project) + + get api("/projects/#{private_project.id}/issues", non_member) + + expect(response).to have_http_status(404) + end + + it 'returns no issues when user has access to project but not issues' do + restricted_project = create(:empty_project, :public, issues_access_level: ProjectFeature::PRIVATE) + create(:issue, project: restricted_project) + + get api("/projects/#{restricted_project.id}/issues", non_member) + + expect(json_response).to eq([]) + end + it 'returns project issues without confidential issues for non project members' do get api("#{base_url}/issues", non_member) expect(response).to have_http_status(200) @@ -630,14 +642,13 @@ describe API::API, api: true do post api("/projects/#{project.id}/issues", user), title: 'new issue', confidential: 'foo' - expect(response).to have_http_status(201) - expect(json_response['title']).to eq('new issue') - expect(json_response['confidential']).to be_falsy + expect(response).to have_http_status(400) + expect(json_response['error']).to eq('confidential is invalid') end it "sends notifications for subscribers of newly added labels" do label = project.labels.first - label.toggle_subscription(user2) + label.toggle_subscription(user2, project) perform_enqueued_jobs do post api("/projects/#{project.id}/issues", user), @@ -673,6 +684,32 @@ describe API::API, api: true do ]) end + context 'resolving issues in a merge request' do + let(:discussion) { Discussion.for_diff_notes([create(:diff_note_on_merge_request)]).first } + let(:merge_request) { discussion.noteable } + let(:project) { merge_request.source_project } + before do + project.team << [user, :master] + post api("/projects/#{project.id}/issues", user), + title: 'New Issue', + merge_request_for_resolving_discussions: merge_request.iid + end + + it 'creates a new project issue' do + expect(response).to have_http_status(:created) + end + + it 'resolves the discussions in a merge request' do + discussion.first_note.reload + + expect(discussion.resolved?).to be(true) + end + + it 'assigns a description to the issue mentioning the merge request' do + expect(json_response['description']).to include(merge_request.to_reference) + end + end + context 'with due date' do it 'creates a new project issue' do due_date = 2.weeks.from_now.strftime('%Y-%m-%d') @@ -697,6 +734,14 @@ describe API::API, api: true do expect(Time.parse(json_response['created_at'])).to be_like_time(creation_time) end end + + context 'the user can only read the issue' do + it 'cannot create new labels' do + expect do + post api("/projects/#{project.id}/issues", non_member), title: 'new issue', labels: 'label, label2' + end.not_to change { project.labels.count } + end + end end describe 'POST /projects/:id/issues with spam filtering' do @@ -809,8 +854,8 @@ describe API::API, api: true do put api("/projects/#{project.id}/issues/#{confidential_issue.id}", user), confidential: 'foo' - expect(response).to have_http_status(200) - expect(json_response['confidential']).to be_truthy + expect(response).to have_http_status(400) + expect(json_response['error']).to eq('confidential is invalid') end end end @@ -828,7 +873,7 @@ describe API::API, api: true do it "sends notifications for subscribers of newly added labels when issue is updated" do label = create(:label, title: 'foo', color: '#FFAABB', project: project) - label.toggle_subscription(user2) + label.toggle_subscription(user2, project) perform_enqueued_jobs do put api("/projects/#{project.id}/issues/#{issue.id}", user), @@ -839,8 +884,8 @@ describe API::API, api: true do end it 'removes all labels' do - put api("/projects/#{project.id}/issues/#{issue.id}", user), - labels: '' + put api("/projects/#{project.id}/issues/#{issue.id}", user), labels: '' + expect(response).to have_http_status(200) expect(json_response['labels']).to eq([]) end @@ -892,8 +937,8 @@ describe API::API, api: true do update_time = 2.weeks.ago put api("/projects/#{project.id}/issues/#{issue.id}", user), labels: 'label3', state_event: 'close', updated_at: update_time - expect(response).to have_http_status(200) + expect(response).to have_http_status(200) expect(json_response['labels']).to include 'label3' expect(Time.parse(json_response['updated_at'])).to be_like_time(update_time) end @@ -932,6 +977,14 @@ describe API::API, api: true do expect(json_response['state']).to eq 'opened' end end + + context 'when issue does not exist' do + it 'returns 404 when trying to move an issue' do + delete api("/projects/#{project.id}/issues/123", user) + + expect(response).to have_http_status(404) + end + end end describe '/projects/:id/issues/:issue_id/move' do @@ -980,6 +1033,7 @@ describe API::API, api: true do to_project_id: target_project.id expect(response).to have_http_status(404) + expect(json_response['message']).to eq('404 Issue Not Found') end end @@ -989,6 +1043,7 @@ describe API::API, api: true do to_project_id: target_project.id expect(response).to have_http_status(404) + expect(json_response['message']).to eq('404 Project Not Found') end end diff --git a/spec/requests/api/keys_spec.rb b/spec/requests/api/keys_spec.rb index 893ed5c2b10..4c80987d680 100644 --- a/spec/requests/api/keys_spec.rb +++ b/spec/requests/api/keys_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe API::API, api: true do +describe API::Keys, api: true do include ApiHelpers let(:user) { create(:user) } diff --git a/spec/requests/api/labels_spec.rb b/spec/requests/api/labels_spec.rb index 2ff90b6deac..b29ce1ea25e 100644 --- a/spec/requests/api/labels_spec.rb +++ b/spec/requests/api/labels_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe API::API, api: true do +describe API::Labels, api: true do include ApiHelpers let(:user) { create(:user) } @@ -15,8 +15,12 @@ describe API::API, api: true do describe 'GET /projects/:id/labels' do it 'returns all available labels to the project' do group = create(:group) - group_label = create(:group_label, group: group) + group_label = create(:group_label, title: 'feature', group: group) project.update(group: group) + create(:labeled_issue, project: project, labels: [group_label], author: user) + create(:labeled_issue, project: project, labels: [label1], author: user, state: :closed) + create(:labeled_merge_request, labels: [priority_label], author: user, source_project: project ) + expected_keys = [ 'id', 'name', 'color', 'description', 'open_issues_count', 'closed_issues_count', 'open_merge_requests_count', @@ -30,14 +34,37 @@ describe API::API, api: true do expect(json_response.size).to eq(3) expect(json_response.first.keys).to match_array expected_keys expect(json_response.map { |l| l['name'] }).to match_array([group_label.name, priority_label.name, label1.name]) - expect(json_response.last['name']).to eq(label1.name) - expect(json_response.last['color']).to be_present - expect(json_response.last['description']).to be_nil - expect(json_response.last['open_issues_count']).to eq(0) - expect(json_response.last['closed_issues_count']).to eq(0) - expect(json_response.last['open_merge_requests_count']).to eq(0) - expect(json_response.last['priority']).to be_nil - expect(json_response.last['subscribed']).to be_falsey + + label1_response = json_response.find { |l| l['name'] == label1.title } + group_label_response = json_response.find { |l| l['name'] == group_label.title } + priority_label_response = json_response.find { |l| l['name'] == priority_label.title } + + expect(label1_response['open_issues_count']).to eq(0) + expect(label1_response['closed_issues_count']).to eq(1) + expect(label1_response['open_merge_requests_count']).to eq(0) + expect(label1_response['name']).to eq(label1.name) + expect(label1_response['color']).to be_present + expect(label1_response['description']).to be_nil + expect(label1_response['priority']).to be_nil + expect(label1_response['subscribed']).to be_falsey + + expect(group_label_response['open_issues_count']).to eq(1) + expect(group_label_response['closed_issues_count']).to eq(0) + expect(group_label_response['open_merge_requests_count']).to eq(0) + expect(group_label_response['name']).to eq(group_label.name) + expect(group_label_response['color']).to be_present + expect(group_label_response['description']).to be_nil + expect(group_label_response['priority']).to be_nil + expect(group_label_response['subscribed']).to be_falsey + + expect(priority_label_response['open_issues_count']).to eq(0) + expect(priority_label_response['closed_issues_count']).to eq(0) + expect(priority_label_response['open_merge_requests_count']).to eq(1) + expect(priority_label_response['name']).to eq(priority_label.name) + expect(priority_label_response['color']).to be_present + expect(priority_label_response['description']).to be_nil + expect(priority_label_response['priority']).to eq(3) + expect(priority_label_response['subscribed']).to be_falsey end end @@ -312,7 +339,7 @@ describe API::API, api: true do end context "when user is already subscribed to label" do - before { label1.subscribe(user) } + before { label1.subscribe(user, project) } it "returns 304" do post api("/projects/#{project.id}/labels/#{label1.id}/subscription", user) @@ -331,7 +358,7 @@ describe API::API, api: true do end describe "DELETE /projects/:id/labels/:label_id/subscription" do - before { label1.subscribe(user) } + before { label1.subscribe(user, project) } context "when label_id is a label title" do it "unsubscribes from the label" do @@ -354,7 +381,7 @@ describe API::API, api: true do end context "when user is already unsubscribed from label" do - before { label1.unsubscribe(user) } + before { label1.unsubscribe(user, project) } it "returns 304" do delete api("/projects/#{project.id}/labels/#{label1.id}/subscription", user) diff --git a/spec/requests/api/members_spec.rb b/spec/requests/api/members_spec.rb index 493c0a893d1..2c94c86ccfa 100644 --- a/spec/requests/api/members_spec.rb +++ b/spec/requests/api/members_spec.rb @@ -9,19 +9,19 @@ describe API::Members, api: true do let(:stranger) { create(:user) } let(:project) do - project = create(:project, :public, creator_id: master.id, namespace: master.namespace) - project.team << [developer, :developer] - project.team << [master, :master] - project.request_access(access_requester) - project + create(:project, :public, :access_requestable, creator_id: master.id, namespace: master.namespace) do |project| + project.team << [developer, :developer] + project.team << [master, :master] + project.request_access(access_requester) + end end let!(:group) do - group = create(:group, :public) - group.add_developer(developer) - group.add_owner(master) - group.request_access(access_requester) - group + create(:group, :public, :access_requestable) do |group| + group.add_developer(developer) + group.add_owner(master) + group.request_access(access_requester) + end end shared_examples 'GET /:sources/:id/members' do |source_type| diff --git a/spec/requests/api/merge_request_diffs_spec.rb b/spec/requests/api/merge_request_diffs_spec.rb index 131c2d406ea..e1887138aab 100644 --- a/spec/requests/api/merge_request_diffs_spec.rb +++ b/spec/requests/api/merge_request_diffs_spec.rb @@ -1,6 +1,6 @@ require "spec_helper" -describe API::API, 'MergeRequestDiffs', api: true do +describe API::MergeRequestDiffs, 'MergeRequestDiffs', api: true do include ApiHelpers let!(:user) { create(:user) } diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb index bae4fa11ec2..f032d1b683d 100644 --- a/spec/requests/api/merge_requests_spec.rb +++ b/spec/requests/api/merge_requests_spec.rb @@ -1,6 +1,6 @@ require "spec_helper" -describe API::API, api: true do +describe API::MergeRequests, api: true do include ApiHelpers let(:base_time) { Time.now } let(:user) { create(:user) } @@ -169,6 +169,16 @@ describe API::API, api: true do expect(json_response.first['id']).to eq merge_request.id end + it 'returns merge_request by iid array' do + get api("/projects/#{project.id}/merge_requests", user), iid: [merge_request.iid, merge_request_closed.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.first['title']).to eq merge_request_closed.title + expect(json_response.first['id']).to eq merge_request_closed.id + end + it "returns a 404 error if merge_request_id not found" do get api("/projects/#{project.id}/merge_requests/999", user) expect(response).to have_http_status(404) @@ -224,11 +234,14 @@ describe API::API, api: true do target_branch: 'master', author: user, labels: 'label, label2', - milestone_id: milestone.id + milestone_id: milestone.id, + remove_source_branch: true + expect(response).to have_http_status(201) expect(json_response['title']).to eq('Test merge_request') expect(json_response['labels']).to eq(['label', 'label2']) expect(json_response['milestone']['id']).to eq(milestone.id) + expect(json_response['force_remove_source_branch']).to be_truthy end it "returns 422 when source_branch equals target_branch" do @@ -392,14 +405,6 @@ describe API::API, api: true do end end - describe "PUT /projects/:id/merge_requests/:merge_request_id to close MR" do - it "returns merge_request" do - put api("/projects/#{project.id}/merge_requests/#{merge_request.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/merge_requests/:merge_request_id/merge" do let(:pipeline) { create(:ci_pipeline_without_jobs) } @@ -463,8 +468,8 @@ describe API::API, api: true do expect(response).to have_http_status(200) end - it "enables merge when build succeeds if the ci is active" do - allow_any_instance_of(MergeRequest).to receive(:pipeline).and_return(pipeline) + it "enables merge when pipeline succeeds if the pipeline is active" do + allow_any_instance_of(MergeRequest).to receive(:head_pipeline).and_return(pipeline) allow(pipeline).to receive(:active?).and_return(true) put api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge", user), merge_when_build_succeeds: true @@ -476,6 +481,15 @@ describe API::API, api: true do end describe "PUT /projects/:id/merge_requests/:merge_request_id" do + context "to close a MR" do + it "returns merge_request" do + put api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user), state_event: "close" + + expect(response).to have_http_status(200) + expect(json_response['state']).to eq('closed') + end + end + it "updates title and returns merge_request" do put api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user), title: "New title" expect(response).to have_http_status(200) @@ -494,23 +508,24 @@ describe API::API, api: true do expect(json_response['milestone']['id']).to eq(milestone.id) end - it "returns 400 when source_branch is specified" do - put api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user), - source_branch: "master", target_branch: "master" - expect(response).to have_http_status(400) - end - it "returns merge_request with renamed target_branch" do put api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user), target_branch: "wiki" expect(response).to have_http_status(200) expect(json_response['target_branch']).to eq('wiki') end + it "returns merge_request that removes the source branch" do + put api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user), remove_source_branch: true + + expect(response).to have_http_status(200) + expect(json_response['force_remove_source_branch']).to be_truthy + end + it 'allows special label names' do - put api("/projects/#{project.id}/merge_requests/#{merge_request.id}", - user), - title: 'new issue', - labels: 'label, label?, label&foo, ?, &' + put api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user), + title: 'new issue', + labels: 'label, label?, label&foo, ?, &' + expect(response.status).to eq(200) expect(json_response['labels']).to include 'label' expect(json_response['labels']).to include 'label?' @@ -518,6 +533,22 @@ describe API::API, api: true do expect(json_response['labels']).to include '?' expect(json_response['labels']).to include '&' end + + it 'does not update state when title is empty' do + put api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user), state_event: 'close', title: nil + + merge_request.reload + expect(response).to have_http_status(400) + expect(merge_request.state).to eq('opened') + end + + it 'does not update state when target_branch is empty' do + put api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user), state_event: 'close', target_branch: nil + + merge_request.reload + expect(response).to have_http_status(400) + expect(merge_request.state).to eq('opened') + end end describe "POST /projects/:id/merge_requests/:merge_request_id/comments" do @@ -539,7 +570,7 @@ describe API::API, api: true do it "returns 404 if note is attached to non existent merge request" do post api("/projects/#{project.id}/merge_requests/404/comments", user), - note: 'My comment' + note: 'My comment' expect(response).to have_http_status(404) end end diff --git a/spec/requests/api/milestones_spec.rb b/spec/requests/api/milestones_spec.rb index dd192bea432..8beef821d6c 100644 --- a/spec/requests/api/milestones_spec.rb +++ b/spec/requests/api/milestones_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe API::API, api: true do +describe API::Milestones, api: true do include ApiHelpers let(:user) { create(:user) } let!(:project) { create(:empty_project, namespace: user.namespace ) } @@ -61,6 +61,15 @@ describe API::API, api: true do expect(json_response.first['id']).to eq closed_milestone.id end + it 'returns a project milestone by iid array' do + get api("/projects/#{project.id}/milestones", user), iid: [milestone.iid, closed_milestone.iid] + + expect(response).to have_http_status(200) + expect(json_response.size).to eq(2) + expect(json_response.first['title']).to eq milestone.title + expect(json_response.first['id']).to eq milestone.id + end + it 'returns 401 error if user not authenticated' do get api("/projects/#{project.id}/milestones/#{milestone.id}") @@ -83,13 +92,14 @@ describe API::API, api: true do expect(json_response['description']).to be_nil end - it 'creates a new project milestone with description and due date' do + 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' + 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 @@ -123,6 +133,15 @@ describe API::API, api: true do 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' diff --git a/spec/requests/api/namespaces_spec.rb b/spec/requests/api/namespaces_spec.rb index 5347cf4f7bc..c1edf384d5c 100644 --- a/spec/requests/api/namespaces_spec.rb +++ b/spec/requests/api/namespaces_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe API::API, api: true do +describe API::Namespaces, api: true do include ApiHelpers let(:admin) { create(:admin) } let(:user) { create(:user) } diff --git a/spec/requests/api/notes_spec.rb b/spec/requests/api/notes_spec.rb index 0124b7271b3..028f93c8561 100644 --- a/spec/requests/api/notes_spec.rb +++ b/spec/requests/api/notes_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe API::API, api: true do +describe API::Notes, api: true do include ApiHelpers let(:user) { create(:user) } let!(:project) { create(:project, :public, namespace: user.namespace) } @@ -25,7 +25,7 @@ describe API::API, api: true do let!(:cross_reference_note) do create :note, noteable: ext_issue, project: ext_proj, - note: "Mentioned in issue #{private_issue.to_reference(ext_proj)}", + note: "mentioned in issue #{private_issue.to_reference(ext_proj)}", system: true end diff --git a/spec/requests/api/notification_settings_spec.rb b/spec/requests/api/notification_settings_spec.rb index e6d8a5ee954..8691a81420f 100644 --- a/spec/requests/api/notification_settings_spec.rb +++ b/spec/requests/api/notification_settings_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe API::API, api: true do +describe API::NotificationSettings, api: true do include ApiHelpers let(:user) { create(:user) } diff --git a/spec/requests/api/pipelines_spec.rb b/spec/requests/api/pipelines_spec.rb index 7011bdc9ec0..9a01f7fa1c4 100644 --- a/spec/requests/api/pipelines_spec.rb +++ b/spec/requests/api/pipelines_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe API::API, api: true do +describe API::Pipelines, api: true do include ApiHelpers let(:user) { create(:user) } @@ -41,6 +41,52 @@ describe API::API, api: true do end end + describe 'POST /projects/:id/pipeline ' do + context 'authorized user' do + context 'with gitlab-ci.yml' do + before { stub_ci_pipeline_to_return_yaml_file } + + it 'creates and returns a new pipeline' do + expect do + post api("/projects/#{project.id}/pipeline", user), ref: project.default_branch + end.to change { Ci::Pipeline.count }.by(1) + + expect(response).to have_http_status(201) + expect(json_response).to be_a Hash + expect(json_response['sha']).to eq project.commit.id + end + + it 'fails when using an invalid ref' do + post api("/projects/#{project.id}/pipeline", user), ref: 'invalid_ref' + + expect(response).to have_http_status(400) + expect(json_response['message']['base'].first).to eq 'Reference not found' + expect(json_response).not_to be_an Array + end + end + + context 'without gitlab-ci.yml' do + it 'fails to create pipeline' do + post api("/projects/#{project.id}/pipeline", user), ref: project.default_branch + + expect(response).to have_http_status(400) + expect(json_response['message']['base'].first).to eq 'Missing .gitlab-ci.yml file' + expect(json_response).not_to be_an Array + end + end + end + + context 'unauthorized user' do + it 'does not create pipeline' do + post api("/projects/#{project.id}/pipeline", non_member), ref: project.default_branch + + expect(response).to have_http_status(404) + expect(json_response['message']).to eq '404 Project Not Found' + expect(json_response).not_to be_an Array + end + end + end + describe 'GET /projects/:id/pipelines/:pipeline_id' do context 'authorized user' do it 'returns project pipelines' do @@ -57,6 +103,18 @@ describe API::API, api: true do expect(json_response['message']).to eq '404 Not found' expect(json_response['id']).to be nil end + + context 'with coverage' do + before do + create(:ci_build, coverage: 30, pipeline: pipeline) + end + + it 'exposes the coverage' do + get api("/projects/#{project.id}/pipelines/#{pipeline.id}", user) + + expect(json_response["coverage"].to_i).to eq(30) + end + end end context 'unauthorized user' do diff --git a/spec/requests/api/project_hooks_spec.rb b/spec/requests/api/project_hooks_spec.rb index 5f39329a1b8..a42cedae614 100644 --- a/spec/requests/api/project_hooks_spec.rb +++ b/spec/requests/api/project_hooks_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe API::API, 'ProjectHooks', api: true do +describe API::ProjectHooks, 'ProjectHooks', api: true do include ApiHelpers let(:user) { create(:user) } let(:user3) { create(:user) } diff --git a/spec/requests/api/project_snippets_spec.rb b/spec/requests/api/project_snippets_spec.rb index 01148f0a05e..01032c0929b 100644 --- a/spec/requests/api/project_snippets_spec.rb +++ b/spec/requests/api/project_snippets_spec.rb @@ -1,12 +1,14 @@ require 'rails_helper' -describe API::API, api: true do +describe API::ProjectSnippets, api: true do include ApiHelpers + let(:project) { create(:empty_project, :public) } + let(:admin) { create(:admin) } + describe 'GET /projects/:project_id/snippets/:id' do # TODO (rspeicher): Deprecated; remove in 9.0 it 'always exposes expires_at as nil' do - admin = create(:admin) snippet = create(:project_snippet, author: admin) get api("/projects/#{snippet.project.id}/snippets/#{snippet.id}", admin) @@ -17,9 +19,9 @@ describe API::API, api: true do end describe 'GET /projects/:project_id/snippets/' do + let(:user) { create(:user) } + it 'returns all snippets available to team member' do - project = create(:project, :public) - user = create(:user) project.team << [user, :developer] public_snippet = create(:project_snippet, :public, project: project) internal_snippet = create(:project_snippet, :internal, project: project) @@ -34,8 +36,6 @@ describe API::API, api: true do end it 'hides private snippets from regular user' do - project = create(:project, :public) - user = create(:user) create(:project_snippet, :private, project: project) get api("/projects/#{project.id}/snippets/", user) @@ -45,16 +45,16 @@ describe API::API, api: true do end describe 'POST /projects/:project_id/snippets/' do - it 'creates a new snippet' do - admin = create(:admin) - project = create(:project) - params = { + let(:params) do + { title: 'Test Title', file_name: 'test.rb', code: 'puts "hello world"', visibility_level: Gitlab::VisibilityLevel::PUBLIC } + end + it 'creates a new snippet' do post api("/projects/#{project.id}/snippets/", admin), params expect(response).to have_http_status(201) @@ -64,12 +64,20 @@ describe API::API, api: true do expect(snippet.file_name).to eq(params[:file_name]) expect(snippet.visibility_level).to eq(params[:visibility_level]) end + + it 'returns 400 for missing parameters' do + params.delete(:title) + + post api("/projects/#{project.id}/snippets/", admin), params + + expect(response).to have_http_status(400) + end end describe 'PUT /projects/:project_id/snippets/:id/' do + let(:snippet) { create(:project_snippet, author: admin) } + it 'updates snippet' do - admin = create(:admin) - snippet = create(:project_snippet, author: admin) new_content = 'New content' put api("/projects/#{snippet.project.id}/snippets/#{snippet.id}/", admin), code: new_content @@ -78,9 +86,24 @@ describe API::API, api: true do snippet.reload expect(snippet.content).to eq(new_content) end + + it 'returns 404 for invalid snippet id' do + put api("/projects/#{snippet.project.id}/snippets/1234", admin), title: 'foo' + + expect(response).to have_http_status(404) + expect(json_response['message']).to eq('404 Snippet Not Found') + end + + it 'returns 400 for missing parameters' do + put api("/projects/#{project.id}/snippets/1234", admin) + + expect(response).to have_http_status(400) + end end describe 'DELETE /projects/:project_id/snippets/:id/' do + let(:snippet) { create(:project_snippet, author: admin) } + it 'deletes snippet' do admin = create(:admin) snippet = create(:project_snippet, author: admin) @@ -89,18 +112,31 @@ describe API::API, api: true do expect(response).to have_http_status(200) end + + it 'returns 404 for invalid snippet id' do + delete api("/projects/#{snippet.project.id}/snippets/1234", admin) + + expect(response).to have_http_status(404) + expect(json_response['message']).to eq('404 Snippet Not Found') + end end describe 'GET /projects/:project_id/snippets/:id/raw' do - it 'returns raw text' do - admin = create(:admin) - snippet = create(:project_snippet, author: admin) + let(:snippet) { create(:project_snippet, author: admin) } + it 'returns raw text' do get api("/projects/#{snippet.project.id}/snippets/#{snippet.id}/raw", admin) expect(response).to have_http_status(200) expect(response.content_type).to eq 'text/plain' expect(response.body).to eq(snippet.content) end + + it 'returns 404 for invalid snippet id' do + delete api("/projects/#{snippet.project.id}/snippets/1234", admin) + + expect(response).to have_http_status(404) + expect(json_response['message']).to eq('404 Snippet Not Found') + end end end diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index 3c8f0ac531a..8304c408064 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- require 'spec_helper' -describe API::API, api: true do +describe API::Projects, api: true do include ApiHelpers include Gitlab::CurrentSettings let(:user) { create(:user) } @@ -167,7 +167,7 @@ describe API::API, api: true do expect(json_response).to satisfy do |response| response.one? do |entry| entry.has_key?('permissions') && - entry['name'] == project.name && + entry['name'] == project.name && entry['owner']['username'] == user.username end end @@ -175,33 +175,68 @@ describe API::API, api: true do end end + describe 'GET /projects/owned' do + before do + project3 + project4 + end + + context 'when unauthenticated' do + it 'returns authentication error' do + get api('/projects/owned') + expect(response).to have_http_status(401) + end + end + + context 'when authenticated as project owner' do + it 'returns an array of projects the user owns' do + get api('/projects/owned', user4) + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(json_response.first['name']).to eq(project4.name) + expect(json_response.first['owner']['username']).to eq(user4.username) + end + end + end + describe 'GET /projects/visible' do - let(:public_project) { create(:project, :public) } + shared_examples_for 'visible projects response' do + it 'returns the visible projects' do + get api('/projects/visible', current_user) + + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(json_response.map { |p| p['id'] }).to contain_exactly(*projects.map(&:id)) + end + end + let!(:public_project) { create(:project, :public) } before do - public_project project project2 project3 project4 end - it 'returns the projects viewable by the user' do - get api('/projects/visible', user) - - expect(response).to have_http_status(200) - expect(json_response).to be_an Array - expect(json_response.map { |project| project['id'] }). - to contain_exactly(public_project.id, project.id, project2.id, project3.id) + context 'when unauthenticated' do + it_behaves_like 'visible projects response' do + let(:current_user) { nil } + let(:projects) { [public_project] } + end end - it 'shows only public projects when the user only has access to those' do - get api('/projects/visible', user2) + context 'when authenticated' do + it_behaves_like 'visible projects response' do + let(:current_user) { user } + let(:projects) { [public_project, project, project2, project3] } + end + end - expect(response).to have_http_status(200) - expect(json_response).to be_an Array - expect(json_response.map { |project| project['id'] }). - to contain_exactly(public_project.id) + context 'when authenticated as a different user' do + it_behaves_like 'visible projects response' do + let(:current_user) { user2 } + let(:projects) { [public_project] } + end end end @@ -336,6 +371,14 @@ describe API::API, api: true do expect(json_response['only_allow_merge_if_all_discussions_are_resolved']).to be_falsey end + it 'sets a project as allowing merge if only_allow_merge_if_all_discussions_are_resolved is nil' do + project = attributes_for(:project, only_allow_merge_if_all_discussions_are_resolved: nil) + + post api('/projects', user), project + + expect(json_response['only_allow_merge_if_all_discussions_are_resolved']).to be_falsey + end + it 'sets a project as allowing merge only if all discussions are resolved' do project = attributes_for(:project, { only_allow_merge_if_all_discussions_are_resolved: true }) @@ -383,16 +426,7 @@ describe API::API, api: true do not_to change { Project.count } expect(response).to have_http_status(400) - expect(json_response['message']['name']).to eq([ - 'can\'t be blank', - 'is too short (minimum is 0 characters)', - Gitlab::Regex.project_name_regex_message - ]) - expect(json_response['message']['path']).to eq([ - 'can\'t be blank', - 'is too short (minimum is 0 characters)', - Gitlab::Regex.send(:project_path_regex_message) - ]) + expect(json_response['error']).to eq('name is missing') end it 'assigns attributes to project' do @@ -406,6 +440,7 @@ describe API::API, api: true do post api("/projects/user/#{user.id}", admin), project + expect(response).to have_http_status(201) project.each_pair do |k, v| next if %i[has_external_issue_tracker path].include?(k) expect(json_response[k.to_s]).to eq(v) @@ -415,6 +450,8 @@ describe API::API, api: true do it 'sets a project as public' do project = attributes_for(:project, :public) post api("/projects/user/#{user.id}", admin), project + + expect(response).to have_http_status(201) expect(json_response['public']).to be_truthy expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::PUBLIC) end @@ -422,6 +459,8 @@ describe API::API, api: true do it 'sets a project as public using :public' do project = attributes_for(:project, { public: true }) post api("/projects/user/#{user.id}", admin), project + + expect(response).to have_http_status(201) expect(json_response['public']).to be_truthy expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::PUBLIC) end @@ -429,6 +468,8 @@ describe API::API, api: true do it 'sets a project as internal' do project = attributes_for(:project, :internal) post api("/projects/user/#{user.id}", admin), project + + expect(response).to have_http_status(201) expect(json_response['public']).to be_falsey expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::INTERNAL) end @@ -436,6 +477,7 @@ describe API::API, api: true do it 'sets a project as internal overriding :public' do project = attributes_for(:project, :internal, { public: true }) post api("/projects/user/#{user.id}", admin), project + expect(response).to have_http_status(201) expect(json_response['public']).to be_falsey expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::INTERNAL) end @@ -497,135 +539,150 @@ describe API::API, api: true do end describe 'GET /projects/:id' do - before { project } - before { project_member } - - it 'returns a project by id' do - group = create(:group) - link = create(:project_group_link, project: project, group: group) + context 'when unauthenticated' do + it 'returns the public projects' do + public_project = create(:project, :public) - get api("/projects/#{project.id}", user) + get api("/projects/#{public_project.id}") - expect(response).to have_http_status(200) - expect(json_response['id']).to eq(project.id) - expect(json_response['description']).to eq(project.description) - expect(json_response['default_branch']).to eq(project.default_branch) - expect(json_response['tag_list']).to be_an Array - expect(json_response['public']).to be_falsey - expect(json_response['archived']).to be_falsey - expect(json_response['visibility_level']).to be_present - expect(json_response['ssh_url_to_repo']).to be_present - expect(json_response['http_url_to_repo']).to be_present - expect(json_response['web_url']).to be_present - expect(json_response['owner']).to be_a Hash - expect(json_response['owner']).to be_a Hash - expect(json_response['name']).to eq(project.name) - expect(json_response['path']).to be_present - expect(json_response['issues_enabled']).to be_present - expect(json_response['merge_requests_enabled']).to be_present - expect(json_response['wiki_enabled']).to be_present - expect(json_response['builds_enabled']).to be_present - expect(json_response['snippets_enabled']).to be_present - expect(json_response['container_registry_enabled']).to be_present - expect(json_response['created_at']).to be_present - expect(json_response['last_activity_at']).to be_present - expect(json_response['shared_runners_enabled']).to be_present - expect(json_response['creator_id']).to be_present - expect(json_response['namespace']).to be_present - expect(json_response['avatar_url']).to be_nil - expect(json_response['star_count']).to be_present - expect(json_response['forks_count']).to be_present - expect(json_response['public_builds']).to be_present - 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) - expect(json_response['shared_with_groups'][0]['group_name']).to eq(group.name) - expect(json_response['shared_with_groups'][0]['group_access_level']).to eq(link.group_access) - expect(json_response['only_allow_merge_if_build_succeeds']).to eq(project.only_allow_merge_if_build_succeeds) - expect(json_response['only_allow_merge_if_all_discussions_are_resolved']).to eq(project.only_allow_merge_if_all_discussions_are_resolved) - end - - it 'returns a project by path name' do - get api("/projects/#{project.id}", user) - expect(response).to have_http_status(200) - expect(json_response['name']).to eq(project.name) + expect(response).to have_http_status(200) + expect(json_response['id']).to eq(public_project.id) + expect(json_response['description']).to eq(public_project.description) + expect(json_response.keys).not_to include('permissions') + end end - it 'returns a 404 error if not found' do - get api('/projects/42', user) - expect(response).to have_http_status(404) - expect(json_response['message']).to eq('404 Project Not Found') - end + context 'when authenticated' do + before do + project + project_member + end - it 'returns a 404 error if user is not a member' do - other_user = create(:user) - get api("/projects/#{project.id}", other_user) - expect(response).to have_http_status(404) - end + it 'returns a project by id' do + group = create(:group) + link = create(:project_group_link, project: project, group: group) - it 'handles users with dots' do - dot_user = create(:user, username: 'dot.user') - project = create(:project, creator_id: dot_user.id, namespace: dot_user.namespace) + get api("/projects/#{project.id}", user) - get api("/projects/#{dot_user.namespace.name}%2F#{project.path}", dot_user) - expect(response).to have_http_status(200) - expect(json_response['name']).to eq(project.name) - end + expect(response).to have_http_status(200) + expect(json_response['id']).to eq(project.id) + expect(json_response['description']).to eq(project.description) + expect(json_response['default_branch']).to eq(project.default_branch) + expect(json_response['tag_list']).to be_an Array + expect(json_response['public']).to be_falsey + expect(json_response['archived']).to be_falsey + expect(json_response['visibility_level']).to be_present + expect(json_response['ssh_url_to_repo']).to be_present + expect(json_response['http_url_to_repo']).to be_present + expect(json_response['web_url']).to be_present + expect(json_response['owner']).to be_a Hash + expect(json_response['owner']).to be_a Hash + expect(json_response['name']).to eq(project.name) + expect(json_response['path']).to be_present + expect(json_response['issues_enabled']).to be_present + expect(json_response['merge_requests_enabled']).to be_present + expect(json_response['wiki_enabled']).to be_present + expect(json_response['builds_enabled']).to be_present + expect(json_response['snippets_enabled']).to be_present + expect(json_response['container_registry_enabled']).to be_present + expect(json_response['created_at']).to be_present + expect(json_response['last_activity_at']).to be_present + expect(json_response['shared_runners_enabled']).to be_present + expect(json_response['creator_id']).to be_present + expect(json_response['namespace']).to be_present + expect(json_response['avatar_url']).to be_nil + expect(json_response['star_count']).to be_present + expect(json_response['forks_count']).to be_present + expect(json_response['public_builds']).to be_present + 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) + expect(json_response['shared_with_groups'][0]['group_name']).to eq(group.name) + expect(json_response['shared_with_groups'][0]['group_access_level']).to eq(link.group_access) + expect(json_response['only_allow_merge_if_build_succeeds']).to eq(project.only_allow_merge_if_build_succeeds) + expect(json_response['only_allow_merge_if_all_discussions_are_resolved']).to eq(project.only_allow_merge_if_all_discussions_are_resolved) + end + + it 'returns a project by path name' do + get api("/projects/#{project.id}", user) + expect(response).to have_http_status(200) + expect(json_response['name']).to eq(project.name) + end - describe 'permissions' do - context 'all projects' do - before { project.team << [user, :master] } + it 'returns a 404 error if not found' do + get api('/projects/42', user) + expect(response).to have_http_status(404) + expect(json_response['message']).to eq('404 Project Not Found') + end - it 'contains permission information' do - get api("/projects", user) + it 'returns a 404 error if user is not a member' do + other_user = create(:user) + get api("/projects/#{project.id}", other_user) + expect(response).to have_http_status(404) + end - expect(response).to have_http_status(200) - expect(json_response.first['permissions']['project_access']['access_level']). - to eq(Gitlab::Access::MASTER) - expect(json_response.first['permissions']['group_access']).to be_nil - end + it 'handles users with dots' do + dot_user = create(:user, username: 'dot.user') + project = create(:project, creator_id: dot_user.id, namespace: dot_user.namespace) + + get api("/projects/#{dot_user.namespace.name}%2F#{project.path}", dot_user) + expect(response).to have_http_status(200) + expect(json_response['name']).to eq(project.name) end - context 'personal project' do - it 'sets project access and returns 200' do - project.team << [user, :master] - get api("/projects/#{project.id}", user) + describe 'permissions' do + context 'all projects' do + before { project.team << [user, :master] } - expect(response).to have_http_status(200) - expect(json_response['permissions']['project_access']['access_level']). + it 'contains permission information' do + get api("/projects", user) + + expect(response).to have_http_status(200) + expect(json_response.first['permissions']['project_access']['access_level']). to eq(Gitlab::Access::MASTER) - expect(json_response['permissions']['group_access']).to be_nil + expect(json_response.first['permissions']['group_access']).to be_nil + end end - end - context 'group project' do - let(:project2) { create(:project, group: create(:group)) } + context 'personal project' do + it 'sets project access and returns 200' do + project.team << [user, :master] + get api("/projects/#{project.id}", user) - before { project2.group.add_owner(user) } + expect(response).to have_http_status(200) + expect(json_response['permissions']['project_access']['access_level']). + to eq(Gitlab::Access::MASTER) + expect(json_response['permissions']['group_access']).to be_nil + end + end - it 'sets the owner and return 200' do - get api("/projects/#{project2.id}", user) + context 'group project' do + let(:project2) { create(:project, group: create(:group)) } - expect(response).to have_http_status(200) - expect(json_response['permissions']['project_access']).to be_nil - expect(json_response['permissions']['group_access']['access_level']). + before { project2.group.add_owner(user) } + + it 'sets the owner and return 200' do + get api("/projects/#{project2.id}", user) + + expect(response).to have_http_status(200) + expect(json_response['permissions']['project_access']).to be_nil + expect(json_response['permissions']['group_access']['access_level']). to eq(Gitlab::Access::OWNER) + end end end end end describe 'GET /projects/:id/events' do - before { project_member2 } - - context 'valid request' do - before do + shared_examples_for 'project events response' do + it 'returns the project events' do + member = create(:user) + create(:project_member, :developer, user: member, project: project) note = create(:note_on_issue, note: 'What an awesome day!', project: project) EventCreateService.new.leave_note(note, note.author) - end - it 'returns all events' do - get api("/projects/#{project.id}/events", user) + get api("/projects/#{project.id}/events", current_user) expect(response).to have_http_status(200) @@ -638,24 +695,90 @@ describe API::API, api: true do expect(last_event['action_name']).to eq('joined') expect(last_event['project_id'].to_i).to eq(project.id) - expect(last_event['author_username']).to eq(user3.username) - expect(last_event['author']['name']).to eq(user3.name) + expect(last_event['author_username']).to eq(member.username) + expect(last_event['author']['name']).to eq(member.name) end end - it 'returns a 404 error if not found' do - get api('/projects/42/events', user) + context 'when unauthenticated' do + it_behaves_like 'project events response' do + let(:project) { create(:project, :public) } + let(:current_user) { nil } + end + end - expect(response).to have_http_status(404) - expect(json_response['message']).to eq('404 Project Not Found') + context 'when authenticated' do + context 'valid request' do + it_behaves_like 'project events response' do + let(:current_user) { user } + end + end + + it 'returns a 404 error if not found' do + get api('/projects/42/events', user) + + expect(response).to have_http_status(404) + expect(json_response['message']).to eq('404 Project Not Found') + end + + it 'returns a 404 error if user is not a member' do + other_user = create(:user) + + get api("/projects/#{project.id}/events", other_user) + + expect(response).to have_http_status(404) + end end + end - it 'returns a 404 error if user is not a member' do - other_user = create(:user) + describe 'GET /projects/:id/users' do + shared_examples_for 'project users response' do + it 'returns the project users' do + member = create(:user) + create(:project_member, :developer, user: member, project: project) - get api("/projects/#{project.id}/events", other_user) + get api("/projects/#{project.id}/users", current_user) - expect(response).to have_http_status(404) + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(json_response.size).to eq(1) + + first_user = json_response.first + + expect(first_user['username']).to eq(member.username) + expect(first_user['name']).to eq(member.name) + expect(first_user.keys).to contain_exactly(*%w[name username id state avatar_url web_url]) + end + end + + context 'when unauthenticated' do + it_behaves_like 'project users response' do + let(:project) { create(:project, :public) } + let(:current_user) { nil } + end + end + + context 'when authenticated' do + context 'valid request' do + it_behaves_like 'project users response' do + let(:current_user) { user } + end + end + + it 'returns a 404 error if not found' do + get api('/projects/42/users', user) + + expect(response).to have_http_status(404) + expect(json_response['message']).to eq('404 Project Not Found') + end + + it 'returns a 404 error if user is not a member' do + other_user = create(:user) + + get api("/projects/#{project.id}/users", other_user) + + expect(response).to have_http_status(404) + end end end @@ -816,7 +939,7 @@ describe API::API, api: true do it 'is idempotent if not forked' do expect(project_fork_target.forked_from_project).to be_nil delete api("/projects/#{project_fork_target.id}/fork", admin) - expect(response).to have_http_status(200) + expect(response).to have_http_status(304) expect(project_fork_target.reload.forked_from_project).to be_nil end end @@ -833,7 +956,7 @@ describe API::API, api: true do post api("/projects/#{project.id}/share", user), group_id: group.id, group_access: Gitlab::Access::DEVELOPER, expires_at: expires_at end.to change { ProjectGroupLink.count }.by(1) - expect(response.status).to eq 201 + expect(response).to have_http_status(201) expect(json_response['group_id']).to eq(group.id) expect(json_response['group_access']).to eq(Gitlab::Access::DEVELOPER) expect(json_response['expires_at']).to eq(expires_at.to_s) @@ -841,18 +964,18 @@ describe API::API, api: true do it "returns a 400 error when group id is not given" do post api("/projects/#{project.id}/share", user), group_access: Gitlab::Access::DEVELOPER - expect(response.status).to eq 400 + expect(response).to have_http_status(400) end it "returns a 400 error when access level is not given" do post api("/projects/#{project.id}/share", user), group_id: group.id - expect(response.status).to eq 400 + expect(response).to have_http_status(400) end it "returns a 400 error when sharing is disabled" do project.namespace.update(share_with_group_lock: true) post api("/projects/#{project.id}/share", user), group_id: group.id, group_access: Gitlab::Access::DEVELOPER - expect(response.status).to eq 400 + expect(response).to have_http_status(400) end it 'returns a 404 error when user cannot read group' do @@ -860,19 +983,50 @@ describe API::API, api: true do post api("/projects/#{project.id}/share", user), group_id: private_group.id, group_access: Gitlab::Access::DEVELOPER - expect(response.status).to eq 404 + expect(response).to have_http_status(404) end it 'returns a 404 error when group does not exist' do post api("/projects/#{project.id}/share", user), group_id: 1234, group_access: Gitlab::Access::DEVELOPER - expect(response.status).to eq 404 + expect(response).to have_http_status(404) end - it "returns a 409 error when wrong params passed" do + it "returns a 400 error when wrong params passed" do post api("/projects/#{project.id}/share", user), group_id: group.id, group_access: 1234 - expect(response.status).to eq 409 - expect(json_response['message']).to eq 'Group access is not included in the list' + + expect(response).to have_http_status(400) + expect(json_response['error']).to eq 'group_access does not have a valid value' + end + end + + describe 'DELETE /projects/:id/share/:group_id' do + it 'returns 204 when deleting a group share' do + group = create(:group, :public) + create(:project_group_link, group: group, project: project) + + delete api("/projects/#{project.id}/share/#{group.id}", user) + + expect(response).to have_http_status(204) + expect(project.project_group_links).to be_empty + end + + it 'returns a 400 when group id is not an integer' do + delete api("/projects/#{project.id}/share/foo", user) + + expect(response).to have_http_status(400) + end + + it 'returns a 404 error when group link does not exist' do + delete api("/projects/#{project.id}/share/1234", user) + + expect(response).to have_http_status(404) + end + + it 'returns a 404 error when project does not exist' do + delete api("/projects/123/share/1234", user) + + expect(response).to have_http_status(404) end end @@ -888,35 +1042,37 @@ describe API::API, api: true do let!(:public) { create(:empty_project, :public, name: "public #{query}") } let!(:unfound_public) { create(:empty_project, :public, name: 'unfound public') } + shared_examples_for 'project search response' do |args = {}| + it 'returns project search responses' do + get api("/projects/search/#{query}", current_user) + + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(json_response.size).to eq(args[:results]) + json_response.each { |project| expect(project['name']).to match(args[:match_regex] || /.*query.*/) } + end + end + context 'when unauthenticated' do - it 'returns authentication error' do - get api("/projects/search/#{query}") - expect(response).to have_http_status(401) + it_behaves_like 'project search response', results: 1 do + let(:current_user) { nil } end end context 'when authenticated' do - it 'returns an array of projects' do - get api("/projects/search/#{query}", user) - expect(response).to have_http_status(200) - expect(json_response).to be_an Array - expect(json_response.size).to eq(6) - json_response.each {|project| expect(project['name']).to match(/.*query.*/)} + it_behaves_like 'project search response', results: 6 do + let(:current_user) { user } end end context 'when authenticated as a different user' do - it 'returns matching public projects' do - get api("/projects/search/#{query}", user2) - expect(response).to have_http_status(200) - expect(json_response).to be_an Array - expect(json_response.size).to eq(2) - json_response.each {|project| expect(project['name']).to match(/(internal|public) query/)} + it_behaves_like 'project search response', results: 2, match_regex: /(internal|public) query/ do + let(:current_user) { user2 } end end end - describe 'PUT /projects/:id̈́' do + describe 'PUT /projects/:id' do before { project } before { user } before { user3 } @@ -955,7 +1111,6 @@ describe API::API, api: true do it 'updates visibility_level from public to private' do project3.update_attributes({ visibility_level: Gitlab::VisibilityLevel::PUBLIC }) - project_param = { public: false } put api("/projects/#{project3.id}", user), project_param expect(response).to have_http_status(200) diff --git a/spec/requests/api/repositories_spec.rb b/spec/requests/api/repositories_spec.rb index c4dc2d9006a..c90b69e8ebb 100644 --- a/spec/requests/api/repositories_spec.rb +++ b/spec/requests/api/repositories_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' require 'mime/types' -describe API::API, api: true do +describe API::Repositories, api: true do include ApiHelpers include RepoHelpers include WorkhorseHelpers @@ -18,6 +18,7 @@ describe API::API, api: true do it "returns project commits" do get api("/projects/#{project.id}/repository/tree", user) + expect(response).to have_http_status(200) expect(json_response).to be_an Array @@ -43,6 +44,39 @@ describe API::API, api: true do end end + describe 'GET /projects/:id/repository/tree?recursive=1' do + context 'authorized user' do + before { project.team << [user2, :reporter] } + + it 'should return recursive project paths tree' do + get api("/projects/#{project.id}/repository/tree?recursive=1", user) + + expect(response.status).to eq(200) + + expect(json_response).to be_an Array + expect(json_response[4]['name']).to eq('html') + expect(json_response[4]['path']).to eq('files/html') + expect(json_response[4]['type']).to eq('tree') + expect(json_response[4]['mode']).to eq('040000') + end + + it 'returns a 404 for unknown ref' do + get api("/projects/#{project.id}/repository/tree?ref_name=foo&recursive=1", user) + expect(response).to have_http_status(404) + + expect(json_response).to be_an Object + json_response['message'] == '404 Tree Not Found' + end + end + + context "unauthorized user" do + it "does not return project commits" do + get api("/projects/#{project.id}/repository/tree?recursive=1") + expect(response).to have_http_status(401) + end + end + end + describe "GET /projects/:id/repository/blobs/:sha" do it "gets the raw file contents" do get api("/projects/#{project.id}/repository/blobs/master?filepath=README.md", user) diff --git a/spec/requests/api/runners_spec.rb b/spec/requests/api/runners_spec.rb index f46f016135e..99414270be6 100644 --- a/spec/requests/api/runners_spec.rb +++ b/spec/requests/api/runners_spec.rb @@ -226,7 +226,7 @@ describe API::Runners, api: true do context 'authorized user' do context 'when runner is shared' do it 'does not update runner' do - put api("/runners/#{shared_runner.id}", user) + put api("/runners/#{shared_runner.id}", user), description: 'test' expect(response).to have_http_status(403) end @@ -234,7 +234,7 @@ describe API::Runners, api: true do context 'when runner is not shared' do it 'does not update runner without access to it' do - put api("/runners/#{specific_runner.id}", user2) + put api("/runners/#{specific_runner.id}", user2), description: 'test' expect(response).to have_http_status(403) end diff --git a/spec/requests/api/services_spec.rb b/spec/requests/api/services_spec.rb index 375671bca4c..668e39f9dba 100644 --- a/spec/requests/api/services_spec.rb +++ b/spec/requests/api/services_spec.rb @@ -1,7 +1,8 @@ require "spec_helper" -describe API::API, api: true do +describe API::Services, api: true do include ApiHelpers + let(:user) { create(:user) } let(:admin) { create(:admin) } let(:user2) { create(:user) } @@ -56,8 +57,7 @@ describe API::API, api: true do # inject some properties into the service before do - project.build_missing_services - service_object = project.send(service_method) + service_object = project.find_or_initialize_service(service) service_object.properties = service_attrs service_object.save end @@ -89,4 +89,61 @@ describe API::API, api: true do end end end + + describe 'POST /projects/:id/services/:slug/trigger' do + let!(:project) { create(:empty_project) } + let(:service_name) { 'mattermost_slash_commands' } + + context 'no service is available' do + it 'returns a not found message' do + post api("/projects/#{project.id}/services/idonotexist/trigger") + + expect(response).to have_http_status(404) + expect(json_response["error"]).to eq("404 Not Found") + end + end + + context 'the service exists' do + let(:params) { { token: 'token' } } + + context 'the service is not active' do + let!(:inactive_service) do + project.create_mattermost_slash_commands_service( + active: false, + properties: { token: 'token' } + ) + end + + it 'when the service is inactive' do + post api("/projects/#{project.id}/services/mattermost_slash_commands/trigger"), params + + expect(response).to have_http_status(404) + end + end + + context 'the service is active' do + let!(:active_service) do + project.create_mattermost_slash_commands_service( + active: true, + properties: { token: 'token' } + ) + end + + it 'returns status 200' do + post api("/projects/#{project.id}/services/mattermost_slash_commands/trigger"), params + + expect(response).to have_http_status(200) + end + end + + context 'when the project can not be found' do + it 'returns a generic 404' do + post api("/projects/404/services/mattermost_slash_commands/trigger"), params + + expect(response).to have_http_status(404) + expect(json_response["message"]).to eq("404 Service Not Found") + end + end + end + end end diff --git a/spec/requests/api/session_spec.rb b/spec/requests/api/session_spec.rb index acad1365ace..794e2b5c04d 100644 --- a/spec/requests/api/session_spec.rb +++ b/spec/requests/api/session_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe API::API, api: true do +describe API::Session, api: true do include ApiHelpers let(:user) { create(:user) } @@ -67,22 +67,24 @@ describe API::API, api: true do end context "when empty password" do - it "returns authentication error" do + it "returns authentication error with email" do post api("/session"), email: user.email - expect(response).to have_http_status(401) - expect(json_response['email']).to be_nil - expect(json_response['private_token']).to be_nil + expect(response).to have_http_status(400) + end + + it "returns authentication error with username" do + post api("/session"), email: user.username + + expect(response).to have_http_status(400) end end context "when empty name" do it "returns authentication error" do post api("/session"), password: user.password - expect(response).to have_http_status(401) - expect(json_response['email']).to be_nil - expect(json_response['private_token']).to be_nil + expect(response).to have_http_status(400) end end end diff --git a/spec/requests/api/settings_spec.rb b/spec/requests/api/settings_spec.rb index 096a8ebab70..9a8d633d657 100644 --- a/spec/requests/api/settings_spec.rb +++ b/spec/requests/api/settings_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe API::API, 'Settings', api: true do +describe API::Settings, 'Settings', api: true do include ApiHelpers let(:user) { create(:user) } diff --git a/spec/requests/api/snippets_spec.rb b/spec/requests/api/snippets_spec.rb new file mode 100644 index 00000000000..f6fb6ea5506 --- /dev/null +++ b/spec/requests/api/snippets_spec.rb @@ -0,0 +1,157 @@ +require 'rails_helper' + +describe API::Snippets, api: true do + include ApiHelpers + let!(:user) { create(:user) } + + describe 'GET /snippets/' do + it 'returns snippets available' do + public_snippet = create(:personal_snippet, :public, author: user) + private_snippet = create(:personal_snippet, :private, author: user) + internal_snippet = create(:personal_snippet, :internal, author: user) + + get api("/snippets/", user) + + expect(response).to have_http_status(200) + expect(json_response.map { |snippet| snippet['id']} ).to contain_exactly( + public_snippet.id, + internal_snippet.id, + private_snippet.id) + expect(json_response.last).to have_key('web_url') + expect(json_response.last).to have_key('raw_url') + end + + it 'hides private snippets from regular user' do + create(:personal_snippet, :private) + + get api("/snippets/", user) + expect(response).to have_http_status(200) + expect(json_response.size).to eq(0) + end + end + + describe 'GET /snippets/public' do + let!(:other_user) { create(:user) } + let!(:public_snippet) { create(:personal_snippet, :public, author: user) } + let!(:private_snippet) { create(:personal_snippet, :private, author: user) } + let!(:internal_snippet) { create(:personal_snippet, :internal, author: user) } + let!(:public_snippet_other) { create(:personal_snippet, :public, author: other_user) } + let!(:private_snippet_other) { create(:personal_snippet, :private, author: other_user) } + let!(:internal_snippet_other) { create(:personal_snippet, :internal, author: other_user) } + + it 'returns all snippets with public visibility from all users' do + get api("/snippets/public", user) + + expect(response).to have_http_status(200) + expect(json_response.map { |snippet| snippet['id']} ).to contain_exactly( + public_snippet.id, + public_snippet_other.id) + expect(json_response.map{ |snippet| snippet['web_url']} ).to include( + "http://localhost/snippets/#{public_snippet.id}", + "http://localhost/snippets/#{public_snippet_other.id}") + expect(json_response.map{ |snippet| snippet['raw_url']} ).to include( + "http://localhost/snippets/#{public_snippet.id}/raw", + "http://localhost/snippets/#{public_snippet_other.id}/raw") + end + end + + describe 'GET /snippets/:id/raw' do + let(:snippet) { create(:personal_snippet, author: user) } + + it 'returns raw text' do + get api("/snippets/#{snippet.id}/raw", user) + + expect(response).to have_http_status(200) + expect(response.content_type).to eq 'text/plain' + expect(response.body).to eq(snippet.content) + end + + it 'returns 404 for invalid snippet id' do + delete api("/snippets/1234", user) + + expect(response).to have_http_status(404) + expect(json_response['message']).to eq('404 Snippet Not Found') + end + end + + describe 'POST /snippets/' do + let(:params) do + { + title: 'Test Title', + file_name: 'test.rb', + content: 'puts "hello world"', + visibility_level: Gitlab::VisibilityLevel::PUBLIC + } + end + + it 'creates a new snippet' do + expect do + post api("/snippets/", user), params + end.to change { PersonalSnippet.count }.by(1) + + expect(response).to have_http_status(201) + expect(json_response['title']).to eq(params[:title]) + expect(json_response['file_name']).to eq(params[:file_name]) + end + + it 'returns 400 for missing parameters' do + params.delete(:title) + + post api("/snippets/", user), params + + expect(response).to have_http_status(400) + end + end + + describe 'PUT /snippets/:id' do + let(:other_user) { create(:user) } + let(:public_snippet) { create(:personal_snippet, :public, author: user) } + it 'updates snippet' do + new_content = 'New content' + + put api("/snippets/#{public_snippet.id}", user), content: new_content + + expect(response).to have_http_status(200) + public_snippet.reload + expect(public_snippet.content).to eq(new_content) + end + + it 'returns 404 for invalid snippet id' do + put api("/snippets/1234", user), title: 'foo' + + expect(response).to have_http_status(404) + expect(json_response['message']).to eq('404 Snippet Not Found') + end + + it "returns 404 for another user's snippet" do + put api("/snippets/#{public_snippet.id}", other_user), title: 'fubar' + + expect(response).to have_http_status(404) + expect(json_response['message']).to eq('404 Snippet Not Found') + end + + it 'returns 400 for missing parameters' do + put api("/snippets/1234", user) + + expect(response).to have_http_status(400) + end + end + + describe 'DELETE /snippets/:id' do + let!(:public_snippet) { create(:personal_snippet, :public, author: user) } + it 'deletes snippet' do + expect do + delete api("/snippets/#{public_snippet.id}", user) + + expect(response).to have_http_status(204) + end.to change { PersonalSnippet.count }.by(-1) + end + + it 'returns 404 for invalid snippet id' do + delete api("/snippets/1234", user) + + expect(response).to have_http_status(404) + expect(json_response['message']).to eq('404 Snippet Not Found') + end + end +end diff --git a/spec/requests/api/system_hooks_spec.rb b/spec/requests/api/system_hooks_spec.rb index 6c9df21f598..b3e5afdadb1 100644 --- a/spec/requests/api/system_hooks_spec.rb +++ b/spec/requests/api/system_hooks_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe API::API, api: true do +describe API::SystemHooks, api: true do include ApiHelpers let(:user) { create(:user) } diff --git a/spec/requests/api/tags_spec.rb b/spec/requests/api/tags_spec.rb index d563883cd47..a1c32ae65ba 100644 --- a/spec/requests/api/tags_spec.rb +++ b/spec/requests/api/tags_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' require 'mime/types' -describe API::API, api: true do +describe API::Tags, api: true do include ApiHelpers include RepoHelpers @@ -15,6 +15,31 @@ describe API::API, api: true do let(:tag_name) { project.repository.tag_names.sort.reverse.first } let(:description) { 'Awesome release!' } + shared_examples_for 'repository tags' do + it 'returns the repository tags' do + get api("/projects/#{project.id}/repository/tags", current_user) + + expect(response).to have_http_status(200) + + first_tag = json_response.first + + expect(first_tag['name']).to eq(tag_name) + end + end + + context 'when unauthenticated' do + it_behaves_like 'repository tags' do + let(:project) { create(:project, :public) } + let(:current_user) { nil } + end + end + + context 'when authenticated' do + it_behaves_like 'repository tags' do + let(:current_user) { user } + end + end + context 'without releases' do it "returns an array of project tags" do get api("/projects/#{project.id}/repository/tags", user) @@ -45,17 +70,33 @@ describe API::API, api: true do describe 'GET /projects/:id/repository/tags/:tag_name' do let(:tag_name) { project.repository.tag_names.sort.reverse.first } - it 'returns a specific tag' do - get api("/projects/#{project.id}/repository/tags/#{tag_name}", user) + shared_examples_for 'repository tag' do + it 'returns the repository tag' do + get api("/projects/#{project.id}/repository/tags/#{tag_name}", current_user) + + expect(response).to have_http_status(200) + + expect(json_response['name']).to eq(tag_name) + end + + it 'returns 404 for an invalid tag name' do + get api("/projects/#{project.id}/repository/tags/foobar", current_user) - expect(response).to have_http_status(200) - expect(json_response['name']).to eq(tag_name) + expect(response).to have_http_status(404) + end end - it 'returns 404 for an invalid tag name' do - get api("/projects/#{project.id}/repository/tags/foobar", user) + context 'when unauthenticated' do + it_behaves_like 'repository tag' do + let(:project) { create(:project, :public) } + let(:current_user) { nil } + end + end - expect(response).to have_http_status(404) + context 'when authenticated' do + it_behaves_like 'repository tag' do + let(:current_user) { user } + end end end diff --git a/spec/requests/api/triggers_spec.rb b/spec/requests/api/triggers_spec.rb index 82bba1ce8a4..67ec3168679 100644 --- a/spec/requests/api/triggers_spec.rb +++ b/spec/requests/api/triggers_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe API::API do +describe API::Triggers do include ApiHelpers let(:user) { create(:user) } @@ -54,6 +54,13 @@ describe API::API do expect(pipeline.builds.size).to eq(5) end + it 'creates builds on webhook from other gitlab repository and branch' do + expect do + post api("/projects/#{project.id}/ref/master/trigger/builds?token=#{trigger_token}"), { ref: 'refs/heads/other-branch' } + end.to change(project.builds, :count).by(5) + expect(response).to have_http_status(201) + end + it 'returns bad request with no builds created if there\'s no commit for that ref' do post api("/projects/#{project.id}/trigger/builds"), options.merge(ref: 'other-branch') expect(response).to have_http_status(400) @@ -68,7 +75,7 @@ describe API::API do it 'validates variables to be a hash' do post api("/projects/#{project.id}/trigger/builds"), options.merge(variables: 'value', ref: 'master') expect(response).to have_http_status(400) - expect(json_response['message']).to eq('variables needs to be a hash') + expect(json_response['error']).to eq('variables is invalid') end it 'validates variables needs to be a map of key-valued strings' do diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb index ae8639d78d5..9e317f3a7e9 100644 --- a/spec/requests/api/users_spec.rb +++ b/spec/requests/api/users_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe API::API, api: true do +describe API::Users, api: true do include ApiHelpers let(:user) { create(:user) } @@ -48,6 +48,17 @@ describe API::API, api: true do end['username']).to eq(username) end + it "returns an array of blocked users" do + ldap_blocked_user + create(:user, state: 'blocked') + + get api("/users?blocked=true", user) + + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(json_response).to all(include('state' => /(blocked|ldap_blocked)/)) + end + it "returns one user" do get api("/users?username=#{omniauth_user.username}", user) expect(response).to have_http_status(200) @@ -69,6 +80,16 @@ describe API::API, api: true do expect(json_response.first.keys).to include 'last_sign_in_at' expect(json_response.first.keys).to include 'confirmed_at' end + + it "returns an array of external users" do + create(:user, external: true) + + get api("/users?external=true", admin) + + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(json_response).to all(include('external' => true)) + end end end @@ -87,7 +108,7 @@ describe API::API, api: true do it "returns a 404 error if user id not found" do get api("/users/9999", user) expect(response).to have_http_status(404) - expect(json_response['message']).to eq('404 Not found') + expect(json_response['message']).to eq('404 User Not Found') end it "returns a 404 for invalid ID" do @@ -338,7 +359,7 @@ describe API::API, api: true 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 Not found') + expect(json_response['message']).to eq('404 User Not Found') end it "returns a 404 if invalid ID" do @@ -366,6 +387,18 @@ describe API::API, api: true do to eq([Gitlab::Regex.namespace_regex_message]) end + it 'returns 400 if provider is missing for identity update' do + put api("/users/#{omniauth_user.id}", admin), extern_uid: '654321' + + expect(response).to have_http_status(400) + end + + it 'returns 400 if external UID is missing for identity update' do + put api("/users/#{omniauth_user.id}", admin), provider: 'ldap' + + expect(response).to have_http_status(400) + end + context "with existing user" do before do post api("/users", admin), { email: 'test@example.com', password: 'password', username: 'test', name: 'test' } @@ -393,14 +426,16 @@ describe API::API, api: true do it "does not create invalid ssh key" do post api("/users/#{user.id}/keys", admin), { title: "invalid key" } + expect(response).to have_http_status(400) - expect(json_response['message']).to eq('400 (Bad request) "key" not given') + expect(json_response['error']).to eq('key is missing') end it 'does not create key without title' do post api("/users/#{user.id}/keys", admin), key: 'some key' + expect(response).to have_http_status(400) - expect(json_response['message']).to eq('400 (Bad request) "title" not given') + expect(json_response['error']).to eq('title is missing') end it "creates ssh key" do @@ -416,7 +451,7 @@ describe API::API, api: true do end end - describe 'GET /user/:uid/keys' do + describe 'GET /user/:id/keys' do before { admin } context 'when unauthenticated' do @@ -444,7 +479,7 @@ describe API::API, api: true do end end - describe 'DELETE /user/:uid/keys/:id' do + describe 'DELETE /user/:id/keys/:key_id' do before { admin } context 'when unauthenticated' do @@ -485,8 +520,9 @@ describe API::API, api: true do it "does not create invalid email" do post api("/users/#{user.id}/emails", admin), {} + expect(response).to have_http_status(400) - expect(json_response['message']).to eq('400 (Bad request) "email" not given') + expect(json_response['error']).to eq('email is missing') end it "creates email" do @@ -503,7 +539,7 @@ describe API::API, api: true do end end - describe 'GET /user/:uid/emails' do + describe 'GET /user/:id/emails' do before { admin } context 'when unauthenticated' do @@ -537,7 +573,7 @@ describe API::API, api: true do end end - describe 'DELETE /user/:uid/emails/:id' do + describe 'DELETE /user/:id/emails/:email_id' do before { admin } context 'when unauthenticated' do @@ -615,20 +651,78 @@ describe API::API, api: true do end describe "GET /user" do - it "returns current user" do - get api("/user", user) - expect(response).to have_http_status(200) - expect(json_response['email']).to eq(user.email) - expect(json_response['is_admin']).to eq(user.is_admin?) - expect(json_response['can_create_project']).to eq(user.can_create_project?) - expect(json_response['can_create_group']).to eq(user.can_create_group?) - expect(json_response['projects_limit']).to eq(user.projects_limit) - expect(json_response['private_token']).to be_blank + let(:personal_access_token) { create(:personal_access_token, user: user).token } + + context 'with regular user' do + context 'with personal access token' do + it 'returns 403 without private token when sudo is defined' do + get api("/user?private_token=#{personal_access_token}&sudo=123") + + expect(response).to have_http_status(403) + end + end + + context 'with private token' do + it 'returns 403 without private token when sudo defined' do + get api("/user?private_token=#{user.private_token}&sudo=123") + + expect(response).to have_http_status(403) + end + end + + it 'returns current user without private token when sudo not defined' do + get api("/user", user) + + expect(response).to have_http_status(200) + expect(response).to match_response_schema('user/public') + expect(json_response['id']).to eq(user.id) + end end - it "returns 401 error if user is unauthenticated" do - get api("/user") - expect(response).to have_http_status(401) + context 'with admin' do + let(:admin_personal_access_token) { create(:personal_access_token, user: admin).token } + + context 'with personal access token' do + it 'returns 403 without private token when sudo defined' do + get api("/user?private_token=#{admin_personal_access_token}&sudo=#{user.id}") + + expect(response).to have_http_status(403) + end + + it 'returns initial current user without private token 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('user/public') + expect(json_response['id']).to eq(admin.id) + end + end + + context 'with private token' do + it 'returns sudoed user with private token when sudo defined' do + get api("/user?private_token=#{admin.private_token}&sudo=#{user.id}") + + expect(response).to have_http_status(200) + expect(response).to match_response_schema('user/login') + expect(json_response['id']).to eq(user.id) + end + + it 'returns initial current user without private token 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('user/public') + expect(json_response['id']).to eq(admin.id) + end + end + end + + context 'with unauthenticated user' do + it "returns 401 error if user is unauthenticated" do + get api("/user") + + expect(response).to have_http_status(401) + end end end @@ -652,7 +746,7 @@ describe API::API, api: true do end end - describe "GET /user/keys/:id" do + describe "GET /user/keys/:key_id" do it "returns single key" do user.keys << key user.save @@ -665,7 +759,7 @@ describe API::API, api: true do get api("/user/keys/42", user) expect(response).to have_http_status(404) - expect(json_response['message']).to eq('404 Not found') + expect(json_response['message']).to eq('404 Key Not Found') end it "returns 404 error if admin accesses user's ssh key" do @@ -674,7 +768,7 @@ describe API::API, api: true do admin get api("/user/keys/#{key.id}", admin) expect(response).to have_http_status(404) - expect(json_response['message']).to eq('404 Not found') + expect(json_response['message']).to eq('404 Key Not Found') end it "returns 404 for invalid ID" do @@ -700,14 +794,16 @@ describe API::API, api: true do it "does not create ssh key without key" do post api("/user/keys", user), title: 'title' + expect(response).to have_http_status(400) - expect(json_response['message']).to eq('400 (Bad request) "key" not given') + expect(json_response['error']).to eq('key is missing') end it 'does not create ssh key without title' do post api('/user/keys', user), key: 'some key' + expect(response).to have_http_status(400) - expect(json_response['message']).to eq('400 (Bad request) "title" not given') + expect(json_response['error']).to eq('title is missing') end it "does not create ssh key without title" do @@ -716,7 +812,7 @@ describe API::API, api: true do end end - describe "DELETE /user/keys/:id" do + describe "DELETE /user/keys/:key_id" do it "deletes existed key" do user.keys << key user.save @@ -726,9 +822,11 @@ describe API::API, api: true do expect(response).to have_http_status(200) end - it "returns success if key ID not found" do + it "returns 404 if key ID not found" do delete api("/user/keys/42", user) - expect(response).to have_http_status(200) + + expect(response).to have_http_status(404) + expect(json_response['message']).to eq('404 Key Not Found') end it "returns 401 error if unauthorized" do @@ -765,7 +863,7 @@ describe API::API, api: true do end end - describe "GET /user/emails/:id" do + describe "GET /user/emails/:email_id" do it "returns single email" do user.emails << email user.save @@ -777,7 +875,7 @@ describe API::API, api: true do it "returns 404 Not Found within invalid ID" do get api("/user/emails/42", user) expect(response).to have_http_status(404) - expect(json_response['message']).to eq('404 Not found') + expect(json_response['message']).to eq('404 Email Not Found') end it "returns 404 error if admin accesses user's email" do @@ -786,7 +884,7 @@ describe API::API, api: true do admin get api("/user/emails/#{email.id}", admin) expect(response).to have_http_status(404) - expect(json_response['message']).to eq('404 Not found') + expect(json_response['message']).to eq('404 Email Not Found') end it "returns 404 for invalid ID" do @@ -812,12 +910,13 @@ describe API::API, api: true do it "does not create email with invalid email" do post api("/user/emails", user), {} + expect(response).to have_http_status(400) - expect(json_response['message']).to eq('400 (Bad request) "email" not given') + expect(json_response['error']).to eq('email is missing') end end - describe "DELETE /user/emails/:id" do + describe "DELETE /user/emails/:email_id" do it "deletes existed email" do user.emails << email user.save @@ -827,9 +926,11 @@ describe API::API, api: true do expect(response).to have_http_status(200) end - it "returns success if email ID not found" do + it "returns 404 if email ID not found" do delete api("/user/emails/42", user) - expect(response).to have_http_status(200) + + expect(response).to have_http_status(404) + expect(json_response['message']).to eq('404 Email Not Found') end it "returns 401 error if unauthorized" do @@ -839,10 +940,10 @@ describe API::API, api: true do expect(response).to have_http_status(401) end - it "returns a 404 for invalid ID" do - delete api("/users/emails/ASDF", admin) + it "returns 400 for invalid ID" do + delete api("/user/emails/ASDF", admin) - expect(response).to have_http_status(404) + expect(response).to have_http_status(400) end end diff --git a/spec/requests/api/variables_spec.rb b/spec/requests/api/variables_spec.rb index 05fbdb909dc..7435f320607 100644 --- a/spec/requests/api/variables_spec.rb +++ b/spec/requests/api/variables_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe API::API, api: true do +describe API::Variables, api: true do include ApiHelpers let(:user) { create(:user) } diff --git a/spec/requests/api/version_spec.rb b/spec/requests/api/version_spec.rb index 54b69a0cae7..da1b2fda70e 100644 --- a/spec/requests/api/version_spec.rb +++ b/spec/requests/api/version_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe API::API, api: true do +describe API::Version, api: true do include ApiHelpers describe 'GET /version' do |