summaryrefslogtreecommitdiff
path: root/spec/requests
diff options
context:
space:
mode:
Diffstat (limited to 'spec/requests')
-rw-r--r--spec/requests/api/branches_spec.rb4
-rw-r--r--spec/requests/api/builds_spec.rb244
-rw-r--r--spec/requests/api/commit_status_spec.rb188
-rw-r--r--spec/requests/api/commits_spec.rb4
-rw-r--r--spec/requests/api/fork_spec.rb2
-rw-r--r--spec/requests/api/internal_spec.rb12
-rw-r--r--spec/requests/api/issues_spec.rb149
-rw-r--r--spec/requests/api/merge_requests_spec.rb100
-rw-r--r--spec/requests/api/notes_spec.rb56
-rw-r--r--spec/requests/api/project_members_spec.rb4
-rw-r--r--spec/requests/api/project_snippets_spec.rb18
-rw-r--r--spec/requests/api/projects_spec.rb86
-rw-r--r--spec/requests/api/repositories_spec.rb17
-rw-r--r--spec/requests/api/runners_spec.rb464
-rw-r--r--spec/requests/api/tags_spec.rb25
-rw-r--r--spec/requests/api/triggers_spec.rb139
-rw-r--r--spec/requests/api/users_spec.rb52
-rw-r--r--spec/requests/api/variables_spec.rb182
-rw-r--r--spec/requests/ci/api/builds_spec.rb191
-rw-r--r--spec/requests/ci/api/runners_spec.rb14
20 files changed, 1767 insertions, 184 deletions
diff --git a/spec/requests/api/branches_spec.rb b/spec/requests/api/branches_spec.rb
index 36461e84c3a..55582aa53d2 100644
--- a/spec/requests/api/branches_spec.rb
+++ b/spec/requests/api/branches_spec.rb
@@ -7,8 +7,8 @@ describe API::API, api: true do
let(:user) { create(:user) }
let(:user2) { create(:user) }
let!(:project) { create(:project, creator_id: user.id) }
- let!(:master) { create(:project_member, user: user, project: project, access_level: ProjectMember::MASTER) }
- let!(:guest) { create(:project_member, user: user2, project: project, access_level: ProjectMember::GUEST) }
+ let!(:master) { create(:project_member, :master, user: user, project: project) }
+ let!(:guest) { create(:project_member, :guest, user: user2, project: project) }
let!(:branch_name) { 'feature' }
let!(:branch_sha) { '0b4bc9a49b562e85de7cc9e834518ea6828729b9' }
diff --git a/spec/requests/api/builds_spec.rb b/spec/requests/api/builds_spec.rb
new file mode 100644
index 00000000000..967c34800d0
--- /dev/null
+++ b/spec/requests/api/builds_spec.rb
@@ -0,0 +1,244 @@
+require 'spec_helper'
+
+describe API::API, api: true do
+ include ApiHelpers
+
+ let(:user) { create(:user) }
+ let(:api_user) { user }
+ let(:user2) { create(:user) }
+ let!(:project) { create(:project, creator_id: user.id) }
+ let!(:developer) { create(:project_member, :developer, user: user, project: project) }
+ let!(:reporter) { create(:project_member, :reporter, user: user2, project: project) }
+ let(:commit) { create(:ci_commit, project: project)}
+ let(:build) { create(:ci_build, commit: commit) }
+
+ describe 'GET /projects/:id/builds ' do
+ let(:query) { '' }
+
+ before { get api("/projects/#{project.id}/builds?#{query}", api_user) }
+
+ context 'authorized user' do
+ it 'should return project builds' do
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ end
+
+ context 'filter project with one scope element' do
+ let(:query) { 'scope=pending' }
+
+ it do
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ end
+ end
+
+ context 'filter project with array of scope elements' do
+ let(:query) { 'scope[0]=pending&scope[1]=running' }
+
+ it do
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ end
+ end
+
+ context 'respond 400 when scope contains invalid state' do
+ let(:query) { 'scope[0]=pending&scope[1]=unknown_status' }
+
+ it { expect(response.status).to eq(400) }
+ end
+ end
+
+ context 'unauthorized user' do
+ let(:api_user) { nil }
+
+ it 'should not return project builds' do
+ expect(response.status).to eq(401)
+ end
+ end
+ end
+
+ describe 'GET /projects/:id/repository/commits/:sha/builds' do
+ before do
+ project.ensure_ci_commit(commit.sha)
+ get api("/projects/#{project.id}/repository/commits/#{commit.sha}/builds", api_user)
+ end
+
+ context 'authorized user' do
+ it 'should return project builds for specific commit' do
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ end
+ end
+
+ context 'unauthorized user' do
+ let(:api_user) { nil }
+
+ it 'should not return project builds' do
+ expect(response.status).to eq(401)
+ end
+ end
+ end
+
+ describe 'GET /projects/:id/builds/:build_id' do
+ before { get api("/projects/#{project.id}/builds/#{build.id}", api_user) }
+
+ context 'authorized user' do
+ it 'should return specific build data' do
+ expect(response.status).to eq(200)
+ expect(json_response['name']).to eq('test')
+ end
+ end
+
+ context 'unauthorized user' do
+ let(:api_user) { nil }
+
+ it 'should not return specific build data' do
+ expect(response.status).to eq(401)
+ end
+ end
+ end
+
+ describe 'GET /projects/:id/builds/:build_id/artifacts' do
+ before { get api("/projects/#{project.id}/builds/#{build.id}/artifacts", api_user) }
+
+ context 'build with artifacts' do
+ let(:build) { create(:ci_build, :artifacts, commit: commit) }
+
+ context 'authorized user' do
+ let(:download_headers) do
+ { 'Content-Transfer-Encoding'=>'binary',
+ 'Content-Disposition'=>'attachment; filename=ci_build_artifacts.zip' }
+ end
+
+ it 'should return specific build artifacts' do
+ expect(response.status).to eq(200)
+ expect(response.headers).to include(download_headers)
+ end
+ end
+
+ context 'unauthorized user' do
+ let(:api_user) { nil }
+
+ it 'should not return specific build artifacts' do
+ expect(response.status).to eq(401)
+ end
+ end
+ end
+
+ it 'should not return build artifacts if not uploaded' do
+ expect(response.status).to eq(404)
+ end
+ end
+
+ describe 'GET /projects/:id/builds/:build_id/trace' do
+ let(:build) { create(:ci_build, :trace, commit: commit) }
+
+ before { get api("/projects/#{project.id}/builds/#{build.id}/trace", api_user) }
+
+ context 'authorized user' do
+ it 'should return specific build trace' do
+ expect(response.status).to eq(200)
+ expect(response.body).to eq(build.trace)
+ end
+ end
+
+ context 'unauthorized user' do
+ let(:api_user) { nil }
+
+ it 'should not return specific build trace' do
+ expect(response.status).to eq(401)
+ end
+ end
+ end
+
+ describe 'POST /projects/:id/builds/:build_id/cancel' do
+ before { post api("/projects/#{project.id}/builds/#{build.id}/cancel", api_user) }
+
+ context 'authorized user' do
+ context 'user with :update_build persmission' do
+ it 'should cancel running or pending build' do
+ expect(response.status).to eq(201)
+ expect(project.builds.first.status).to eq('canceled')
+ end
+ end
+
+ context 'user without :update_build permission' do
+ let(:api_user) { user2 }
+
+ it 'should not cancel build' do
+ expect(response.status).to eq(403)
+ end
+ end
+ end
+
+ context 'unauthorized user' do
+ let(:api_user) { nil }
+
+ it 'should not cancel build' do
+ expect(response.status).to eq(401)
+ end
+ end
+ end
+
+ describe 'POST /projects/:id/builds/:build_id/retry' do
+ let(:build) { create(:ci_build, :canceled, commit: commit) }
+
+ before { post api("/projects/#{project.id}/builds/#{build.id}/retry", api_user) }
+
+ context 'authorized user' do
+ context 'user with :update_build permission' do
+ it 'should retry non-running build' do
+ expect(response.status).to eq(201)
+ expect(project.builds.first.status).to eq('canceled')
+ expect(json_response['status']).to eq('pending')
+ end
+ end
+
+ context 'user without :update_build permission' do
+ let(:api_user) { user2 }
+
+ it 'should not retry build' do
+ expect(response.status).to eq(403)
+ end
+ end
+ end
+
+ context 'unauthorized user' do
+ let(:api_user) { nil }
+
+ it 'should not retry build' do
+ expect(response.status).to eq(401)
+ end
+ end
+ end
+
+ describe 'POST /projects/:id/builds/:build_id/erase' do
+ before do
+ post api("/projects/#{project.id}/builds/#{build.id}/erase", user)
+ end
+
+ context 'build is erasable' do
+ let(:build) { create(:ci_build, :trace, :artifacts, :success, project: project, commit: commit) }
+
+ it 'should erase build content' do
+ expect(response.status).to eq 201
+ expect(build.trace).to be_empty
+ expect(build.artifacts_file.exists?).to be_falsy
+ expect(build.artifacts_metadata.exists?).to be_falsy
+ end
+
+ it 'should update build' do
+ expect(build.reload.erased_at).to be_truthy
+ expect(build.reload.erased_by).to eq user
+ end
+ end
+
+ context 'build is not erasable' do
+ let(:build) { create(:ci_build, :trace, project: project, commit: commit) }
+
+ it 'should respond with forbidden' do
+ expect(response.status).to eq 403
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/commit_status_spec.rb b/spec/requests/api/commit_status_spec.rb
index a28607bd240..429a24109fd 100644
--- a/spec/requests/api/commit_status_spec.rb
+++ b/spec/requests/api/commit_status_spec.rb
@@ -1,86 +1,126 @@
require 'spec_helper'
-describe API::API, api: true do
+describe API::CommitStatus, api: true do
include ApiHelpers
- let(:user) { create(:user) }
- let(:user2) { create(:user) }
- let!(:project) { create(:project, creator_id: user.id) }
- let!(:reporter) { create(:project_member, user: user, project: project, access_level: ProjectMember::REPORTER) }
- let!(:guest) { create(:project_member, user: user2, project: project, access_level: ProjectMember::GUEST) }
+
+ let!(:project) { create(:project) }
let(:commit) { project.repository.commit }
- let!(:ci_commit) { project.ensure_ci_commit(commit.id) }
let(:commit_status) { create(:commit_status, commit: ci_commit) }
+ let(:guest) { create_user(:guest) }
+ let(:reporter) { create_user(:reporter) }
+ let(:developer) { create_user(:developer) }
+ let(:sha) { commit.id }
+
describe "GET /projects/:id/repository/commits/:sha/statuses" do
- context "reporter user" do
- let(:statuses_id) { json_response.map { |status| status['id'] } }
-
- before do
- @status1 = create(:commit_status, commit: ci_commit, status: 'running')
- @status2 = create(:commit_status, commit: ci_commit, name: 'coverage', status: 'pending')
- @status3 = create(:commit_status, commit: ci_commit, name: 'coverage', ref: 'develop', status: 'running', allow_failure: true)
- @status4 = create(:commit_status, commit: ci_commit, name: 'coverage', status: 'success')
- @status5 = create(:commit_status, commit: ci_commit, ref: 'develop', status: 'success')
- @status6 = create(:commit_status, commit: ci_commit, status: 'success')
- end
+ let(:get_url) { "/projects/#{project.id}/repository/commits/#{sha}/statuses" }
- it "should return latest commit statuses" do
- get api("/projects/#{project.id}/repository/commits/#{commit.id}/statuses", user)
- expect(response.status).to eq(200)
+ context 'ci commit exists' do
+ let!(:ci_commit) { project.ensure_ci_commit(commit.id) }
- expect(json_response).to be_an Array
- expect(statuses_id).to contain_exactly(@status3.id, @status4.id, @status5.id, @status6.id)
- json_response.sort_by!{ |status| status['id'] }
- expect(json_response.map{ |status| status['allow_failure'] }).to eq([true, false, false, false])
+ it_behaves_like 'a paginated resources' do
+ let(:request) { get api(get_url, reporter) }
end
- it "should return all commit statuses" do
- get api("/projects/#{project.id}/repository/commits/#{commit.id}/statuses?all=1", user)
- expect(response.status).to eq(200)
+ context "reporter user" do
+ let(:statuses_id) { json_response.map { |status| status['id'] } }
- expect(json_response).to be_an Array
- expect(statuses_id).to contain_exactly(@status1.id, @status2.id, @status3.id, @status4.id, @status5.id, @status6.id)
- end
+ def create_status(opts = {})
+ create(:commit_status, { commit: ci_commit }.merge(opts))
+ end
- it "should return latest commit statuses for specific ref" do
- get api("/projects/#{project.id}/repository/commits/#{commit.id}/statuses?ref=develop", user)
- expect(response.status).to eq(200)
+ let!(:status1) { create_status(status: 'running') }
+ let!(:status2) { create_status(name: 'coverage', status: 'pending') }
+ let!(:status3) { create_status(ref: 'develop', status: 'running', allow_failure: true) }
+ let!(:status4) { create_status(name: 'coverage', status: 'success') }
+ let!(:status5) { create_status(name: 'coverage', ref: 'develop', status: 'success') }
+ let!(:status6) { create_status(status: 'success') }
- expect(json_response).to be_an Array
- expect(statuses_id).to contain_exactly(@status3.id, @status5.id)
+ context 'latest commit statuses' do
+ before { get api(get_url, reporter) }
+
+ it 'returns latest commit statuses' do
+ expect(response.status).to eq(200)
+
+ expect(json_response).to be_an Array
+ expect(statuses_id).to contain_exactly(status3.id, status4.id, status5.id, status6.id)
+ json_response.sort_by!{ |status| status['id'] }
+ expect(json_response.map{ |status| status['allow_failure'] }).to eq([true, false, false, false])
+ end
+ end
+
+ context 'all commit statuses' do
+ before { get api(get_url, reporter), all: 1 }
+
+ it 'returns all commit statuses' do
+ expect(response.status).to eq(200)
+
+ expect(json_response).to be_an Array
+ expect(statuses_id).to contain_exactly(status1.id, status2.id,
+ status3.id, status4.id,
+ status5.id, status6.id)
+ end
+ end
+
+ context 'latest commit statuses for specific ref' do
+ before { get api(get_url, reporter), ref: 'develop' }
+
+ it 'returns latest commit statuses for specific ref' do
+ expect(response.status).to eq(200)
+
+ expect(json_response).to be_an Array
+ expect(statuses_id).to contain_exactly(status3.id, status5.id)
+ end
+ end
+
+ context 'latest commit statues for specific name' do
+ before { get api(get_url, reporter), name: 'coverage' }
+
+ it 'return latest commit statuses for specific name' do
+ expect(response.status).to eq(200)
+
+ expect(json_response).to be_an Array
+ expect(statuses_id).to contain_exactly(status4.id, status5.id)
+ end
+ end
end
+ end
- it "should return latest commit statuses for specific name" do
- get api("/projects/#{project.id}/repository/commits/#{commit.id}/statuses?name=coverage", user)
- expect(response.status).to eq(200)
+ context 'ci commit does not exist' do
+ before { get api(get_url, reporter) }
+ it 'returns empty array' do
+ expect(response.status).to eq 200
expect(json_response).to be_an Array
- expect(statuses_id).to contain_exactly(@status3.id, @status4.id)
+ expect(json_response).to be_empty
end
end
context "guest user" do
+ before { get api(get_url, guest) }
+
it "should not return project commits" do
- get api("/projects/#{project.id}/repository/commits/#{commit.id}/statuses", user2)
expect(response.status).to eq(403)
end
end
context "unauthorized user" do
+ before { get api(get_url) }
+
it "should not return project commits" do
- get api("/projects/#{project.id}/repository/commits/#{commit.id}/statuses")
expect(response.status).to eq(401)
end
end
end
describe 'POST /projects/:id/statuses/:sha' do
- let(:post_url) { "/projects/#{project.id}/statuses/#{commit.id}" }
+ let(:post_url) { "/projects/#{project.id}/statuses/#{sha}" }
- context 'reporter user' do
- context 'should create commit status' do
- it 'with only required parameters' do
- post api(post_url, user), state: 'success'
+ context 'developer user' do
+ context 'only required parameters' do
+ before { post api(post_url, developer), state: 'success' }
+
+ it 'creates commit status' do
expect(response.status).to eq(201)
expect(json_response['sha']).to eq(commit.id)
expect(json_response['status']).to eq('success')
@@ -89,9 +129,17 @@ describe API::API, api: true do
expect(json_response['target_url']).to be_nil
expect(json_response['description']).to be_nil
end
+ end
+
+ context 'with all optional parameters' do
+ before do
+ optional_params = { state: 'success', context: 'coverage',
+ ref: 'develop', target_url: 'url', description: 'test' }
+
+ post api(post_url, developer), optional_params
+ end
- it 'with all optional parameters' do
- post api(post_url, user), state: 'success', context: 'coverage', ref: 'develop', target_url: 'url', description: 'test'
+ it 'creates commit status' do
expect(response.status).to eq(201)
expect(json_response['sha']).to eq(commit.id)
expect(json_response['status']).to eq('success')
@@ -102,36 +150,60 @@ describe API::API, api: true do
end
end
- context 'should not create commit status' do
- it 'with invalid state' do
- post api(post_url, user), state: 'invalid'
+ context 'invalid status' do
+ before { post api(post_url, developer), state: 'invalid' }
+
+ it 'does not create commit status' do
expect(response.status).to eq(400)
end
+ end
+
+ context 'request without state' do
+ before { post api(post_url, developer) }
- it 'without state' do
- post api(post_url, user)
+ it 'does not create commit status' do
expect(response.status).to eq(400)
end
+ end
- it 'invalid commit' do
- post api("/projects/#{project.id}/statuses/invalid_sha", user), state: 'running'
+ context 'invalid commit' do
+ let(:sha) { 'invalid_sha' }
+ before { post api(post_url, developer), state: 'running' }
+
+ it 'returns not found error' do
expect(response.status).to eq(404)
end
end
end
+ context 'reporter user' do
+ before { post api(post_url, reporter) }
+
+ it 'should not create commit status' do
+ expect(response.status).to eq(403)
+ end
+ end
+
context 'guest user' do
+ before { post api(post_url, guest) }
+
it 'should not create commit status' do
- post api(post_url, user2)
expect(response.status).to eq(403)
end
end
context 'unauthorized user' do
+ before { post api(post_url) }
+
it 'should not create commit status' do
- post api(post_url)
expect(response.status).to eq(401)
end
end
end
+
+ def create_user(access_level_trait)
+ user = create(:user)
+ create(:project_member, access_level_trait, user: user, project: project)
+ user
+ end
end
diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb
index 49acc3368f4..7ff21175c1b 100644
--- a/spec/requests/api/commits_spec.rb
+++ b/spec/requests/api/commits_spec.rb
@@ -6,8 +6,8 @@ describe API::API, api: true do
let(:user) { create(:user) }
let(:user2) { create(:user) }
let!(:project) { create(:project, creator_id: user.id) }
- let!(:master) { create(:project_member, user: user, project: project, access_level: ProjectMember::MASTER) }
- let!(:guest) { create(:project_member, user: user2, project: project, access_level: ProjectMember::GUEST) }
+ let!(:master) { create(:project_member, :master, user: user, project: project) }
+ let!(:guest) { create(:project_member, :guest, user: user2, project: project) }
let!(:note) { create(:note_on_commit, author: user, project: project, commit_id: project.repository.commit.id, note: 'a comment on a commit') }
let!(:another_note) { create(:note_on_commit, author: user, project: project, commit_id: project.repository.commit.id, note: 'another comment on a commit') }
diff --git a/spec/requests/api/fork_spec.rb b/spec/requests/api/fork_spec.rb
index 3fe7efff5ba..fa94e03ec32 100644
--- a/spec/requests/api/fork_spec.rb
+++ b/spec/requests/api/fork_spec.rb
@@ -12,7 +12,7 @@ describe API::API, api: true do
end
let(:project_user2) do
- create(:project_member, user: user2, project: project, access_level: ProjectMember::GUEST)
+ create(:project_member, :guest, user: user2, project: project)
end
describe 'POST /projects/fork/:id' do
diff --git a/spec/requests/api/internal_spec.rb b/spec/requests/api/internal_spec.rb
index 8d0ae1475c2..22802dd0e05 100644
--- a/spec/requests/api/internal_spec.rb
+++ b/spec/requests/api/internal_spec.rb
@@ -54,6 +54,18 @@ describe API::API, api: true do
project.team << [user, :developer]
end
+ context "git push with project.wiki" do
+ it 'responds with success' do
+ project_wiki = create(:project, name: 'my.wiki', path: 'my.wiki')
+ project_wiki.team << [user, :developer]
+
+ push(key, project_wiki)
+
+ expect(response.status).to eq(200)
+ expect(json_response["status"]).to be_truthy
+ end
+ end
+
context "git pull" do
it do
pull(key, project)
diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb
index 5e65ad18c0e..bb2ab058003 100644
--- a/spec/requests/api/issues_spec.rb
+++ b/spec/requests/api/issues_spec.rb
@@ -3,7 +3,11 @@ require 'spec_helper'
describe API::API, api: true do
include ApiHelpers
let(:user) { create(:user) }
- let!(:project) { create(:project, namespace: user.namespace ) }
+ let(:non_member) { create(:user) }
+ let(:author) { create(:author) }
+ let(:assignee) { create(:assignee) }
+ let(:admin) { create(:admin) }
+ let!(:project) { create(:project, :public, namespace: user.namespace ) }
let!(:closed_issue) do
create :closed_issue,
author: user,
@@ -12,6 +16,13 @@ describe API::API, api: true do
state: :closed,
milestone: milestone
end
+ let!(:confidential_issue) do
+ create :issue,
+ :confidential,
+ project: project,
+ author: author,
+ assignee: assignee
+ end
let!(:issue) do
create :issue,
author: user,
@@ -46,10 +57,10 @@ describe API::API, api: true do
expect(json_response.first['title']).to eq(issue.title)
end
- it "should add pagination headers" do
- get api("/issues?per_page=3", user)
+ it "should add 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>; rel="first", <http://www.example.com/api/v3/issues?page=1&per_page=3>; rel="last"'
+ '<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
@@ -123,10 +134,43 @@ describe API::API, api: true do
let(:base_url) { "/projects/#{project.id}" }
let(:title) { milestone.title }
- it "should return project issues" do
+ it 'should return project issues without confidential issues for non project members' do
+ get api("#{base_url}/issues", non_member)
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(2)
+ expect(json_response.first['title']).to eq(issue.title)
+ end
+
+ it 'should return project confidential issues for author' do
+ get api("#{base_url}/issues", author)
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(3)
+ expect(json_response.first['title']).to eq(issue.title)
+ end
+
+ it 'should return project confidential issues for assignee' do
+ get api("#{base_url}/issues", assignee)
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(3)
+ expect(json_response.first['title']).to eq(issue.title)
+ end
+
+ it 'should return project issues with confidential issues for project members' do
get api("#{base_url}/issues", user)
expect(response.status).to eq(200)
expect(json_response).to be_an Array
+ expect(json_response.length).to eq(3)
+ expect(json_response.first['title']).to eq(issue.title)
+ end
+
+ it 'should return project confidential issues for admin' do
+ get api("#{base_url}/issues", admin)
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(3)
expect(json_response.first['title']).to eq(issue.title)
end
@@ -206,6 +250,41 @@ describe API::API, api: true do
get api("/projects/#{project.id}/issues/54321", user)
expect(response.status).to eq(404)
end
+
+ context 'confidential issues' do
+ it "should return 404 for non project members" do
+ get api("/projects/#{project.id}/issues/#{confidential_issue.id}", non_member)
+ expect(response.status).to eq(404)
+ end
+
+ it "should return confidential issue for project members" do
+ get api("/projects/#{project.id}/issues/#{confidential_issue.id}", user)
+ expect(response.status).to eq(200)
+ expect(json_response['title']).to eq(confidential_issue.title)
+ expect(json_response['iid']).to eq(confidential_issue.iid)
+ end
+
+ it "should return confidential issue for author" do
+ get api("/projects/#{project.id}/issues/#{confidential_issue.id}", author)
+ expect(response.status).to eq(200)
+ expect(json_response['title']).to eq(confidential_issue.title)
+ expect(json_response['iid']).to eq(confidential_issue.iid)
+ end
+
+ it "should return confidential issue for assignee" do
+ get api("/projects/#{project.id}/issues/#{confidential_issue.id}", assignee)
+ expect(response.status).to eq(200)
+ expect(json_response['title']).to eq(confidential_issue.title)
+ expect(json_response['iid']).to eq(confidential_issue.iid)
+ end
+
+ it "should return confidential issue for admin" do
+ get api("/projects/#{project.id}/issues/#{confidential_issue.id}", admin)
+ expect(response.status).to eq(200)
+ expect(json_response['title']).to eq(confidential_issue.title)
+ expect(json_response['iid']).to eq(confidential_issue.iid)
+ end
+ end
end
describe "POST /projects/:id/issues" do
@@ -241,6 +320,37 @@ describe API::API, api: true do
end
end
+ describe 'POST /projects/:id/issues with spam filtering' do
+ before do
+ Grape::Endpoint.before_each do |endpoint|
+ allow(endpoint).to receive(:check_for_spam?).and_return(true)
+ allow(endpoint).to receive(:is_spam?).and_return(true)
+ end
+ end
+
+ let(:params) do
+ {
+ title: 'new issue',
+ description: 'content here',
+ labels: 'label, label2'
+ }
+ end
+
+ it "should not create a new project issue" do
+ expect { post api("/projects/#{project.id}/issues", user), params }.not_to change(Issue, :count)
+ expect(response.status).to eq(400)
+ expect(json_response['message']).to eq({ "error" => "Spam detected" })
+
+ spam_logs = SpamLog.all
+ expect(spam_logs.count).to eq(1)
+ expect(spam_logs[0].title).to eq('new issue')
+ expect(spam_logs[0].description).to eq('content here')
+ expect(spam_logs[0].user).to eq(user)
+ expect(spam_logs[0].noteable_type).to eq('Issue')
+ expect(spam_logs[0].project_id).to eq(project.id)
+ end
+ end
+
describe "PUT /projects/:id/issues/:issue_id to update only title" do
it "should update a project issue" do
put api("/projects/#{project.id}/issues/#{issue.id}", user),
@@ -263,6 +373,35 @@ describe API::API, api: true do
expect(response.status).to eq(400)
expect(json_response['message']['labels']['?']['title']).to eq(['is invalid'])
end
+
+ context 'confidential issues' do
+ it "should return 403 for non project members" do
+ put api("/projects/#{project.id}/issues/#{confidential_issue.id}", non_member),
+ title: 'updated title'
+ expect(response.status).to eq(403)
+ end
+
+ it "should update a confidential issue for project members" do
+ put api("/projects/#{project.id}/issues/#{confidential_issue.id}", user),
+ title: 'updated title'
+ expect(response.status).to eq(200)
+ expect(json_response['title']).to eq('updated title')
+ end
+
+ it "should update a confidential issue for author" do
+ put api("/projects/#{project.id}/issues/#{confidential_issue.id}", author),
+ title: 'updated title'
+ expect(response.status).to eq(200)
+ expect(json_response['title']).to eq('updated title')
+ end
+
+ it "should update a confidential issue for admin" do
+ put api("/projects/#{project.id}/issues/#{confidential_issue.id}", admin),
+ title: 'updated title'
+ expect(response.status).to eq(200)
+ expect(json_response['title']).to eq('updated title')
+ end
+ end
end
describe 'PUT /projects/:id/issues/:issue_id to update labels' do
diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb
index e194eb93cf4..4fd1df25568 100644
--- a/spec/requests/api/merge_requests_spec.rb
+++ b/spec/requests/api/merge_requests_spec.rb
@@ -10,6 +10,7 @@ describe API::API, api: true do
let!(:merge_request_merged) { create(:merge_request, state: "merged", author: user, assignee: user, source_project: project, target_project: project, title: "Merged test", created_at: base_time + 2.seconds) }
let!(:note) { create(:note_on_merge_request, author: user, project: project, noteable: merge_request, note: "a comment on a MR") }
let!(:note2) { create(:note_on_merge_request, author: user, project: project, noteable: merge_request, note: "another comment on a MR") }
+ let(:milestone) { create(:milestone, title: '1.0.0', project: project) }
before do
project.team << [user, :reporters]
@@ -109,12 +110,13 @@ describe API::API, api: true do
end
end
- describe "GET /projects/:id/merge_request/:merge_request_id" do
+ describe "GET /projects/:id/merge_requests/:merge_request_id" do
it "should return merge_request" do
- get api("/projects/#{project.id}/merge_request/#{merge_request.id}", user)
+ get api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user)
expect(response.status).to eq(200)
expect(json_response['title']).to eq(merge_request.title)
expect(json_response['iid']).to eq(merge_request.iid)
+ expect(json_response['merge_status']).to eq('can_be_merged')
end
it 'should return merge_request by iid' do
@@ -126,14 +128,14 @@ describe API::API, api: true do
end
it "should return a 404 error if merge_request_id not found" do
- get api("/projects/#{project.id}/merge_request/999", user)
+ get api("/projects/#{project.id}/merge_requests/999", user)
expect(response.status).to eq(404)
end
end
- describe 'GET /projects/:id/merge_request/:merge_request_id/commits' do
+ describe 'GET /projects/:id/merge_requests/:merge_request_id/commits' do
context 'valid merge request' do
- before { get api("/projects/#{project.id}/merge_request/#{merge_request.id}/commits", user) }
+ before { get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/commits", user) }
let(:commit) { merge_request.commits.first }
it { expect(response.status).to eq 200 }
@@ -143,20 +145,20 @@ describe API::API, api: true do
end
it 'returns a 404 when merge_request_id not found' do
- get api("/projects/#{project.id}/merge_request/999/commits", user)
+ get api("/projects/#{project.id}/merge_requests/999/commits", user)
expect(response.status).to eq(404)
end
end
- describe 'GET /projects/:id/merge_request/:merge_request_id/changes' do
+ describe 'GET /projects/:id/merge_requests/:merge_request_id/changes' do
it 'should return the change information of the merge_request' do
- get api("/projects/#{project.id}/merge_request/#{merge_request.id}/changes", user)
+ get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/changes", user)
expect(response.status).to eq 200
expect(json_response['changes'].size).to eq(merge_request.diffs.size)
end
it 'returns a 404 when merge_request_id not found' do
- get api("/projects/#{project.id}/merge_request/999/changes", user)
+ get api("/projects/#{project.id}/merge_requests/999/changes", user)
expect(response.status).to eq(404)
end
end
@@ -169,10 +171,12 @@ describe API::API, api: true do
source_branch: 'feature_conflict',
target_branch: 'master',
author: user,
- labels: 'label, label2'
+ labels: 'label, label2',
+ milestone_id: milestone.id
expect(response.status).to eq(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)
end
it "should return 422 when source_branch equals target_branch" do
@@ -311,19 +315,19 @@ describe API::API, api: true do
end
end
- describe "PUT /projects/:id/merge_request/:merge_request_id to close MR" do
+ describe "PUT /projects/:id/merge_requests/:merge_request_id to close MR" do
it "should return merge_request" do
- put api("/projects/#{project.id}/merge_request/#{merge_request.id}", user), state_event: "close"
+ put api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user), state_event: "close"
expect(response.status).to eq(200)
expect(json_response['state']).to eq('closed')
end
end
- describe "PUT /projects/:id/merge_request/:merge_request_id/merge" do
+ describe "PUT /projects/:id/merge_requests/:merge_request_id/merge" do
let(:ci_commit) { create(:ci_commit_without_jobs) }
it "should return merge_request in case of success" do
- put api("/projects/#{project.id}/merge_request/#{merge_request.id}/merge", user)
+ put api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge", user)
expect(response.status).to eq(200)
end
@@ -332,7 +336,7 @@ describe API::API, api: true do
allow_any_instance_of(MergeRequest).
to receive(:can_be_merged?).and_return(false)
- put api("/projects/#{project.id}/merge_request/#{merge_request.id}/merge", user)
+ put api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge", user)
expect(response.status).to eq(406)
expect(json_response['message']).to eq('Branch cannot be merged')
@@ -340,14 +344,14 @@ describe API::API, api: true do
it "should return 405 if merge_request is not open" do
merge_request.close
- put api("/projects/#{project.id}/merge_request/#{merge_request.id}/merge", user)
+ put api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge", user)
expect(response.status).to eq(405)
expect(json_response['message']).to eq('405 Method Not Allowed')
end
it "should return 405 if merge_request is a work in progress" do
merge_request.update_attribute(:title, "WIP: #{merge_request.title}")
- put api("/projects/#{project.id}/merge_request/#{merge_request.id}/merge", user)
+ put api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge", user)
expect(response.status).to eq(405)
expect(json_response['message']).to eq('405 Method Not Allowed')
end
@@ -355,7 +359,7 @@ describe API::API, api: true do
it "should return 401 if user has no permissions to merge" do
user2 = create(:user)
project.team << [user2, :reporter]
- put api("/projects/#{project.id}/merge_request/#{merge_request.id}/merge", user2)
+ put api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge", user2)
expect(response.status).to eq(401)
expect(json_response['message']).to eq('401 Unauthorized')
end
@@ -364,7 +368,7 @@ describe API::API, api: true do
allow_any_instance_of(MergeRequest).to receive(:ci_commit).and_return(ci_commit)
allow(ci_commit).to receive(:active?).and_return(true)
- put api("/projects/#{project.id}/merge_request/#{merge_request.id}/merge", user), merge_when_build_succeeds: true
+ put api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge", user), merge_when_build_succeeds: true
expect(response.status).to eq(200)
expect(json_response['title']).to eq('Test')
@@ -372,33 +376,39 @@ describe API::API, api: true do
end
end
- describe "PUT /projects/:id/merge_request/:merge_request_id" do
- it "should return merge_request" do
- put api("/projects/#{project.id}/merge_request/#{merge_request.id}", user), title: "New title"
+ describe "PUT /projects/:id/merge_requests/:merge_request_id" do
+ it "updates title and returns merge_request" do
+ put api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user), title: "New title"
expect(response.status).to eq(200)
expect(json_response['title']).to eq('New title')
end
- it "should return merge_request" do
- put api("/projects/#{project.id}/merge_request/#{merge_request.id}", user), description: "New description"
+ it "updates description and returns merge_request" do
+ put api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user), description: "New description"
expect(response.status).to eq(200)
expect(json_response['description']).to eq('New description')
end
+ it "updates milestone_id and returns merge_request" do
+ put api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user), milestone_id: milestone.id
+ expect(response.status).to eq(200)
+ expect(json_response['milestone']['id']).to eq(milestone.id)
+ end
+
it "should return 400 when source_branch is specified" do
- put api("/projects/#{project.id}/merge_request/#{merge_request.id}", user),
+ put api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user),
source_branch: "master", target_branch: "master"
expect(response.status).to eq(400)
end
it "should return merge_request with renamed target_branch" do
- put api("/projects/#{project.id}/merge_request/#{merge_request.id}", user), target_branch: "wiki"
+ put api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user), target_branch: "wiki"
expect(response.status).to eq(200)
expect(json_response['target_branch']).to eq('wiki')
end
it 'should return 400 on invalid label names' do
- put api("/projects/#{project.id}/merge_request/#{merge_request.id}",
+ put api("/projects/#{project.id}/merge_requests/#{merge_request.id}",
user),
title: 'new issue',
labels: 'label, ?'
@@ -407,11 +417,11 @@ describe API::API, api: true do
end
end
- describe "POST /projects/:id/merge_request/:merge_request_id/comments" do
+ describe "POST /projects/:id/merge_requests/:merge_request_id/comments" do
it "should return comment" do
original_count = merge_request.notes.size
- post api("/projects/#{project.id}/merge_request/#{merge_request.id}/comments", user), note: "My comment"
+ post api("/projects/#{project.id}/merge_requests/#{merge_request.id}/comments", user), note: "My comment"
expect(response.status).to eq(201)
expect(json_response['note']).to eq('My comment')
expect(json_response['author']['name']).to eq(user.name)
@@ -420,20 +430,20 @@ describe API::API, api: true do
end
it "should return 400 if note is missing" do
- post api("/projects/#{project.id}/merge_request/#{merge_request.id}/comments", user)
+ post api("/projects/#{project.id}/merge_requests/#{merge_request.id}/comments", user)
expect(response.status).to eq(400)
end
it "should return 404 if note is attached to non existent merge request" do
- post api("/projects/#{project.id}/merge_request/404/comments", user),
+ post api("/projects/#{project.id}/merge_requests/404/comments", user),
note: 'My comment'
expect(response.status).to eq(404)
end
end
- describe "GET :id/merge_request/:merge_request_id/comments" do
+ describe "GET :id/merge_requests/:merge_request_id/comments" do
it "should return merge_request comments ordered by created_at" do
- get api("/projects/#{project.id}/merge_request/#{merge_request.id}/comments", user)
+ get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/comments", user)
expect(response.status).to eq(200)
expect(json_response).to be_an Array
expect(json_response.length).to eq(2)
@@ -443,11 +453,33 @@ describe API::API, api: true do
end
it "should return a 404 error if merge_request_id not found" do
- get api("/projects/#{project.id}/merge_request/999/comments", user)
+ get api("/projects/#{project.id}/merge_requests/999/comments", user)
expect(response.status).to eq(404)
end
end
+ describe 'GET :id/merge_requests/:merge_request_id/closes_issues' do
+ it 'returns the issue that will be closed on merge' do
+ issue = create(:issue, project: project)
+ mr = merge_request.tap do |mr|
+ mr.update_attribute(:description, "Closes #{issue.to_reference(mr.project)}")
+ end
+
+ get api("/projects/#{project.id}/merge_requests/#{mr.id}/closes_issues", user)
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(1)
+ expect(json_response.first['id']).to eq(issue.id)
+ end
+
+ it 'returns an empty array when there are no issues to be closed' do
+ get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/closes_issues", user)
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(0)
+ end
+ end
+
def mr_with_later_created_and_updated_at_time
merge_request
merge_request.created_at += 1.hour
diff --git a/spec/requests/api/notes_spec.rb b/spec/requests/api/notes_spec.rb
index 8b177af4689..39f9a06fe1b 100644
--- a/spec/requests/api/notes_spec.rb
+++ b/spec/requests/api/notes_spec.rb
@@ -10,9 +10,32 @@ describe API::API, api: true do
let!(:issue_note) { create(:note, noteable: issue, project: project, author: user) }
let!(:merge_request_note) { create(:note, noteable: merge_request, project: project, author: user) }
let!(:snippet_note) { create(:note, noteable: snippet, project: project, author: user) }
+
+ # For testing the cross-reference of a private issue in a public issue
+ let(:private_user) { create(:user) }
+ let(:private_project) do
+ create(:project, namespace: private_user.namespace).
+ tap { |p| p.team << [private_user, :master] }
+ end
+ let(:private_issue) { create(:issue, project: private_project) }
+
+ let(:ext_proj) { create(:project, :public) }
+ let(:ext_issue) { create(:issue, project: ext_proj) }
+
+ let!(:cross_reference_note) do
+ create :note,
+ noteable: ext_issue, project: ext_proj,
+ note: "mentioned in issue #{private_issue.to_reference(ext_proj)}",
+ system: true
+ end
+
before { project.team << [user, :reporter] }
describe "GET /projects/:id/noteable/:noteable_id/notes" do
+ it_behaves_like 'a paginated resources' do
+ let(:request) { get api("/projects/#{project.id}/issues/#{issue.id}/notes", user) }
+ end
+
context "when noteable is an Issue" do
it "should return an array of issue notes" do
get api("/projects/#{project.id}/issues/#{issue.id}/notes", user)
@@ -25,6 +48,24 @@ describe API::API, api: true do
get api("/projects/#{project.id}/issues/123/notes", user)
expect(response.status).to eq(404)
end
+
+ context "that references a private issue" do
+ it "should return an empty array" do
+ get api("/projects/#{ext_proj.id}/issues/#{ext_issue.id}/notes", user)
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(json_response).to be_empty
+ end
+
+ context "and current user can view the note" do
+ it "should return an empty array" do
+ get api("/projects/#{ext_proj.id}/issues/#{ext_issue.id}/notes", private_user)
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(json_response.first['body']).to eq(cross_reference_note.note)
+ end
+ end
+ end
end
context "when noteable is a Snippet" do
@@ -68,6 +109,21 @@ describe API::API, api: true do
get api("/projects/#{project.id}/issues/#{issue.id}/notes/123", user)
expect(response.status).to eq(404)
end
+
+ context "that references a private issue" do
+ it "should return a 404 error" do
+ get api("/projects/#{ext_proj.id}/issues/#{ext_issue.id}/notes/#{cross_reference_note.id}", user)
+ expect(response.status).to eq(404)
+ end
+
+ context "and current user can view the note" do
+ it "should return an issue note by id" do
+ get api("/projects/#{ext_proj.id}/issues/#{ext_issue.id}/notes/#{cross_reference_note.id}", private_user)
+ expect(response.status).to eq(200)
+ expect(json_response['body']).to eq(cross_reference_note.note)
+ end
+ end
+ end
end
context "when noteable is a Snippet" do
diff --git a/spec/requests/api/project_members_spec.rb b/spec/requests/api/project_members_spec.rb
index 6358f6a2a4a..4301588b16a 100644
--- a/spec/requests/api/project_members_spec.rb
+++ b/spec/requests/api/project_members_spec.rb
@@ -6,8 +6,8 @@ describe API::API, api: true do
let(:user2) { create(:user) }
let(:user3) { create(:user) }
let(:project) { create(:project, creator_id: user.id, namespace: user.namespace) }
- let(:project_member) { create(:project_member, user: user, project: project, access_level: ProjectMember::MASTER) }
- let(:project_member2) { create(:project_member, user: user3, project: project, access_level: ProjectMember::DEVELOPER) }
+ let(:project_member) { create(:project_member, :master, user: user, project: project) }
+ let(:project_member2) { create(:project_member, :developer, user: user3, project: project) }
describe "GET /projects/:id/members" do
before { project_member }
diff --git a/spec/requests/api/project_snippets_spec.rb b/spec/requests/api/project_snippets_spec.rb
new file mode 100644
index 00000000000..3722ddf5a33
--- /dev/null
+++ b/spec/requests/api/project_snippets_spec.rb
@@ -0,0 +1,18 @@
+require 'rails_helper'
+
+describe API::API, api: true do
+ include ApiHelpers
+
+ 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)
+
+ expect(json_response).to have_key('expires_at')
+ expect(json_response['expires_at']).to be_nil
+ end
+ end
+end
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index 7f0f9454b10..a6699cdc81c 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -12,8 +12,8 @@ describe API::API, api: true do
let(:project2) { create(:project, path: 'project2', creator_id: user.id, namespace: user.namespace) }
let(:project3) { create(:project, path: 'project3', creator_id: user.id, namespace: user.namespace) }
let(:snippet) { create(:project_snippet, author: user, project: project, title: 'example') }
- let(:project_member) { create(:project_member, user: user, project: project, access_level: ProjectMember::MASTER) }
- let(:project_member2) { create(:project_member, user: user3, project: project, access_level: ProjectMember::DEVELOPER) }
+ let(:project_member) { create(:project_member, :master, user: user, project: project) }
+ let(:project_member2) { create(:project_member, :developer, user: user3, project: project) }
let(:user4) { create(:user) }
let(:project3) do
create(:project,
@@ -90,6 +90,29 @@ describe API::API, api: true do
end
end
+ context 'and using the visibility filter' do
+ it 'should filter based on private visibility param' do
+ get api('/projects', user), { visibility: 'private' }
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(user.namespace.projects.where(visibility_level: Gitlab::VisibilityLevel::PRIVATE).count)
+ end
+
+ it 'should filter based on internal visibility param' do
+ get api('/projects', user), { visibility: 'internal' }
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(user.namespace.projects.where(visibility_level: Gitlab::VisibilityLevel::INTERNAL).count)
+ end
+
+ it 'should filter based on public visibility param' do
+ get api('/projects', user), { visibility: 'public' }
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(user.namespace.projects.where(visibility_level: Gitlab::VisibilityLevel::PUBLIC).count)
+ end
+ end
+
context 'and using sorting' do
before do
project2
@@ -353,6 +376,20 @@ describe API::API, api: true do
end
end
+ describe "POST /projects/:id/uploads" do
+ before { project }
+
+ it "uploads the file and returns its info" do
+ post api("/projects/#{project.id}/uploads", user), file: fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "image/png")
+
+ expect(response.status).to be(201)
+ expect(json_response['alt']).to eq("dk")
+ expect(json_response['url']).to start_with("/uploads/")
+ expect(json_response['url']).to end_with("/dk.png")
+ expect(json_response['is_image']).to eq(true)
+ end
+ end
+
describe 'GET /projects/:id' do
before { project }
before { project_member }
@@ -382,6 +419,15 @@ describe API::API, api: true do
expect(response.status).to eq(404)
end
+ it 'should handle 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.status).to eq(200)
+ expect(json_response['name']).to eq(project.name)
+ end
+
describe 'permissions' do
context 'all projects' do
it 'Contains permission information' do
@@ -701,6 +747,42 @@ describe API::API, api: true do
end
end
+ describe "POST /projects/:id/share" do
+ let(:group) { create(:group) }
+
+ it "should share project with group" do
+ expect do
+ post api("/projects/#{project.id}/share", user), group_id: group.id, group_access: Gitlab::Access::DEVELOPER
+ end.to change { ProjectGroupLink.count }.by(1)
+
+ expect(response.status).to eq 201
+ expect(json_response['group_id']).to eq group.id
+ expect(json_response['group_access']).to eq Gitlab::Access::DEVELOPER
+ end
+
+ it "should return 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
+ end
+
+ it "should return 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
+ end
+
+ it "should return 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
+ end
+
+ it "should return a 409 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'
+ end
+ end
+
describe 'GET /projects/search/:query' do
let!(:query) { 'query'}
let!(:search) { create(:empty_project, name: query, creator_id: user.id, namespace: user.namespace) }
diff --git a/spec/requests/api/repositories_spec.rb b/spec/requests/api/repositories_spec.rb
index 4911cdd9da6..7cf4a01d76b 100644
--- a/spec/requests/api/repositories_spec.rb
+++ b/spec/requests/api/repositories_spec.rb
@@ -4,12 +4,13 @@ require 'mime/types'
describe API::API, api: true do
include ApiHelpers
include RepoHelpers
+ include WorkhorseHelpers
let(:user) { create(:user) }
let(:user2) { create(:user) }
let!(:project) { create(:project, creator_id: user.id) }
- let!(:master) { create(:project_member, user: user, project: project, access_level: ProjectMember::MASTER) }
- let!(:guest) { create(:project_member, user: user2, project: project, access_level: ProjectMember::GUEST) }
+ let!(:master) { create(:project_member, :master, user: user, project: project) }
+ let!(:guest) { create(:project_member, :guest, user: user2, project: project) }
describe "GET /projects/:id/repository/tree" do
context "authorized user" do
@@ -91,21 +92,27 @@ describe API::API, api: true do
get api("/projects/#{project.id}/repository/archive", user)
repo_name = project.repository.name.gsub("\.git", "")
expect(response.status).to eq(200)
- expect(json_response['ArchivePath']).to match(/#{repo_name}\-[^\.]+\.tar.gz/)
+ type, params = workhorse_send_data
+ expect(type).to eq('git-archive')
+ expect(params['ArchivePath']).to match(/#{repo_name}\-[^\.]+\.tar.gz/)
end
it "should get the archive.zip" do
get api("/projects/#{project.id}/repository/archive.zip", user)
repo_name = project.repository.name.gsub("\.git", "")
expect(response.status).to eq(200)
- expect(json_response['ArchivePath']).to match(/#{repo_name}\-[^\.]+\.zip/)
+ type, params = workhorse_send_data
+ expect(type).to eq('git-archive')
+ expect(params['ArchivePath']).to match(/#{repo_name}\-[^\.]+\.zip/)
end
it "should get the archive.tar.bz2" do
get api("/projects/#{project.id}/repository/archive.tar.bz2", user)
repo_name = project.repository.name.gsub("\.git", "")
expect(response.status).to eq(200)
- expect(json_response['ArchivePath']).to match(/#{repo_name}\-[^\.]+\.tar.bz2/)
+ type, params = workhorse_send_data
+ expect(type).to eq('git-archive')
+ expect(params['ArchivePath']).to match(/#{repo_name}\-[^\.]+\.tar.bz2/)
end
it "should return 404 for invalid sha" do
diff --git a/spec/requests/api/runners_spec.rb b/spec/requests/api/runners_spec.rb
new file mode 100644
index 00000000000..3af61d4b335
--- /dev/null
+++ b/spec/requests/api/runners_spec.rb
@@ -0,0 +1,464 @@
+require 'spec_helper'
+
+describe API::Runners, api: true do
+ include ApiHelpers
+
+ let(:admin) { create(:user, :admin) }
+ let(:user) { create(:user) }
+ let(:user2) { create(:user) }
+
+ let(:project) { create(:project, creator_id: user.id) }
+ let(:project2) { create(:project, creator_id: user.id) }
+
+ let!(:shared_runner) { create(:ci_runner, :shared) }
+ let!(:unused_specific_runner) { create(:ci_runner) }
+
+ let!(:specific_runner) do
+ create(:ci_runner).tap do |runner|
+ create(:ci_runner_project, runner: runner, project: project)
+ end
+ end
+
+ let!(:two_projects_runner) do
+ create(:ci_runner).tap do |runner|
+ create(:ci_runner_project, runner: runner, project: project)
+ create(:ci_runner_project, runner: runner, project: project2)
+ end
+ end
+
+ before do
+ # Set project access for users
+ create(:project_member, :master, user: user, project: project)
+ create(:project_member, :master, user: user, project: project2)
+ create(:project_member, :reporter, user: user2, project: project)
+ end
+
+ describe 'GET /runners' do
+ context 'authorized user' do
+ it 'should return user available runners' do
+ get api('/runners', user)
+ shared = json_response.any?{ |r| r['is_shared'] }
+
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(shared).to be_falsey
+ end
+
+ it 'should filter runners by scope' do
+ get api('/runners?scope=active', user)
+ shared = json_response.any?{ |r| r['is_shared'] }
+
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(shared).to be_falsey
+ end
+
+ it 'should avoid filtering if scope is invalid' do
+ get api('/runners?scope=unknown', user)
+ expect(response.status).to eq(400)
+ end
+ end
+
+ context 'unauthorized user' do
+ it 'should not return runners' do
+ get api('/runners')
+
+ expect(response.status).to eq(401)
+ end
+ end
+ end
+
+ describe 'GET /runners/all' do
+ context 'authorized user' do
+ context 'with admin privileges' do
+ it 'should return all runners' do
+ get api('/runners/all', admin)
+ shared = json_response.any?{ |r| r['is_shared'] }
+
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(shared).to be_truthy
+ end
+ end
+
+ context 'without admin privileges' do
+ it 'should not return runners list' do
+ get api('/runners/all', user)
+
+ expect(response.status).to eq(403)
+ end
+ end
+
+ it 'should filter runners by scope' do
+ get api('/runners/all?scope=specific', admin)
+ shared = json_response.any?{ |r| r['is_shared'] }
+
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(shared).to be_falsey
+ end
+
+ it 'should avoid filtering if scope is invalid' do
+ get api('/runners?scope=unknown', admin)
+ expect(response.status).to eq(400)
+ end
+ end
+
+ context 'unauthorized user' do
+ it 'should not return runners' do
+ get api('/runners')
+
+ expect(response.status).to eq(401)
+ end
+ end
+ end
+
+ describe 'GET /runners/:id' do
+ context 'admin user' do
+ context 'when runner is shared' do
+ it "should return runner's details" do
+ get api("/runners/#{shared_runner.id}", admin)
+
+ expect(response.status).to eq(200)
+ expect(json_response['description']).to eq(shared_runner.description)
+ end
+ end
+
+ context 'when runner is not shared' do
+ it "should return runner's details" do
+ get api("/runners/#{specific_runner.id}", admin)
+
+ expect(response.status).to eq(200)
+ expect(json_response['description']).to eq(specific_runner.description)
+ end
+ end
+
+ it 'should return 404 if runner does not exists' do
+ get api('/runners/9999', admin)
+
+ expect(response.status).to eq(404)
+ end
+ end
+
+ context "runner project's administrative user" do
+ context 'when runner is not shared' do
+ it "should return runner's details" do
+ get api("/runners/#{specific_runner.id}", user)
+
+ expect(response.status).to eq(200)
+ expect(json_response['description']).to eq(specific_runner.description)
+ end
+ end
+
+ context 'when runner is shared' do
+ it "should return runner's details" do
+ get api("/runners/#{shared_runner.id}", user)
+
+ expect(response.status).to eq(200)
+ expect(json_response['description']).to eq(shared_runner.description)
+ end
+ end
+ end
+
+ context 'other authorized user' do
+ it "should not return runner's details" do
+ get api("/runners/#{specific_runner.id}", user2)
+
+ expect(response.status).to eq(403)
+ end
+ end
+
+ context 'unauthorized user' do
+ it "should not return runner's details" do
+ get api("/runners/#{specific_runner.id}")
+
+ expect(response.status).to eq(401)
+ end
+ end
+ end
+
+ describe 'PUT /runners/:id' do
+ context 'admin user' do
+ context 'when runner is shared' do
+ it 'should update runner' do
+ description = shared_runner.description
+ active = shared_runner.active
+
+ put api("/runners/#{shared_runner.id}", admin), description: "#{description}_updated", active: !active,
+ tag_list: ['ruby2.1', 'pgsql', 'mysql']
+ shared_runner.reload
+
+ expect(response.status).to eq(200)
+ expect(shared_runner.description).to eq("#{description}_updated")
+ expect(shared_runner.active).to eq(!active)
+ expect(shared_runner.tag_list).to include('ruby2.1', 'pgsql', 'mysql')
+ end
+ end
+
+ context 'when runner is not shared' do
+ it 'should update runner' do
+ description = specific_runner.description
+ put api("/runners/#{specific_runner.id}", admin), description: 'test'
+ specific_runner.reload
+
+ expect(response.status).to eq(200)
+ expect(specific_runner.description).to eq('test')
+ expect(specific_runner.description).not_to eq(description)
+ end
+ end
+
+ it 'should return 404 if runner does not exists' do
+ put api('/runners/9999', admin), description: 'test'
+
+ expect(response.status).to eq(404)
+ end
+ end
+
+ context 'authorized user' do
+ context 'when runner is shared' do
+ it 'should not update runner' do
+ put api("/runners/#{shared_runner.id}", user)
+
+ expect(response.status).to eq(403)
+ end
+ end
+
+ context 'when runner is not shared' do
+ it 'should not update runner without access to it' do
+ put api("/runners/#{specific_runner.id}", user2)
+
+ expect(response.status).to eq(403)
+ end
+
+ it 'should update runner with access to it' do
+ description = specific_runner.description
+ put api("/runners/#{specific_runner.id}", admin), description: 'test'
+ specific_runner.reload
+
+ expect(response.status).to eq(200)
+ expect(specific_runner.description).to eq('test')
+ expect(specific_runner.description).not_to eq(description)
+ end
+ end
+ end
+
+ context 'unauthorized user' do
+ it 'should not delete runner' do
+ put api("/runners/#{specific_runner.id}")
+
+ expect(response.status).to eq(401)
+ end
+ end
+ end
+
+ describe 'DELETE /runners/:id' do
+ context 'admin user' do
+ context 'when runner is shared' do
+ it 'should delete runner' do
+ expect do
+ delete api("/runners/#{shared_runner.id}", admin)
+ end.to change{ Ci::Runner.shared.count }.by(-1)
+ expect(response.status).to eq(200)
+ end
+ end
+
+ context 'when runner is not shared' do
+ it 'should delete unused runner' do
+ expect do
+ delete api("/runners/#{unused_specific_runner.id}", admin)
+ end.to change{ Ci::Runner.specific.count }.by(-1)
+ expect(response.status).to eq(200)
+ end
+
+ it 'should delete used runner' do
+ expect do
+ delete api("/runners/#{specific_runner.id}", admin)
+ end.to change{ Ci::Runner.specific.count }.by(-1)
+ expect(response.status).to eq(200)
+ end
+ end
+
+ it 'should return 404 if runner does not exists' do
+ delete api('/runners/9999', admin)
+
+ expect(response.status).to eq(404)
+ end
+ end
+
+ context 'authorized user' do
+ context 'when runner is shared' do
+ it 'should not delete runner' do
+ delete api("/runners/#{shared_runner.id}", user)
+ expect(response.status).to eq(403)
+ end
+ end
+
+ context 'when runner is not shared' do
+ it 'should not delete runner without access to it' do
+ delete api("/runners/#{specific_runner.id}", user2)
+ expect(response.status).to eq(403)
+ end
+
+ it 'should not delete runner with more than one associated project' do
+ delete api("/runners/#{two_projects_runner.id}", user)
+ expect(response.status).to eq(403)
+ end
+
+ it 'should delete runner for one owned project' do
+ expect do
+ delete api("/runners/#{specific_runner.id}", user)
+ end.to change{ Ci::Runner.specific.count }.by(-1)
+ expect(response.status).to eq(200)
+ end
+ end
+ end
+
+ context 'unauthorized user' do
+ it 'should not delete runner' do
+ delete api("/runners/#{specific_runner.id}")
+
+ expect(response.status).to eq(401)
+ end
+ end
+ end
+
+ describe 'GET /projects/:id/runners' do
+ context 'authorized user with master privileges' do
+ it "should return project's runners" do
+ get api("/projects/#{project.id}/runners", user)
+ shared = json_response.any?{ |r| r['is_shared'] }
+
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(shared).to be_truthy
+ end
+ end
+
+ context 'authorized user without master privileges' do
+ it "should not return project's runners" do
+ get api("/projects/#{project.id}/runners", user2)
+
+ expect(response.status).to eq(403)
+ end
+ end
+
+ context 'unauthorized user' do
+ it "should not return project's runners" do
+ get api("/projects/#{project.id}/runners")
+
+ expect(response.status).to eq(401)
+ end
+ end
+ end
+
+ describe 'POST /projects/:id/runners' do
+ context 'authorized user' do
+ it 'should enable specific runner' do
+ specific_runner2 = create(:ci_runner).tap do |runner|
+ create(:ci_runner_project, runner: runner, project: project2)
+ end
+
+ expect do
+ post api("/projects/#{project.id}/runners", user), runner_id: specific_runner2.id
+ end.to change{ project.runners.count }.by(+1)
+ expect(response.status).to eq(201)
+ end
+
+ it 'should avoid changes when enabling already enabled runner' do
+ expect do
+ post api("/projects/#{project.id}/runners", user), runner_id: specific_runner.id
+ end.to change{ project.runners.count }.by(0)
+ expect(response.status).to eq(201)
+ end
+
+ it 'should not enable shared runner' do
+ post api("/projects/#{project.id}/runners", user), runner_id: shared_runner.id
+
+ expect(response.status).to eq(403)
+ end
+
+ context 'user is admin' do
+ it 'should enable any specific runner' do
+ expect do
+ post api("/projects/#{project.id}/runners", admin), runner_id: unused_specific_runner.id
+ end.to change{ project.runners.count }.by(+1)
+ expect(response.status).to eq(201)
+ end
+ end
+
+ context 'user is not admin' do
+ it 'should not enable runner without access to' do
+ post api("/projects/#{project.id}/runners", user), runner_id: unused_specific_runner.id
+
+ expect(response.status).to eq(403)
+ end
+ end
+
+ it 'should raise an error when no runner_id param is provided' do
+ post api("/projects/#{project.id}/runners", admin)
+
+ expect(response.status).to eq(400)
+ end
+ end
+
+ context 'authorized user without permissions' do
+ it 'should not enable runner' do
+ post api("/projects/#{project.id}/runners", user2)
+
+ expect(response.status).to eq(403)
+ end
+ end
+
+ context 'unauthorized user' do
+ it 'should not enable runner' do
+ post api("/projects/#{project.id}/runners")
+
+ expect(response.status).to eq(401)
+ end
+ end
+ end
+
+ describe 'DELETE /projects/:id/runners/:runner_id' do
+ context 'authorized user' do
+ context 'when runner have more than one associated projects' do
+ it "should disable project's runner" do
+ expect do
+ delete api("/projects/#{project.id}/runners/#{two_projects_runner.id}", user)
+ end.to change{ project.runners.count }.by(-1)
+ expect(response.status).to eq(200)
+ end
+ end
+
+ context 'when runner have one associated projects' do
+ it "should not disable project's runner" do
+ expect do
+ delete api("/projects/#{project.id}/runners/#{specific_runner.id}", user)
+ end.to change{ project.runners.count }.by(0)
+ expect(response.status).to eq(403)
+ end
+ end
+
+ it 'should return 404 is runner is not found' do
+ delete api("/projects/#{project.id}/runners/9999", user)
+
+ expect(response.status).to eq(404)
+ end
+ end
+
+ context 'authorized user without permissions' do
+ it "should not disable project's runner" do
+ delete api("/projects/#{project.id}/runners/#{specific_runner.id}", user2)
+
+ expect(response.status).to eq(403)
+ end
+ end
+
+ context 'unauthorized user' do
+ it "should not disable project's runner" do
+ delete api("/projects/#{project.id}/runners/#{specific_runner.id}")
+
+ expect(response.status).to eq(401)
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/tags_spec.rb b/spec/requests/api/tags_spec.rb
index 17f2643fd45..a15be07ed57 100644
--- a/spec/requests/api/tags_spec.rb
+++ b/spec/requests/api/tags_spec.rb
@@ -8,8 +8,8 @@ describe API::API, api: true do
let(:user) { create(:user) }
let(:user2) { create(:user) }
let!(:project) { create(:project, creator_id: user.id) }
- let!(:master) { create(:project_member, user: user, project: project, access_level: ProjectMember::MASTER) }
- let!(:guest) { create(:project_member, user: user2, project: project, access_level: ProjectMember::GUEST) }
+ let!(:master) { create(:project_member, :master, user: user, project: project) }
+ let!(:guest) { create(:project_member, :guest, user: user2, project: project) }
describe "GET /projects/:id/repository/tags" do
let(:tag_name) { project.repository.tag_names.sort.reverse.first }
@@ -65,6 +65,27 @@ describe API::API, api: true do
end
end
+ describe 'DELETE /projects/:id/repository/tags/:tag_name' do
+ let(:tag_name) { project.repository.tag_names.sort.reverse.first }
+
+ before do
+ allow_any_instance_of(Repository).to receive(:rm_tag).and_return(true)
+ end
+
+ context 'delete tag' do
+ it 'should delete an existing tag' do
+ delete api("/projects/#{project.id}/repository/tags/#{tag_name}", user)
+ expect(response.status).to eq(200)
+ expect(json_response['tag_name']).to eq(tag_name)
+ end
+
+ it 'should raise 404 if the tag does not exist' do
+ delete api("/projects/#{project.id}/repository/tags/foobar", user)
+ expect(response.status).to eq(404)
+ end
+ end
+ end
+
context 'annotated tag' do
it 'should create a new annotated tag' do
# Identity must be set in .gitconfig to create annotated tag.
diff --git a/spec/requests/api/triggers_spec.rb b/spec/requests/api/triggers_spec.rb
index 314bd7ddc59..0510b77a39b 100644
--- a/spec/requests/api/triggers_spec.rb
+++ b/spec/requests/api/triggers_spec.rb
@@ -3,11 +3,19 @@ require 'spec_helper'
describe API::API do
include ApiHelpers
+ let(:user) { create(:user) }
+ let(:user2) { create(:user) }
+ let!(:trigger_token) { 'secure_token' }
+ let!(:trigger_token_2) { 'secure_token_2' }
+ let!(:project) { create(:project, creator_id: user.id) }
+ let!(:master) { create(:project_member, :master, user: user, project: project) }
+ let!(:developer) { create(:project_member, :developer, user: user2, project: project) }
+ let!(:trigger) { create(:ci_trigger, project: project, token: trigger_token) }
+ let!(:trigger2) { create(:ci_trigger, project: project, token: trigger_token_2) }
+ let!(:trigger_request) { create(:ci_trigger_request, trigger: trigger, created_at: '2015-01-01 12:13:14') }
+
describe 'POST /projects/:project_id/trigger' do
- let!(:trigger_token) { 'secure token' }
- let!(:project) { FactoryGirl.create(:project) }
- let!(:project2) { FactoryGirl.create(:empty_project) }
- let!(:trigger) { FactoryGirl.create(:ci_trigger, project: project, token: trigger_token) }
+ let!(:project2) { create(:empty_project) }
let(:options) do
{
token: trigger_token
@@ -77,4 +85,127 @@ describe API::API do
end
end
end
+
+ describe 'GET /projects/:id/triggers' do
+ context 'authenticated user with valid permissions' do
+ it 'should return list of triggers' do
+ get api("/projects/#{project.id}/triggers", user)
+
+ expect(response.status).to eq(200)
+ expect(json_response).to be_a(Array)
+ expect(json_response[0]).to have_key('token')
+ end
+ end
+
+ context 'authenticated user with invalid permissions' do
+ it 'should not return triggers list' do
+ get api("/projects/#{project.id}/triggers", user2)
+
+ expect(response.status).to eq(403)
+ end
+ end
+
+ context 'unauthenticated user' do
+ it 'should not return triggers list' do
+ get api("/projects/#{project.id}/triggers")
+
+ expect(response.status).to eq(401)
+ end
+ end
+ end
+
+ describe 'GET /projects/:id/triggers/:token' do
+ context 'authenticated user with valid permissions' do
+ it 'should return trigger details' do
+ get api("/projects/#{project.id}/triggers/#{trigger.token}", user)
+
+ expect(response.status).to eq(200)
+ expect(json_response).to be_a(Hash)
+ end
+
+ it 'should respond with 404 Not Found if requesting non-existing trigger' do
+ get api("/projects/#{project.id}/triggers/abcdef012345", user)
+
+ expect(response.status).to eq(404)
+ end
+ end
+
+ context 'authenticated user with invalid permissions' do
+ it 'should not return triggers list' do
+ get api("/projects/#{project.id}/triggers/#{trigger.token}", user2)
+
+ expect(response.status).to eq(403)
+ end
+ end
+
+ context 'unauthenticated user' do
+ it 'should not return triggers list' do
+ get api("/projects/#{project.id}/triggers/#{trigger.token}")
+
+ expect(response.status).to eq(401)
+ end
+ end
+ end
+
+ describe 'POST /projects/:id/triggers' do
+ context 'authenticated user with valid permissions' do
+ it 'should create trigger' do
+ expect do
+ post api("/projects/#{project.id}/triggers", user)
+ end.to change{project.triggers.count}.by(1)
+
+ expect(response.status).to eq(201)
+ expect(json_response).to be_a(Hash)
+ end
+ end
+
+ context 'authenticated user with invalid permissions' do
+ it 'should not create trigger' do
+ post api("/projects/#{project.id}/triggers", user2)
+
+ expect(response.status).to eq(403)
+ end
+ end
+
+ context 'unauthenticated user' do
+ it 'should not create trigger' do
+ post api("/projects/#{project.id}/triggers")
+
+ expect(response.status).to eq(401)
+ end
+ end
+ end
+
+ describe 'DELETE /projects/:id/triggers/:token' do
+ context 'authenticated user with valid permissions' do
+ it 'should delete trigger' do
+ expect do
+ delete api("/projects/#{project.id}/triggers/#{trigger.token}", user)
+ end.to change{project.triggers.count}.by(-1)
+ expect(response.status).to eq(200)
+ end
+
+ it 'should respond with 404 Not Found if requesting non-existing trigger' do
+ delete api("/projects/#{project.id}/triggers/abcdef012345", user)
+
+ expect(response.status).to eq(404)
+ end
+ end
+
+ context 'authenticated user with invalid permissions' do
+ it 'should not delete trigger' do
+ delete api("/projects/#{project.id}/triggers/#{trigger.token}", user2)
+
+ expect(response.status).to eq(403)
+ end
+ end
+
+ context 'unauthenticated user' do
+ it 'should not delete trigger' do
+ delete api("/projects/#{project.id}/triggers/#{trigger.token}")
+
+ expect(response.status).to eq(401)
+ end
+ end
+ end
end
diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb
index 4f278551d07..679227bf881 100644
--- a/spec/requests/api/users_spec.rb
+++ b/spec/requests/api/users_spec.rb
@@ -8,6 +8,8 @@ describe API::API, api: true do
let(:key) { create(:key, user: user) }
let(:email) { create(:email, user: user) }
let(:omniauth_user) { create(:omniauth_user) }
+ let(:ldap_user) { create(:omniauth_user, provider: 'ldapmain') }
+ let(:ldap_blocked_user) { create(:omniauth_user, provider: 'ldapmain', state: 'ldap_blocked') }
describe "GET /users" do
context "when unauthenticated" do
@@ -45,6 +47,8 @@ describe API::API, api: true do
expect(json_response.first.keys).to include 'identities'
expect(json_response.first.keys).to include 'can_create_project'
expect(json_response.first.keys).to include 'two_factor_enabled'
+ expect(json_response.first.keys).to include 'last_sign_in_at'
+ expect(json_response.first.keys).to include 'confirmed_at'
end
end
end
@@ -116,6 +120,26 @@ describe API::API, api: true do
expect(response.status).to eq(201)
end
+ it 'creates non-external users by default' do
+ post api("/users", admin), attributes_for(:user)
+ expect(response.status).to eq(201)
+
+ user_id = json_response['id']
+ new_user = User.find(user_id)
+ expect(new_user).not_to eq nil
+ expect(new_user.external).to be_falsy
+ end
+
+ it 'should allow an external user to be created' do
+ post api("/users", admin), attributes_for(:user, external: true)
+ expect(response.status).to eq(201)
+
+ user_id = json_response['id']
+ new_user = User.find(user_id)
+ expect(new_user).not_to eq nil
+ expect(new_user.external).to be_truthy
+ end
+
it "should not create user with invalid email" do
post api('/users', admin),
email: 'invalid email',
@@ -258,6 +282,13 @@ describe API::API, api: true do
expect(user.reload.admin).to eq(true)
end
+ it "should update external status" do
+ put api("/users/#{user.id}", admin), { external: true }
+ expect(response.status).to eq 200
+ expect(json_response['external']).to eq(true)
+ expect(user.reload.external?).to be_truthy
+ end
+
it "should not update admin status" do
put api("/users/#{admin_user.id}", admin), { can_create_group: false }
expect(response.status).to eq(200)
@@ -783,6 +814,12 @@ describe API::API, api: true do
expect(user.reload.state).to eq('blocked')
end
+ it 'should not re-block ldap blocked users' do
+ put api("/users/#{ldap_blocked_user.id}/block", admin)
+ expect(response.status).to eq(403)
+ expect(ldap_blocked_user.reload.state).to eq('ldap_blocked')
+ end
+
it 'should not be available for non admin users' do
put api("/users/#{user.id}/block", user)
expect(response.status).to eq(403)
@@ -797,7 +834,9 @@ describe API::API, api: true do
end
describe 'PUT /user/:id/unblock' do
+ let(:blocked_user) { create(:user, state: 'blocked') }
before { admin }
+
it 'should unblock existing user' do
put api("/users/#{user.id}/unblock", admin)
expect(response.status).to eq(200)
@@ -805,12 +844,15 @@ describe API::API, api: true do
end
it 'should unblock a blocked user' do
- put api("/users/#{user.id}/block", admin)
+ put api("/users/#{blocked_user.id}/unblock", admin)
expect(response.status).to eq(200)
- expect(user.reload.state).to eq('blocked')
- put api("/users/#{user.id}/unblock", admin)
- expect(response.status).to eq(200)
- expect(user.reload.state).to eq('active')
+ expect(blocked_user.reload.state).to eq('active')
+ end
+
+ it 'should not unblock ldap blocked users' do
+ put api("/users/#{ldap_blocked_user.id}/unblock", admin)
+ expect(response.status).to eq(403)
+ expect(ldap_blocked_user.reload.state).to eq('ldap_blocked')
end
it 'should not be available for non admin users' do
diff --git a/spec/requests/api/variables_spec.rb b/spec/requests/api/variables_spec.rb
new file mode 100644
index 00000000000..b1e1053d037
--- /dev/null
+++ b/spec/requests/api/variables_spec.rb
@@ -0,0 +1,182 @@
+require 'spec_helper'
+
+describe API::API, api: true do
+ include ApiHelpers
+
+ let(:user) { create(:user) }
+ let(:user2) { create(:user) }
+ let!(:project) { create(:project, creator_id: user.id) }
+ let!(:master) { create(:project_member, :master, user: user, project: project) }
+ let!(:developer) { create(:project_member, :developer, user: user2, project: project) }
+ let!(:variable) { create(:ci_variable, project: project) }
+
+ describe 'GET /projects/:id/variables' do
+ context 'authorized user with proper permissions' do
+ it 'should return project variables' do
+ get api("/projects/#{project.id}/variables", user)
+
+ expect(response.status).to eq(200)
+ expect(json_response).to be_a(Array)
+ end
+ end
+
+ context 'authorized user with invalid permissions' do
+ it 'should not return project variables' do
+ get api("/projects/#{project.id}/variables", user2)
+
+ expect(response.status).to eq(403)
+ end
+ end
+
+ context 'unauthorized user' do
+ it 'should not return project variables' do
+ get api("/projects/#{project.id}/variables")
+
+ expect(response.status).to eq(401)
+ end
+ end
+ end
+
+ describe 'GET /projects/:id/variables/:key' do
+ context 'authorized user with proper permissions' do
+ it 'should return project variable details' do
+ get api("/projects/#{project.id}/variables/#{variable.key}", user)
+
+ expect(response.status).to eq(200)
+ expect(json_response['value']).to eq(variable.value)
+ end
+
+ it 'should respond with 404 Not Found if requesting non-existing variable' do
+ get api("/projects/#{project.id}/variables/non_existing_variable", user)
+
+ expect(response.status).to eq(404)
+ end
+ end
+
+ context 'authorized user with invalid permissions' do
+ it 'should not return project variable details' do
+ get api("/projects/#{project.id}/variables/#{variable.key}", user2)
+
+ expect(response.status).to eq(403)
+ end
+ end
+
+ context 'unauthorized user' do
+ it 'should not return project variable details' do
+ get api("/projects/#{project.id}/variables/#{variable.key}")
+
+ expect(response.status).to eq(401)
+ end
+ end
+ end
+
+ describe 'POST /projects/:id/variables' do
+ context 'authorized user with proper permissions' do
+ it 'should create variable' do
+ expect do
+ post api("/projects/#{project.id}/variables", user), key: 'TEST_VARIABLE_2', value: 'VALUE_2'
+ end.to change{project.variables.count}.by(1)
+
+ expect(response.status).to eq(201)
+ expect(json_response['key']).to eq('TEST_VARIABLE_2')
+ expect(json_response['value']).to eq('VALUE_2')
+ end
+
+ it 'should not allow to duplicate variable key' do
+ expect do
+ post api("/projects/#{project.id}/variables", user), key: variable.key, value: 'VALUE_2'
+ end.to change{project.variables.count}.by(0)
+
+ expect(response.status).to eq(400)
+ end
+ end
+
+ context 'authorized user with invalid permissions' do
+ it 'should not create variable' do
+ post api("/projects/#{project.id}/variables", user2)
+
+ expect(response.status).to eq(403)
+ end
+ end
+
+ context 'unauthorized user' do
+ it 'should not create variable' do
+ post api("/projects/#{project.id}/variables")
+
+ expect(response.status).to eq(401)
+ end
+ end
+ end
+
+ describe 'PUT /projects/:id/variables/:key' do
+ context 'authorized user with proper permissions' do
+ it 'should update variable data' do
+ initial_variable = project.variables.first
+ value_before = initial_variable.value
+
+ put api("/projects/#{project.id}/variables/#{variable.key}", user), value: 'VALUE_1_UP'
+
+ updated_variable = project.variables.first
+
+ expect(response.status).to eq(200)
+ expect(value_before).to eq(variable.value)
+ expect(updated_variable.value).to eq('VALUE_1_UP')
+ end
+
+ it 'should responde with 404 Not Found if requesting non-existing variable' do
+ put api("/projects/#{project.id}/variables/non_existing_variable", user)
+
+ expect(response.status).to eq(404)
+ end
+ end
+
+ context 'authorized user with invalid permissions' do
+ it 'should not update variable' do
+ put api("/projects/#{project.id}/variables/#{variable.key}", user2)
+
+ expect(response.status).to eq(403)
+ end
+ end
+
+ context 'unauthorized user' do
+ it 'should not update variable' do
+ put api("/projects/#{project.id}/variables/#{variable.key}")
+
+ expect(response.status).to eq(401)
+ end
+ end
+ end
+
+ describe 'DELETE /projects/:id/variables/:key' do
+ context 'authorized user with proper permissions' do
+ it 'should delete variable' do
+ expect do
+ delete api("/projects/#{project.id}/variables/#{variable.key}", user)
+ end.to change{project.variables.count}.by(-1)
+ expect(response.status).to eq(200)
+ end
+
+ it 'should responde with 404 Not Found if requesting non-existing variable' do
+ delete api("/projects/#{project.id}/variables/non_existing_variable", user)
+
+ expect(response.status).to eq(404)
+ end
+ end
+
+ context 'authorized user with invalid permissions' do
+ it 'should not delete variable' do
+ delete api("/projects/#{project.id}/variables/#{variable.key}", user2)
+
+ expect(response.status).to eq(403)
+ end
+ end
+
+ context 'unauthorized user' do
+ it 'should not delete variable' do
+ delete api("/projects/#{project.id}/variables/#{variable.key}")
+
+ expect(response.status).to eq(401)
+ end
+ end
+ end
+end
diff --git a/spec/requests/ci/api/builds_spec.rb b/spec/requests/ci/api/builds_spec.rb
index c27e87c4acc..57d7eb927fd 100644
--- a/spec/requests/ci/api/builds_spec.rb
+++ b/spec/requests/ci/api/builds_spec.rb
@@ -101,31 +101,66 @@ describe Ci::API::API do
{ "key" => "TRIGGER_KEY", "value" => "TRIGGER_VALUE", "public" => false },
])
end
+
+ it "returns dependent builds" do
+ commit = FactoryGirl.create(:ci_commit, project: project)
+ commit.create_builds('master', false, nil, nil)
+ commit.builds.where(stage: 'test').each(&:success)
+
+ post ci_api("/builds/register"), token: runner.token, info: { platform: :darwin }
+
+ expect(response.status).to eq(201)
+ expect(json_response["depends_on_builds"].count).to eq(2)
+ expect(json_response["depends_on_builds"][0]["name"]).to eq("rspec")
+ end
+
+ %w(name version revision platform architecture).each do |param|
+ context "updates runner #{param}" do
+ let(:value) { "#{param}_value" }
+
+ subject { runner.read_attribute(param.to_sym) }
+
+ it do
+ post ci_api("/builds/register"), token: runner.token, info: { param => value }
+ expect(response.status).to eq(404)
+ runner.reload
+ is_expected.to eq(value)
+ end
+ end
+ end
end
describe "PUT /builds/:id" do
- let(:commit) { FactoryGirl.create(:ci_commit, project: project)}
- let(:build) { FactoryGirl.create(:ci_build, commit: commit, runner_id: runner.id) }
+ let(:commit) {create(:ci_commit, project: project)}
+ let(:build) { create(:ci_build, :trace, commit: commit, runner_id: runner.id) }
- it "should update a running build" do
+ before do
build.run!
put ci_api("/builds/#{build.id}"), token: runner.token
+ end
+
+ it "should update a running build" do
expect(response.status).to eq(200)
end
- it 'Should not override trace information when no trace is given' do
- build.run!
- build.update!(trace: 'hello_world')
- put ci_api("/builds/#{build.id}"), token: runner.token
- expect(build.reload.trace).to eq 'hello_world'
+ it 'should not override trace information when no trace is given' do
+ expect(build.reload.trace).to eq 'BUILD TRACE'
+ end
+
+ context 'build has been erased' do
+ let(:build) { create(:ci_build, runner_id: runner.id, erased_at: Time.now) }
+
+ it 'should respond with forbidden' do
+ expect(response.status).to eq 403
+ end
end
end
context "Artifacts" do
let(:file_upload) { fixture_file_upload(Rails.root + 'spec/fixtures/banana_sample.gif', 'image/gif') }
let(:file_upload2) { fixture_file_upload(Rails.root + 'spec/fixtures/dk.png', 'image/gif') }
- let(:commit) { FactoryGirl.create(:ci_commit, project: project) }
- let(:build) { FactoryGirl.create(:ci_build, commit: commit, runner_id: runner.id) }
+ let(:commit) { create(:ci_commit, project: project) }
+ let(:build) { create(:ci_build, commit: commit, runner_id: runner.id) }
let(:authorize_url) { ci_api("/builds/#{build.id}/artifacts/authorize") }
let(:post_url) { ci_api("/builds/#{build.id}/artifacts") }
let(:delete_url) { ci_api("/builds/#{build.id}/artifacts") }
@@ -133,12 +168,10 @@ describe Ci::API::API do
let(:headers) { { "GitLab-Workhorse" => "1.0" } }
let(:headers_with_token) { headers.merge(Ci::API::Helpers::BUILD_TOKEN_HEADER => build.token) }
+ before { build.run! }
+
describe "POST /builds/:id/artifacts/authorize" do
context "should authorize posting artifact to running build" do
- before do
- build.run!
- end
-
it "using token as parameter" do
post authorize_url, { token: build.token }, headers
expect(response.status).to eq(200)
@@ -153,10 +186,6 @@ describe Ci::API::API do
end
context "should fail to post too large artifact" do
- before do
- build.run!
- end
-
it "using token as parameter" do
stub_application_setting(max_artifacts_size: 0)
post authorize_url, { token: build.token, filesize: 100 }, headers
@@ -170,26 +199,32 @@ describe Ci::API::API do
end
end
- context "should get denied" do
- it do
- post authorize_url, { token: 'invalid', filesize: 100 }
+ context 'authorization token is invalid' do
+ before { post authorize_url, { token: 'invalid', filesize: 100 } }
+
+ it 'should respond with forbidden' do
expect(response.status).to eq(403)
end
end
end
describe "POST /builds/:id/artifacts" do
- context "Disable sanitizer" do
+ context "disable sanitizer" do
before do
# by configuring this path we allow to pass temp file from any path
allow(ArtifactUploader).to receive(:artifacts_upload_path).and_return('/')
end
- context "should post artifact to running build" do
- before do
- build.run!
+ context 'build has been erased' do
+ let(:build) { create(:ci_build, erased_at: Time.now) }
+ before { upload_artifacts(file_upload, headers_with_token) }
+
+ it 'should respond with forbidden' do
+ expect(response.status).to eq 403
end
+ end
+ context "should post artifact to running build" do
it "uses regual file post" do
upload_artifacts(file_upload, headers_with_token, false)
expect(response.status).to eq(201)
@@ -210,55 +245,83 @@ describe Ci::API::API do
end
end
- context "should fail to post too large artifact" do
+ context 'should post artifacts file and metadata file' do
+ let!(:artifacts) { file_upload }
+ let!(:metadata) { file_upload2 }
+
+ let(:stored_artifacts_file) { build.reload.artifacts_file.file }
+ let(:stored_metadata_file) { build.reload.artifacts_metadata.file }
+
before do
- build.run!
+ post(post_url, post_data, headers_with_token)
+ end
+
+ context 'post data accelerated by workhorse is correct' do
+ let(:post_data) do
+ { 'file.path' => artifacts.path,
+ 'file.name' => artifacts.original_filename,
+ 'metadata.path' => metadata.path,
+ 'metadata.name' => metadata.original_filename }
+ end
+
+ it 'stores artifacts and artifacts metadata' do
+ expect(response.status).to eq(201)
+ expect(stored_artifacts_file.original_filename).to eq(artifacts.original_filename)
+ expect(stored_metadata_file.original_filename).to eq(metadata.original_filename)
+ end
+ end
+
+ context 'no artifacts file in post data' do
+ let(:post_data) do
+ { 'metadata' => metadata }
+ end
+
+ it 'is expected to respond with bad request' do
+ expect(response.status).to eq(400)
+ end
+
+ it 'does not store metadata' do
+ expect(stored_metadata_file).to be_nil
+ end
end
+ end
- it do
+ context "artifacts file is too large" do
+ it "should fail to post too large artifact" do
stub_application_setting(max_artifacts_size: 0)
upload_artifacts(file_upload, headers_with_token)
expect(response.status).to eq(413)
end
end
- context "should fail to post artifacts without file" do
- before do
- build.run!
- end
-
- it do
+ context "artifacts post request does not contain file" do
+ it "should fail to post artifacts without file" do
post post_url, {}, headers_with_token
expect(response.status).to eq(400)
end
end
- context "should fail to post artifacts without GitLab-Workhorse" do
- before do
- build.run!
- end
-
- it do
+ context 'GitLab Workhorse is not configured' do
+ it "should fail to post artifacts without GitLab-Workhorse" do
post post_url, { token: build.token }, {}
expect(response.status).to eq(403)
end
end
end
- context "should fail to post artifacts for outside of tmp path" do
+ context "artifacts are being stored outside of tmp path" do
before do
# by configuring this path we allow to pass file from @tmpdir only
# but all temporary files are stored in system tmp directory
@tmpdir = Dir.mktmpdir
allow(ArtifactUploader).to receive(:artifacts_upload_path).and_return(@tmpdir)
- build.run!
end
after do
FileUtils.remove_entry @tmpdir
end
- it do
+ it "should fail to post artifacts for outside of tmp path" do
upload_artifacts(file_upload, headers_with_token)
expect(response.status).to eq(400)
end
@@ -276,33 +339,37 @@ describe Ci::API::API do
end
end
- describe "DELETE /builds/:id/artifacts" do
- before do
- build.run!
- post delete_url, token: build.token, file: file_upload
- end
+ describe 'DELETE /builds/:id/artifacts' do
+ let(:build) { create(:ci_build, :artifacts) }
+ before { delete delete_url, token: build.token }
- it "should delete artifact build" do
- build.success
- delete delete_url, token: build.token
+ it 'should remove build artifacts' do
expect(response.status).to eq(200)
+ expect(build.artifacts_file.exists?).to be_falsy
+ expect(build.artifacts_metadata.exists?).to be_falsy
end
end
- describe "GET /builds/:id/artifacts" do
- before do
- build.run!
- end
+ describe 'GET /builds/:id/artifacts' do
+ before { get get_url, token: build.token }
- it "should download artifact" do
- build.update_attributes(artifacts_file: file_upload)
- get get_url, token: build.token
- expect(response.status).to eq(200)
+ context 'build has artifacts' do
+ let(:build) { create(:ci_build, :artifacts) }
+ let(:download_headers) do
+ { 'Content-Transfer-Encoding'=>'binary',
+ 'Content-Disposition'=>'attachment; filename=ci_build_artifacts.zip' }
+ end
+
+ it 'should download artifact' do
+ expect(response.status).to eq(200)
+ expect(response.headers).to include download_headers
+ end
end
- it "should fail to download if no artifact uploaded" do
- get get_url, token: build.token
- expect(response.status).to eq(404)
+ context 'build does not has artifacts' do
+ it 'should respond with not found' do
+ expect(response.status).to eq(404)
+ end
end
end
end
diff --git a/spec/requests/ci/api/runners_spec.rb b/spec/requests/ci/api/runners_spec.rb
index 5942aa7a1b5..db8189ffb79 100644
--- a/spec/requests/ci/api/runners_spec.rb
+++ b/spec/requests/ci/api/runners_spec.rb
@@ -51,6 +51,20 @@ describe Ci::API::API do
expect(response.status).to eq(400)
end
+
+ %w(name version revision platform architecture).each do |param|
+ context "creates runner with #{param} saved" do
+ let(:value) { "#{param}_value" }
+
+ subject { Ci::Runner.first.read_attribute(param.to_sym) }
+
+ it do
+ post ci_api("/runners/register"), token: registration_token, info: { param => value }
+ expect(response.status).to eq(201)
+ is_expected.to eq(value)
+ end
+ end
+ end
end
describe "DELETE /runners/delete" do