diff options
Diffstat (limited to 'spec/requests')
22 files changed, 945 insertions, 118 deletions
diff --git a/spec/requests/api/builds_spec.rb b/spec/requests/api/builds_spec.rb index 5ead735be48..6cb7be188ef 100644 --- a/spec/requests/api/builds_spec.rb +++ b/spec/requests/api/builds_spec.rb @@ -9,8 +9,8 @@ describe API::API, api: true do 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) } + let(:pipeline) { create(:ci_pipeline, project: project)} + let(:build) { create(:ci_build, pipeline: pipeline) } describe 'GET /projects/:id/builds ' do let(:query) { '' } @@ -59,8 +59,8 @@ describe API::API, api: true do describe 'GET /projects/:id/repository/commits/:sha/builds' do before do - project.ensure_ci_commit(commit.sha, 'master') - get api("/projects/#{project.id}/repository/commits/#{commit.sha}/builds", api_user) + project.ensure_pipeline(pipeline.sha, 'master') + get api("/projects/#{project.id}/repository/commits/#{pipeline.sha}/builds", api_user) end context 'authorized user' do @@ -102,12 +102,12 @@ describe API::API, api: true 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) } + let(:build) { create(:ci_build, :artifacts, pipeline: pipeline) } context 'authorized user' do let(:download_headers) do - { 'Content-Transfer-Encoding'=>'binary', - 'Content-Disposition'=>'attachment; filename=ci_build_artifacts.zip' } + { 'Content-Transfer-Encoding' => 'binary', + 'Content-Disposition' => 'attachment; filename=ci_build_artifacts.zip' } end it 'should return specific build artifacts' do @@ -131,7 +131,7 @@ describe API::API, api: true do end describe 'GET /projects/:id/builds/:build_id/trace' do - let(:build) { create(:ci_build, :trace, commit: commit) } + let(:build) { create(:ci_build, :trace, pipeline: pipeline) } before { get api("/projects/#{project.id}/builds/#{build.id}/trace", api_user) } @@ -181,7 +181,7 @@ describe API::API, api: true do end describe 'POST /projects/:id/builds/:build_id/retry' do - let(:build) { create(:ci_build, :canceled, commit: commit) } + let(:build) { create(:ci_build, :canceled, pipeline: pipeline) } before { post api("/projects/#{project.id}/builds/#{build.id}/retry", api_user) } @@ -218,7 +218,7 @@ describe API::API, api: true do end context 'build is erasable' do - let(:build) { create(:ci_build, :trace, :artifacts, :success, project: project, commit: commit) } + let(:build) { create(:ci_build, :trace, :artifacts, :success, project: project, pipeline: pipeline) } it 'should erase build content' do expect(response.status).to eq 201 @@ -234,7 +234,7 @@ describe API::API, api: true do end context 'build is not erasable' do - let(:build) { create(:ci_build, :trace, project: project, commit: commit) } + let(:build) { create(:ci_build, :trace, project: project, pipeline: pipeline) } it 'should respond with forbidden' do expect(response.status).to eq 403 diff --git a/spec/requests/api/commit_status_spec.rb b/spec/requests/api/commit_statuses_spec.rb index f3785b19362..298cdbad329 100644 --- a/spec/requests/api/commit_status_spec.rb +++ b/spec/requests/api/commit_statuses_spec.rb @@ -1,11 +1,11 @@ require 'spec_helper' -describe API::CommitStatus, api: true do +describe API::CommitStatuses, api: true do include ApiHelpers let!(:project) { create(:project) } let(:commit) { project.repository.commit } - let(:commit_status) { create(:commit_status, commit: ci_commit) } + let(:commit_status) { create(:commit_status, pipeline: pipeline) } let(:guest) { create_user(:guest) } let(:reporter) { create_user(:reporter) } let(:developer) { create_user(:developer) } @@ -16,8 +16,8 @@ describe API::CommitStatus, api: true do let(:get_url) { "/projects/#{project.id}/repository/commits/#{sha}/statuses" } context 'ci commit exists' do - let!(:master) { project.ci_commits.create(sha: commit.id, ref: 'master') } - let!(:develop) { project.ci_commits.create(sha: commit.id, ref: 'develop') } + let!(:master) { project.pipelines.create(sha: commit.id, ref: 'master') } + let!(:develop) { project.pipelines.create(sha: commit.id, ref: 'develop') } it_behaves_like 'a paginated resources' do let(:request) { get api(get_url, reporter) } @@ -27,7 +27,7 @@ describe API::CommitStatus, api: true do let(:statuses_id) { json_response.map { |status| status['id'] } } def create_status(commit, opts = {}) - create(:commit_status, { commit: commit, ref: commit.ref }.merge(opts)) + create(:commit_status, { pipeline: commit, ref: commit.ref }.merge(opts)) end let!(:status1) { create_status(master, status: 'running') } diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb index cb82ca7802d..6fc38f537d3 100644 --- a/spec/requests/api/commits_spec.rb +++ b/spec/requests/api/commits_spec.rb @@ -90,10 +90,10 @@ describe API::API, api: true do end it "should return status for CI" do - ci_commit = project.ensure_ci_commit(project.repository.commit.sha, 'master') + pipeline = project.ensure_pipeline(project.repository.commit.sha, 'master') get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}", user) expect(response.status).to eq(200) - expect(json_response['status']).to eq(ci_commit.status) + expect(json_response['status']).to eq(pipeline.status) end end diff --git a/spec/requests/api/gitignores_spec.rb b/spec/requests/api/gitignores_spec.rb new file mode 100644 index 00000000000..aab2d8c81b9 --- /dev/null +++ b/spec/requests/api/gitignores_spec.rb @@ -0,0 +1,29 @@ +require 'spec_helper' + +describe API::Gitignores, api: true do + include ApiHelpers + + describe 'Entity Gitignore' do + before { get api('/gitignores/Ruby') } + + it { expect(json_response['name']).to eq('Ruby') } + it { expect(json_response['content']).to include('*.gem') } + end + + describe 'Entity GitignoresList' do + before { get api('/gitignores') } + + it { expect(json_response.first['name']).not_to be_nil } + it { expect(json_response.first['content']).to be_nil } + end + + describe 'GET /gitignores' do + it 'returns a list of available license templates' do + get api('/gitignores') + + expect(response.status).to eq(200) + expect(json_response).to be_an Array + expect(json_response.size).to be > 15 + end + end +end diff --git a/spec/requests/api/group_members_spec.rb b/spec/requests/api/group_members_spec.rb index 96d89e69209..02553d0f8e2 100644 --- a/spec/requests/api/group_members_spec.rb +++ b/spec/requests/api/group_members_spec.rb @@ -34,11 +34,11 @@ describe API::API, api: true do expect(response.status).to eq(200) expect(json_response).to be_an Array expect(json_response.size).to eq(5) - expect(json_response.find { |e| e['id']==owner.id }['access_level']).to eq(GroupMember::OWNER) - expect(json_response.find { |e| e['id']==reporter.id }['access_level']).to eq(GroupMember::REPORTER) - expect(json_response.find { |e| e['id']==developer.id }['access_level']).to eq(GroupMember::DEVELOPER) - expect(json_response.find { |e| e['id']==master.id }['access_level']).to eq(GroupMember::MASTER) - expect(json_response.find { |e| e['id']==guest.id }['access_level']).to eq(GroupMember::GUEST) + expect(json_response.find { |e| e['id'] == owner.id }['access_level']).to eq(GroupMember::OWNER) + expect(json_response.find { |e| e['id'] == reporter.id }['access_level']).to eq(GroupMember::REPORTER) + expect(json_response.find { |e| e['id'] == developer.id }['access_level']).to eq(GroupMember::DEVELOPER) + expect(json_response.find { |e| e['id'] == master.id }['access_level']).to eq(GroupMember::MASTER) + expect(json_response.find { |e| e['id'] == guest.id }['access_level']).to eq(GroupMember::GUEST) end end diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb index 37ddab83c30..7ecefce80d6 100644 --- a/spec/requests/api/groups_spec.rb +++ b/spec/requests/api/groups_spec.rb @@ -12,6 +12,7 @@ describe API::API, api: true do let!(:group2) { create(:group, :private) } let!(:project1) { create(:project, namespace: group1) } let!(:project2) { create(:project, namespace: group2) } + let!(:project3) { create(:project, namespace: group1, path: 'test', visibility_level: Gitlab::VisibilityLevel::PRIVATE) } before do group1.add_owner(user1) @@ -147,9 +148,11 @@ describe API::API, api: true do context "when authenticated as user" do it "should return the group's projects" do get api("/groups/#{group1.id}/projects", user1) + expect(response.status).to eq(200) - expect(json_response.length).to eq(1) - expect(json_response.first['name']).to eq(project1.name) + 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]) end it "should not return a non existing group" do @@ -162,6 +165,16 @@ describe API::API, api: true do expect(response.status).to eq(404) end + + it "should only return projects to which user has access" do + project3.team << [user3, :developer] + + get api("/groups/#{group1.id}/projects", user3) + + expect(response.status).to eq(200) + expect(json_response.length).to eq(1) + expect(json_response.first['name']).to eq(project3.name) + end end context "when authenticated as admin" do @@ -181,8 +194,10 @@ describe API::API, api: true do context 'when using group path in URL' do it 'should return any existing group' do get api("/groups/#{group1.path}/projects", admin) + expect(response.status).to eq(200) - expect(json_response.first['name']).to eq(project1.name) + project_names = json_response.map { |proj| proj['name' ] } + expect(project_names).to match_array([project1.name, project3.name]) end it 'should not return a non existing group' do diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb index f88e39cad9e..bb926172593 100644 --- a/spec/requests/api/issues_spec.rb +++ b/spec/requests/api/issues_spec.rb @@ -39,6 +39,7 @@ describe API::API, api: true do let!(:empty_milestone) do create(:milestone, title: '2.0.0', project: project) end + let!(:note) { create(:note_on_issue, author: user, project: project, noteable: issue) } before { project.team << [user, :reporter] } @@ -232,8 +233,27 @@ describe API::API, api: true do end describe "GET /projects/:id/issues/:issue_id" do + it 'exposes known attributes' do + get api("/projects/#{project.id}/issues/#{issue.id}", user) + + expect(response.status).to eq(200) + expect(json_response['id']).to eq(issue.id) + expect(json_response['iid']).to eq(issue.iid) + expect(json_response['project_id']).to eq(issue.project.id) + expect(json_response['title']).to eq(issue.title) + expect(json_response['description']).to eq(issue.description) + expect(json_response['state']).to eq(issue.state) + expect(json_response['created_at']).to be_present + expect(json_response['updated_at']).to be_present + expect(json_response['labels']).to eq(issue.label_names) + expect(json_response['milestone']).to be_a Hash + expect(json_response['assignee']).to be_a Hash + expect(json_response['author']).to be_a Hash + end + it "should return a project issue by id" do get api("/projects/#{project.id}/issues/#{issue.id}", user) + expect(response.status).to eq(200) expect(json_response['title']).to eq(issue.title) expect(json_response['iid']).to eq(issue.iid) @@ -602,6 +622,12 @@ describe API::API, api: true do expect(response.status).to eq(404) end + + it 'returns 404 if the issue is confidential' do + post api("/projects/#{project.id}/issues/#{confidential_issue.id}/subscription", non_member) + + expect(response.status).to eq(404) + end end describe 'DELETE :id/issues/:issue_id/subscription' do @@ -623,5 +649,11 @@ describe API::API, api: true do expect(response.status).to eq(404) end + + it 'returns 404 if the issue is confidential' do + delete api("/projects/#{project.id}/issues/#{confidential_issue.id}/subscription", non_member) + + expect(response.status).to eq(404) + end end end diff --git a/spec/requests/api/labels_spec.rb b/spec/requests/api/labels_spec.rb index 6943ff9d26c..b2c7f8d9acb 100644 --- a/spec/requests/api/labels_spec.rb +++ b/spec/requests/api/labels_spec.rb @@ -190,4 +190,86 @@ describe API::API, api: true do expect(json_response['message']['color']).to eq(['must be a valid color code']) end end + + describe "POST /projects/:id/labels/:label_id/subscription" do + context "when label_id is a label title" do + it "should subscribe to the label" do + post api("/projects/#{project.id}/labels/#{label1.title}/subscription", user) + + expect(response.status).to eq(201) + expect(json_response["name"]).to eq(label1.title) + expect(json_response["subscribed"]).to be_truthy + end + end + + context "when label_id is a label ID" do + it "should subscribe to the label" do + post api("/projects/#{project.id}/labels/#{label1.id}/subscription", user) + + expect(response.status).to eq(201) + expect(json_response["name"]).to eq(label1.title) + expect(json_response["subscribed"]).to be_truthy + end + end + + context "when user is already subscribed to label" do + before { label1.subscribe(user) } + + it "should return 304" do + post api("/projects/#{project.id}/labels/#{label1.id}/subscription", user) + + expect(response.status).to eq(304) + end + end + + context "when label ID is not found" do + it "should a return 404 error" do + post api("/projects/#{project.id}/labels/1234/subscription", user) + + expect(response.status).to eq(404) + end + end + end + + describe "DELETE /projects/:id/labels/:label_id/subscription" do + before { label1.subscribe(user) } + + context "when label_id is a label title" do + it "should unsubscribe from the label" do + delete api("/projects/#{project.id}/labels/#{label1.title}/subscription", user) + + expect(response.status).to eq(200) + expect(json_response["name"]).to eq(label1.title) + expect(json_response["subscribed"]).to be_falsey + end + end + + context "when label_id is a label ID" do + it "should unsubscribe from the label" do + delete api("/projects/#{project.id}/labels/#{label1.id}/subscription", user) + + expect(response.status).to eq(200) + expect(json_response["name"]).to eq(label1.title) + expect(json_response["subscribed"]).to be_falsey + end + end + + context "when user is already unsubscribed from label" do + before { label1.unsubscribe(user) } + + it "should return 304" do + delete api("/projects/#{project.id}/labels/#{label1.id}/subscription", user) + + expect(response.status).to eq(304) + end + end + + context "when label ID is not found" do + it "should a return 404 error" do + delete api("/projects/#{project.id}/labels/1234/subscription", user) + + expect(response.status).to eq(404) + end + end + end end diff --git a/spec/requests/api/licenses_spec.rb b/spec/requests/api/licenses_spec.rb index c17dcb222a9..3726b2f5688 100644 --- a/spec/requests/api/licenses_spec.rb +++ b/spec/requests/api/licenses_spec.rb @@ -57,7 +57,7 @@ describe API::Licenses, api: true do end it 'replaces placeholder values' do - expect(json_response['content']).to include('Copyright (c) 2016 Anton') + expect(json_response['content']).to include("Copyright (c) #{Time.now.year} Anton") end end @@ -70,7 +70,7 @@ describe API::Licenses, api: true do it 'replaces placeholder values' do expect(json_response['content']).to include('My Awesome Project') - expect(json_response['content']).to include('Copyright (C) 2016 Anton') + expect(json_response['content']).to include("Copyright (C) #{Time.now.year} Anton") end end @@ -83,7 +83,7 @@ describe API::Licenses, api: true do it 'replaces placeholder values' do expect(json_response['content']).to include('My Awesome Project') - expect(json_response['content']).to include('Copyright (C) 2016 Anton') + expect(json_response['content']).to include("Copyright (C) #{Time.now.year} Anton") end end @@ -96,7 +96,7 @@ describe API::Licenses, api: true do it 'replaces placeholder values' do expect(json_response['content']).to include('My Awesome Project') - expect(json_response['content']).to include('Copyright (C) 2016 Anton') + expect(json_response['content']).to include("Copyright (C) #{Time.now.year} Anton") end end @@ -108,7 +108,7 @@ describe API::Licenses, api: true do end it 'replaces placeholder values' do - expect(json_response['content']).to include('Copyright 2016 Anton') + expect(json_response['content']).to include("Copyright #{Time.now.year} Anton") end end @@ -128,7 +128,7 @@ describe API::Licenses, api: true do it 'replaces the copyright owner placeholder with the name of the current user' do get api('/licenses/mit', user) - expect(json_response['content']).to include("Copyright (c) 2016 #{user.name}") + expect(json_response['content']).to include("Copyright (c) #{Time.now.year} #{user.name}") end end end diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb index 1fa7e76894f..5896b93603f 100644 --- a/spec/requests/api/merge_requests_spec.rb +++ b/spec/requests/api/merge_requests_spec.rb @@ -113,6 +113,33 @@ describe API::API, api: true do end describe "GET /projects/:id/merge_requests/:merge_request_id" do + it 'exposes known attributes' do + get api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user) + + expect(response.status).to eq(200) + expect(json_response['id']).to eq(merge_request.id) + expect(json_response['iid']).to eq(merge_request.iid) + expect(json_response['project_id']).to eq(merge_request.project.id) + expect(json_response['title']).to eq(merge_request.title) + expect(json_response['description']).to eq(merge_request.description) + expect(json_response['state']).to eq(merge_request.state) + expect(json_response['created_at']).to be_present + expect(json_response['updated_at']).to be_present + expect(json_response['labels']).to eq(merge_request.label_names) + expect(json_response['milestone']).to be_nil + expect(json_response['assignee']).to be_a Hash + expect(json_response['author']).to be_a Hash + expect(json_response['target_branch']).to eq(merge_request.target_branch) + expect(json_response['source_branch']).to eq(merge_request.source_branch) + expect(json_response['upvotes']).to eq(0) + expect(json_response['downvotes']).to eq(0) + expect(json_response['source_project_id']).to eq(merge_request.source_project.id) + expect(json_response['target_project_id']).to eq(merge_request.target_project.id) + expect(json_response['work_in_progress']).to be_falsy + expect(json_response['merge_when_build_succeeds']).to be_falsy + expect(json_response['merge_status']).to eq('can_be_merged') + end + it "should return merge_request" do get api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user) expect(response.status).to eq(200) @@ -360,7 +387,7 @@ describe API::API, api: true do end describe "PUT /projects/:id/merge_requests/:merge_request_id/merge" do - let(:ci_commit) { create(:ci_commit_without_jobs) } + let(:pipeline) { create(:ci_pipeline_without_jobs) } it "should return merge_request in case of success" do put api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge", user) @@ -392,6 +419,15 @@ describe API::API, api: true do expect(json_response['message']).to eq('405 Method Not Allowed') end + it 'returns 405 if the build failed for a merge request that requires success' do + allow_any_instance_of(MergeRequest).to receive(:mergeable_ci_state?).and_return(false) + + 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 401 if user has no permissions to merge" do user2 = create(:user) project.team << [user2, :reporter] @@ -400,9 +436,22 @@ describe API::API, api: true do expect(json_response['message']).to eq('401 Unauthorized') end + it "returns 409 if the SHA parameter doesn't match" do + put api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge", user), sha: merge_request.source_sha.succ + + expect(response.status).to eq(409) + expect(json_response['message']).to start_with('SHA does not match HEAD of source branch') + end + + it "succeeds if the SHA parameter matches" do + put api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge", user), sha: merge_request.source_sha + + expect(response.status).to eq(200) + end + it "enables merge when build succeeds if the ci is active" do - allow_any_instance_of(MergeRequest).to receive(:ci_commit).and_return(ci_commit) - allow(ci_commit).to receive(:active?).and_return(true) + allow_any_instance_of(MergeRequest).to receive(: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 @@ -514,6 +563,21 @@ describe API::API, api: true do expect(json_response).to be_an Array expect(json_response.length).to eq(0) end + + it 'handles external issues' do + jira_project = create(:jira_project, :public, name: 'JIR_EXT1') + issue = ExternalIssue.new("#{jira_project.name}-123", jira_project) + merge_request = create(:merge_request, :simple, author: user, assignee: user, source_project: jira_project) + merge_request.update_attribute(:description, "Closes #{issue.to_reference(jira_project)}") + + get api("/projects/#{jira_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(1) + expect(json_response.first['title']).to eq(issue.title) + expect(json_response.first['id']).to eq(issue.id) + end end describe 'POST :id/merge_requests/:merge_request_id/subscription' do diff --git a/spec/requests/api/notes_spec.rb b/spec/requests/api/notes_spec.rb index 49091fc0f49..beb29a68692 100644 --- a/spec/requests/api/notes_spec.rb +++ b/spec/requests/api/notes_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe API::API, api: true do include ApiHelpers let(:user) { create(:user) } - let!(:project) { create(:project, namespace: user.namespace) } + let!(:project) { create(:project, :public, namespace: user.namespace) } let!(:issue) { create(:issue, project: project, author: user) } let!(:merge_request) { create(:merge_request, source_project: project, target_project: project, author: user) } let!(:snippet) { create(:project_snippet, project: project, author: user) } @@ -39,6 +39,7 @@ describe API::API, api: true do 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) + expect(response.status).to eq(200) expect(json_response).to be_an Array expect(json_response.first['body']).to eq(issue_note.note) @@ -46,20 +47,33 @@ describe API::API, api: true do it "should return a 404 error when issue id not found" do get api("/projects/#{project.id}/issues/12345/notes", user) + expect(response.status).to eq(404) end - context "that references a private issue" do + context "and current user cannot view the notes" 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 issue is confidential" do + before { ext_issue.update_attributes(confidential: true) } + + it "returns 404" do + get api("/projects/#{ext_proj.id}/issues/#{ext_issue.id}/notes", user) + + expect(response.status).to eq(404) + end + 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) @@ -71,6 +85,7 @@ describe API::API, api: true do context "when noteable is a Snippet" do it "should return an array of snippet notes" do get api("/projects/#{project.id}/snippets/#{snippet.id}/notes", user) + expect(response.status).to eq(200) expect(json_response).to be_an Array expect(json_response.first['body']).to eq(snippet_note.note) @@ -78,6 +93,13 @@ describe API::API, api: true do it "should return a 404 error when snippet id not found" do get api("/projects/#{project.id}/snippets/42/notes", user) + + expect(response.status).to eq(404) + end + + it "returns 404 when not authorized" do + get api("/projects/#{project.id}/snippets/#{snippet.id}/notes", private_user) + expect(response.status).to eq(404) end end @@ -85,6 +107,7 @@ describe API::API, api: true do context "when noteable is a Merge Request" do it "should return an array of merge_requests notes" do get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/notes", user) + expect(response.status).to eq(200) expect(json_response).to be_an Array expect(json_response.first['body']).to eq(merge_request_note.note) @@ -92,6 +115,13 @@ describe API::API, api: true do it "should return a 404 error if merge request id not found" do get api("/projects/#{project.id}/merge_requests/4444/notes", user) + + expect(response.status).to eq(404) + end + + it "returns 404 when not authorized" do + get api("/projects/#{project.id}/merge_requests/4444/notes", private_user) + expect(response.status).to eq(404) end end @@ -101,24 +131,39 @@ describe API::API, api: true do context "when noteable is an Issue" do it "should return an issue note by id" do get api("/projects/#{project.id}/issues/#{issue.id}/notes/#{issue_note.id}", user) + expect(response.status).to eq(200) expect(json_response['body']).to eq(issue_note.note) end it "should return a 404 error if issue note not found" do get api("/projects/#{project.id}/issues/#{issue.id}/notes/12345", user) + expect(response.status).to eq(404) end - context "that references a private issue" do + context "and current user cannot view the note" 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 "when issue is confidential" do + before { issue.update_attributes(confidential: true) } + + it "returns 404" do + get api("/projects/#{project.id}/issues/#{issue.id}/notes/#{issue_note.id}", private_user) + + expect(response.status).to eq(404) + end + 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 @@ -129,12 +174,14 @@ describe API::API, api: true do context "when noteable is a Snippet" do it "should return a snippet note by id" do get api("/projects/#{project.id}/snippets/#{snippet.id}/notes/#{snippet_note.id}", user) + expect(response.status).to eq(200) expect(json_response['body']).to eq(snippet_note.note) end it "should return a 404 error if snippet note not found" do get api("/projects/#{project.id}/snippets/#{snippet.id}/notes/12345", user) + expect(response.status).to eq(404) end end @@ -144,6 +191,7 @@ describe API::API, api: true do context "when noteable is an Issue" do it "should create a new issue note" do post api("/projects/#{project.id}/issues/#{issue.id}/notes", user), body: 'hi!' + expect(response.status).to eq(201) expect(json_response['body']).to eq('hi!') expect(json_response['author']['username']).to eq(user.username) @@ -151,11 +199,13 @@ describe API::API, api: true do it "should return a 400 bad request error if body not given" do post api("/projects/#{project.id}/issues/#{issue.id}/notes", user) + expect(response.status).to eq(400) end it "should return a 401 unauthorized error if user not authenticated" do post api("/projects/#{project.id}/issues/#{issue.id}/notes"), body: 'hi!' + expect(response.status).to eq(401) end @@ -164,6 +214,7 @@ describe API::API, api: true do creation_time = 2.weeks.ago post api("/projects/#{project.id}/issues/#{issue.id}/notes", user), body: 'hi!', created_at: creation_time + expect(response.status).to eq(201) expect(json_response['body']).to eq('hi!') expect(json_response['author']['username']).to eq(user.username) @@ -176,6 +227,7 @@ describe API::API, api: true do context "when noteable is a Snippet" do it "should create a new snippet note" do post api("/projects/#{project.id}/snippets/#{snippet.id}/notes", user), body: 'hi!' + expect(response.status).to eq(201) expect(json_response['body']).to eq('hi!') expect(json_response['author']['username']).to eq(user.username) @@ -183,11 +235,13 @@ describe API::API, api: true do it "should return a 400 bad request error if body not given" do post api("/projects/#{project.id}/snippets/#{snippet.id}/notes", user) + expect(response.status).to eq(400) end it "should return a 401 unauthorized error if user not authenticated" do post api("/projects/#{project.id}/snippets/#{snippet.id}/notes"), body: 'hi!' + expect(response.status).to eq(401) end end @@ -204,8 +258,8 @@ describe API::API, api: true do body: 'Hi!' end - it 'responds with 500' do - expect(response.status).to eq 500 + it 'responds with resource not found error' do + expect(response.status).to eq 404 end it 'does not create new note' do @@ -227,6 +281,7 @@ describe API::API, api: true do it 'should return modified note' do put api("/projects/#{project.id}/issues/#{issue.id}/"\ "notes/#{issue_note.id}", user), body: 'Hello!' + expect(response.status).to eq(200) expect(json_response['body']).to eq('Hello!') end @@ -234,12 +289,14 @@ describe API::API, api: true do it 'should return a 404 error when note id not found' do put api("/projects/#{project.id}/issues/#{issue.id}/notes/12345", user), body: 'Hello!' + expect(response.status).to eq(404) end it 'should return a 400 bad request error if body not given' do put api("/projects/#{project.id}/issues/#{issue.id}/"\ "notes/#{issue_note.id}", user) + expect(response.status).to eq(400) end end @@ -248,6 +305,7 @@ describe API::API, api: true do it 'should return modified note' do put api("/projects/#{project.id}/snippets/#{snippet.id}/"\ "notes/#{snippet_note.id}", user), body: 'Hello!' + expect(response.status).to eq(200) expect(json_response['body']).to eq('Hello!') end @@ -255,6 +313,7 @@ describe API::API, api: true do it 'should return a 404 error when note id not found' do put api("/projects/#{project.id}/snippets/#{snippet.id}/"\ "notes/12345", user), body: "Hello!" + expect(response.status).to eq(404) end end @@ -263,6 +322,7 @@ describe API::API, api: true do it 'should return modified note' do put api("/projects/#{project.id}/merge_requests/#{merge_request.id}/"\ "notes/#{merge_request_note.id}", user), body: 'Hello!' + expect(response.status).to eq(200) expect(json_response['body']).to eq('Hello!') end @@ -270,6 +330,7 @@ describe API::API, api: true do it 'should return a 404 error when note id not found' do put api("/projects/#{project.id}/merge_requests/#{merge_request.id}/"\ "notes/12345", user), body: "Hello!" + expect(response.status).to eq(404) end end diff --git a/spec/requests/api/project_members_spec.rb b/spec/requests/api/project_members_spec.rb index c112ca5e3ca..44b532b10e1 100644 --- a/spec/requests/api/project_members_spec.rb +++ b/spec/requests/api/project_members_spec.rb @@ -133,7 +133,7 @@ describe API::API, api: true do delete api("/projects/#{project.id}/members/#{user3.id}", user) expect do delete api("/projects/#{project.id}/members/#{user3.id}", user) - end.to_not change { ProjectMember.count } + end.not_to change { ProjectMember.count } expect(response.status).to eq(200) end diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index 66193eac051..f167813e07d 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -10,20 +10,20 @@ describe API::API, api: true do let(:admin) { create(:admin) } let(:project) { create(:project, creator_id: user.id, namespace: user.namespace) } 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, :public, author: user, project: project, title: 'example') } 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, + :private, name: 'second_project', path: 'second_project', creator_id: user.id, namespace: user.namespace, merge_requests_enabled: false, issues_enabled: false, wiki_enabled: false, - snippets_enabled: false, visibility_level: 0) + snippets_enabled: false) end let(:project_member3) do create(:project_member, @@ -164,21 +164,18 @@ describe API::API, api: true do end describe 'GET /projects/starred' do + let(:public_project) { create(:project, :public) } + before do - admin.starred_projects << project - admin.save! + project_member2 + user3.update_attributes(starred_projects: [project, project2, project3, public_project]) end - it 'should return the starred projects' do - get api('/projects/all', admin) + it 'should return the starred projects viewable by the user' do + get api('/projects/starred', user3) expect(response.status).to eq(200) expect(json_response).to be_an Array - - expect(json_response).to satisfy do |response| - response.one? do |entry| - entry['name'] == project.name - end - end + expect(json_response.map { |project| project['id'] }).to contain_exactly(project.id, public_project.id) end end diff --git a/spec/requests/api/runners_spec.rb b/spec/requests/api/runners_spec.rb index 3af61d4b335..73ae8ef631c 100644 --- a/spec/requests/api/runners_spec.rb +++ b/spec/requests/api/runners_spec.rb @@ -184,21 +184,24 @@ describe API::Runners, api: true 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'] + update_runner(shared_runner.id, admin, description: "#{description}_updated", + active: !active, + tag_list: ['ruby2.1', 'pgsql', 'mysql'], + run_untagged: 'false') 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') + expect(shared_runner.run_untagged?).to be false 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' + update_runner(specific_runner.id, admin, description: 'test') specific_runner.reload expect(response.status).to eq(200) @@ -208,10 +211,14 @@ describe API::Runners, api: true do end it 'should return 404 if runner does not exists' do - put api('/runners/9999', admin), description: 'test' + update_runner(9999, admin, description: 'test') expect(response.status).to eq(404) end + + def update_runner(id, user, args) + put api("/runners/#{id}", user), args + end end context 'authorized user' do diff --git a/spec/requests/api/system_hooks_spec.rb b/spec/requests/api/system_hooks_spec.rb index 3e676515488..94eebc48ec8 100644 --- a/spec/requests/api/system_hooks_spec.rb +++ b/spec/requests/api/system_hooks_spec.rb @@ -49,7 +49,7 @@ describe API::API, api: true do it "should not create new hook without url" do expect do post api("/hooks", admin) - end.to_not change { SystemHook.count } + end.not_to change { SystemHook.count } end end diff --git a/spec/requests/api/triggers_spec.rb b/spec/requests/api/triggers_spec.rb index 0510b77a39b..fdd4ec6d761 100644 --- a/spec/requests/api/triggers_spec.rb +++ b/spec/requests/api/triggers_spec.rb @@ -23,7 +23,7 @@ describe API::API do end before do - stub_ci_commit_to_return_yaml_file + stub_ci_pipeline_to_return_yaml_file end context 'Handles errors' do @@ -44,13 +44,13 @@ describe API::API do end context 'Have a commit' do - let(:commit) { project.ci_commits.last } + let(:pipeline) { project.pipelines.last } it 'should create builds' do post api("/projects/#{project.id}/trigger/builds"), options.merge(ref: 'master') expect(response.status).to eq(201) - commit.builds.reload - expect(commit.builds.size).to eq(2) + pipeline.builds.reload + expect(pipeline.builds.size).to eq(2) end it 'should return bad request with no builds created if there\'s no commit for that ref' do @@ -79,8 +79,8 @@ describe API::API do it 'create trigger request with variables' do post api("/projects/#{project.id}/trigger/builds"), options.merge(variables: variables, ref: 'master') expect(response.status).to eq(201) - commit.builds.reload - expect(commit.builds.first.trigger_request.variables).to eq(variables) + pipeline.builds.reload + expect(pipeline.builds.first.trigger_request.variables).to eq(variables) end end end diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb index 40b24c125b5..a7690f430c4 100644 --- a/spec/requests/api/users_spec.rb +++ b/spec/requests/api/users_spec.rb @@ -20,7 +20,7 @@ describe API::API, api: true do end context "when authenticated" do - #These specs are written just in case API authentication is not required anymore + # These specs are written just in case API authentication is not required anymore context "when public level is restricted" do before do stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC]) diff --git a/spec/requests/ci/api/builds_spec.rb b/spec/requests/ci/api/builds_spec.rb index dfd361a2cdd..e8508f8f950 100644 --- a/spec/requests/ci/api/builds_spec.rb +++ b/spec/requests/ci/api/builds_spec.rb @@ -7,7 +7,7 @@ describe Ci::API::API do let(:project) { FactoryGirl.create(:empty_project) } before do - stub_ci_commit_to_return_yaml_file + stub_ci_pipeline_to_return_yaml_file end describe "Builds API for runners" do @@ -20,9 +20,9 @@ describe Ci::API::API do describe "POST /builds/register" do it "should start a build" do - commit = FactoryGirl.create(:ci_commit, project: project, ref: 'master') - commit.create_builds(nil) - build = commit.builds.first + pipeline = FactoryGirl.create(:ci_pipeline, project: project, ref: 'master') + pipeline.create_builds(nil) + build = pipeline.builds.first post ci_api("/builds/register"), token: runner.token, info: { platform: :darwin } @@ -38,8 +38,8 @@ describe Ci::API::API do end it "should return 404 error if no builds for specific runner" do - commit = FactoryGirl.create(:ci_commit, project: shared_project) - FactoryGirl.create(:ci_build, commit: commit, status: 'pending') + pipeline = FactoryGirl.create(:ci_pipeline, project: shared_project) + FactoryGirl.create(:ci_build, pipeline: pipeline, status: 'pending') post ci_api("/builds/register"), token: runner.token @@ -47,8 +47,8 @@ describe Ci::API::API do end it "should return 404 error if no builds for shared runner" do - commit = FactoryGirl.create(:ci_commit, project: project) - FactoryGirl.create(:ci_build, commit: commit, status: 'pending') + pipeline = FactoryGirl.create(:ci_pipeline, project: project) + FactoryGirl.create(:ci_build, pipeline: pipeline, status: 'pending') post ci_api("/builds/register"), token: shared_runner.token @@ -56,8 +56,8 @@ describe Ci::API::API do end it "returns options" do - commit = FactoryGirl.create(:ci_commit, project: project, ref: 'master') - commit.create_builds(nil) + pipeline = FactoryGirl.create(:ci_pipeline, project: project, ref: 'master') + pipeline.create_builds(nil) post ci_api("/builds/register"), token: runner.token, info: { platform: :darwin } @@ -66,8 +66,8 @@ describe Ci::API::API do end it "returns variables" do - commit = FactoryGirl.create(:ci_commit, project: project, ref: 'master') - commit.create_builds(nil) + pipeline = FactoryGirl.create(:ci_pipeline, project: project, ref: 'master') + pipeline.create_builds(nil) project.variables << Ci::Variable.new(key: "SECRET_KEY", value: "secret_value") post ci_api("/builds/register"), token: runner.token, info: { platform: :darwin } @@ -83,10 +83,10 @@ describe Ci::API::API do it "returns variables for triggers" do trigger = FactoryGirl.create(:ci_trigger, project: project) - commit = FactoryGirl.create(:ci_commit, project: project, ref: 'master') + pipeline = FactoryGirl.create(:ci_pipeline, project: project, ref: 'master') - trigger_request = FactoryGirl.create(:ci_trigger_request_with_variables, commit: commit, trigger: trigger) - commit.create_builds(nil, trigger_request) + trigger_request = FactoryGirl.create(:ci_trigger_request_with_variables, pipeline: pipeline, trigger: trigger) + pipeline.create_builds(nil, trigger_request) project.variables << Ci::Variable.new(key: "SECRET_KEY", value: "secret_value") post ci_api("/builds/register"), token: runner.token, info: { platform: :darwin } @@ -103,9 +103,9 @@ describe Ci::API::API do end it "returns dependent builds" do - commit = FactoryGirl.create(:ci_commit, project: project, ref: 'master') - commit.create_builds(nil, nil) - commit.builds.where(stage: 'test').each(&:success) + pipeline = FactoryGirl.create(:ci_pipeline, project: project, ref: 'master') + pipeline.create_builds(nil, nil) + pipeline.builds.where(stage: 'test').each(&:success) post ci_api("/builds/register"), token: runner.token, info: { platform: :darwin } @@ -128,11 +128,43 @@ describe Ci::API::API do end end end + + context 'when build has no tags' do + before do + pipeline = create(:ci_pipeline, project: project) + create(:ci_build, pipeline: pipeline, tags: []) + end + + context 'when runner is allowed to pick untagged builds' do + before { runner.update_column(:run_untagged, true) } + + it 'picks build' do + register_builds + + expect(response).to have_http_status 201 + end + end + + context 'when runner is not allowed to pick untagged builds' do + before { runner.update_column(:run_untagged, false) } + + it 'does not pick build' do + register_builds + + expect(response).to have_http_status 404 + end + end + + def register_builds + post ci_api("/builds/register"), token: runner.token, + info: { platform: :darwin } + end + end end describe "PUT /builds/:id" do - let(:commit) {create(:ci_commit, project: project)} - let(:build) { create(:ci_build, :trace, commit: commit, runner_id: runner.id) } + let(:pipeline) {create(:ci_pipeline, project: project)} + let(:build) { create(:ci_build, :trace, pipeline: pipeline, runner_id: runner.id) } before do build.run! @@ -205,8 +237,8 @@ describe Ci::API::API do 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) { create(:ci_commit, project: project) } - let(:build) { create(:ci_build, commit: commit, runner_id: runner.id) } + let(:pipeline) { create(:ci_pipeline, project: project) } + let(:build) { create(:ci_build, pipeline: pipeline, 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") } @@ -221,13 +253,13 @@ describe Ci::API::API do it "using token as parameter" do post authorize_url, { token: build.token }, headers expect(response.status).to eq(200) - expect(json_response["TempPath"]).to_not be_nil + expect(json_response["TempPath"]).not_to be_nil end it "using token as header" do post authorize_url, {}, headers_with_token expect(response.status).to eq(200) - expect(json_response["TempPath"]).to_not be_nil + expect(json_response["TempPath"]).not_to be_nil end end @@ -402,8 +434,8 @@ describe Ci::API::API do 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' } + { 'Content-Transfer-Encoding' => 'binary', + 'Content-Disposition' => 'attachment; filename=ci_build_artifacts.zip' } end it 'should download artifact' do diff --git a/spec/requests/ci/api/runners_spec.rb b/spec/requests/ci/api/runners_spec.rb index db8189ffb79..43596f07cb5 100644 --- a/spec/requests/ci/api/runners_spec.rb +++ b/spec/requests/ci/api/runners_spec.rb @@ -12,44 +12,85 @@ describe Ci::API::API do end describe "POST /runners/register" do - describe "should create a runner if token provided" do + context 'when runner token is provided' do before { post ci_api("/runners/register"), token: registration_token } - it { expect(response.status).to eq(201) } + it 'creates runner with default values' do + expect(response).to have_http_status 201 + expect(Ci::Runner.first.run_untagged).to be true + end end - describe "should create a runner with description" do - before { post ci_api("/runners/register"), token: registration_token, description: "server.hostname" } + context 'when runner description is provided' do + before do + post ci_api("/runners/register"), token: registration_token, + description: "server.hostname" + end - it { expect(response.status).to eq(201) } - it { expect(Ci::Runner.first.description).to eq("server.hostname") } + it 'creates runner' do + expect(response).to have_http_status 201 + expect(Ci::Runner.first.description).to eq("server.hostname") + end end - describe "should create a runner with tags" do - before { post ci_api("/runners/register"), token: registration_token, tag_list: "tag1, tag2" } + context 'when runner tags are provided' do + before do + post ci_api("/runners/register"), token: registration_token, + tag_list: "tag1, tag2" + end - it { expect(response.status).to eq(201) } - it { expect(Ci::Runner.first.tag_list.sort).to eq(["tag1", "tag2"]) } + it 'creates runner' do + expect(response).to have_http_status 201 + expect(Ci::Runner.first.tag_list.sort).to eq(["tag1", "tag2"]) + end end - describe "should create a runner if project token provided" do + context 'when option for running untagged jobs is provided' do + context 'when tags are provided' do + it 'creates runner' do + post ci_api("/runners/register"), token: registration_token, + run_untagged: false, + tag_list: ['tag'] + + expect(response).to have_http_status 201 + expect(Ci::Runner.first.run_untagged).to be false + end + end + + context 'when tags are not provided' do + it 'does not create runner' do + post ci_api("/runners/register"), token: registration_token, + run_untagged: false + + expect(response).to have_http_status 404 + end + end + end + + context 'when project token is provided' do let(:project) { FactoryGirl.create(:empty_project) } before { post ci_api("/runners/register"), token: project.runners_token } - it { expect(response.status).to eq(201) } - it { expect(project.runners.size).to eq(1) } + it 'creates runner' do + expect(response).to have_http_status 201 + expect(project.runners.size).to eq(1) + end end - it "should return 403 error if token is invalid" do - post ci_api("/runners/register"), token: 'invalid' + context 'when token is invalid' do + it 'returns 403 error' do + post ci_api("/runners/register"), token: 'invalid' - expect(response.status).to eq(403) + expect(response).to have_http_status 403 + end end - it "should return 400 error if no token" do - post ci_api("/runners/register") + context 'when no token provided' do + it 'returns 400 error' do + post ci_api("/runners/register") - expect(response.status).to eq(400) + expect(response).to have_http_status 400 + end end %w(name version revision platform architecture).each do |param| @@ -60,7 +101,7 @@ describe Ci::API::API do it do post ci_api("/runners/register"), token: registration_token, info: { param => value } - expect(response.status).to eq(201) + expect(response).to have_http_status 201 is_expected.to eq(value) end end @@ -71,7 +112,7 @@ describe Ci::API::API do let!(:runner) { FactoryGirl.create(:ci_runner) } before { delete ci_api("/runners/delete"), token: runner.token } - it { expect(response.status).to eq(200) } + it { expect(response).to have_http_status 200 } it { expect(Ci::Runner.count).to eq(0) } end end diff --git a/spec/requests/ci/api/triggers_spec.rb b/spec/requests/ci/api/triggers_spec.rb index 0ef03f9371b..72f6a3c981d 100644 --- a/spec/requests/ci/api/triggers_spec.rb +++ b/spec/requests/ci/api/triggers_spec.rb @@ -15,7 +15,7 @@ describe Ci::API::API do end before do - stub_ci_commit_to_return_yaml_file + stub_ci_pipeline_to_return_yaml_file end context 'Handles errors' do @@ -36,13 +36,13 @@ describe Ci::API::API do end context 'Have a commit' do - let(:commit) { project.ci_commits.last } + let(:pipeline) { project.pipelines.last } it 'should create builds' do post ci_api("/projects/#{project.ci_id}/refs/master/trigger"), options expect(response.status).to eq(201) - commit.builds.reload - expect(commit.builds.size).to eq(2) + pipeline.builds.reload + expect(pipeline.builds.size).to eq(2) end it 'should return bad request with no builds created if there\'s no commit for that ref' do @@ -71,8 +71,8 @@ describe Ci::API::API do it 'create trigger request with variables' do post ci_api("/projects/#{project.ci_id}/refs/master/trigger"), options.merge(variables: variables) expect(response.status).to eq(201) - commit.builds.reload - expect(commit.builds.first.trigger_request.variables).to eq(variables) + pipeline.builds.reload + expect(pipeline.builds.first.trigger_request.variables).to eq(variables) end end end diff --git a/spec/requests/git_http_spec.rb b/spec/requests/git_http_spec.rb new file mode 100644 index 00000000000..c44a4a7a1fc --- /dev/null +++ b/spec/requests/git_http_spec.rb @@ -0,0 +1,395 @@ +require "spec_helper" + +describe 'Git HTTP requests', lib: true do + let(:user) { create(:user) } + let(:project) { create(:project, path: 'project.git-project') } + + it "gives WWW-Authenticate hints" do + clone_get('doesnt/exist.git') + + expect(response.header['WWW-Authenticate']).to start_with('Basic ') + end + + context "when the project doesn't exist" do + context "when no authentication is provided" do + it "responds with status 401 (no project existence information leak)" do + download('doesnt/exist.git') do |response| + expect(response.status).to eq(401) + end + end + end + + context "when username and password are provided" do + context "when authentication fails" do + it "responds with status 401" do + download('doesnt/exist.git', user: user.username, password: "nope") do |response| + expect(response.status).to eq(401) + end + end + end + + context "when authentication succeeds" do + it "responds with status 404" do + download('/doesnt/exist.git', user: user.username, password: user.password) do |response| + expect(response.status).to eq(404) + end + end + end + end + end + + context "when the Wiki for a project exists" do + it "responds with the right project" do + wiki = ProjectWiki.new(project) + project.update_attribute(:visibility_level, Project::PUBLIC) + + download("/#{wiki.repository.path_with_namespace}.git") do |response| + json_body = ActiveSupport::JSON.decode(response.body) + + expect(response.status).to eq(200) + expect(json_body['RepoPath']).to include(wiki.repository.path_with_namespace) + end + end + end + + context "when the project exists" do + let(:path) { "#{project.path_with_namespace}.git" } + + context "when the project is public" do + before do + project.update_attribute(:visibility_level, Project::PUBLIC) + end + + it "downloads get status 200" do + download(path, {}) do |response| + expect(response.status).to eq(200) + end + end + + it "uploads get status 401" do + upload(path, {}) do |response| + expect(response.status).to eq(401) + end + end + + context "with correct credentials" do + let(:env) { { user: user.username, password: user.password } } + + it "uploads get status 200 (because Git hooks do the real check)" do + upload(path, env) do |response| + expect(response.status).to eq(200) + end + end + + context 'but git-receive-pack is disabled' do + it "responds with status 404" do + allow(Gitlab.config.gitlab_shell).to receive(:receive_pack).and_return(false) + + upload(path, env) do |response| + expect(response.status).to eq(404) + end + end + end + end + + context 'but git-upload-pack is disabled' do + it "responds with status 404" do + allow(Gitlab.config.gitlab_shell).to receive(:upload_pack).and_return(false) + + download(path, {}) do |response| + expect(response.status).to eq(404) + end + end + end + end + + context "when the project is private" do + before do + project.update_attribute(:visibility_level, Project::PRIVATE) + end + + context "when no authentication is provided" do + it "responds with status 401 to downloads" do + download(path, {}) do |response| + expect(response.status).to eq(401) + end + end + + it "responds with status 401 to uploads" do + upload(path, {}) do |response| + expect(response.status).to eq(401) + end + end + end + + context "when username and password are provided" do + let(:env) { { user: user.username, password: 'nope' } } + + context "when authentication fails" do + it "responds with status 401" do + download(path, env) do |response| + expect(response.status).to eq(401) + end + end + + context "when the user is IP banned" do + it "responds with status 401" do + expect(Rack::Attack::Allow2Ban).to receive(:filter).and_return(true) + allow_any_instance_of(Rack::Request).to receive(:ip).and_return('1.2.3.4') + + clone_get(path, env) + + expect(response.status).to eq(401) + end + end + end + + context "when authentication succeeds" do + let(:env) { { user: user.username, password: user.password } } + + context "when the user has access to the project" do + before do + project.team << [user, :master] + end + + context "when the user is blocked" do + it "responds with status 404" do + user.block + project.team << [user, :master] + + download(path, env) do |response| + expect(response.status).to eq(404) + end + end + end + + context "when the user isn't blocked" do + it "downloads get status 200" do + expect(Rack::Attack::Allow2Ban).to receive(:reset) + + clone_get(path, env) + + expect(response.status).to eq(200) + end + + it "uploads get status 200" do + upload(path, env) do |response| + expect(response.status).to eq(200) + end + end + end + + context "when an oauth token is provided" do + before do + application = Doorkeeper::Application.create!(name: "MyApp", redirect_uri: "https://app.com", owner: user) + @token = Doorkeeper::AccessToken.create!(application_id: application.id, resource_owner_id: user.id) + end + + it "downloads get status 200" do + clone_get "#{project.path_with_namespace}.git", user: 'oauth2', password: @token.token + + expect(response.status).to eq(200) + end + + it "uploads get status 401 (no project existence information leak)" do + push_get "#{project.path_with_namespace}.git", user: 'oauth2', password: @token.token + + expect(response.status).to eq(401) + end + end + + context "when blank password attempts follow a valid login" do + def attempt_login(include_password) + password = include_password ? user.password : "" + clone_get path, user: user.username, password: password + response.status + end + + it "repeated attempts followed by successful attempt" do + options = Gitlab.config.rack_attack.git_basic_auth + maxretry = options[:maxretry] - 1 + ip = '1.2.3.4' + + allow_any_instance_of(Rack::Request).to receive(:ip).and_return(ip) + Rack::Attack::Allow2Ban.reset(ip, options) + + maxretry.times.each do + expect(attempt_login(false)).to eq(401) + end + + expect(attempt_login(true)).to eq(200) + expect(Rack::Attack::Allow2Ban.banned?(ip)).to be_falsey + + maxretry.times.each do + expect(attempt_login(false)).to eq(401) + end + + Rack::Attack::Allow2Ban.reset(ip, options) + end + end + end + + context "when the user doesn't have access to the project" do + it "downloads get status 404" do + download(path, user: user.username, password: user.password) do |response| + expect(response.status).to eq(404) + end + end + + it "uploads get status 200 (because Git hooks do the real check)" do + upload(path, user: user.username, password: user.password) do |response| + expect(response.status).to eq(200) + end + end + end + end + end + + context "when a gitlab ci token is provided" do + let(:token) { 123 } + let(:project) { FactoryGirl.create :empty_project } + + before do + project.update_attributes(runners_token: token, builds_enabled: true) + end + + it "downloads get status 200" do + clone_get "#{project.path_with_namespace}.git", user: 'gitlab-ci-token', password: token + + expect(response.status).to eq(200) + end + + it "uploads get status 401 (no project existence information leak)" do + push_get "#{project.path_with_namespace}.git", user: 'gitlab-ci-token', password: token + + expect(response.status).to eq(401) + end + end + end + end + + context "when the project path doesn't end in .git" do + context "GET info/refs" do + let(:path) { "/#{project.path_with_namespace}/info/refs" } + + context "when no params are added" do + before { get path } + + it "redirects to the .git suffix version" do + expect(response).to redirect_to("/#{project.path_with_namespace}.git/info/refs") + end + end + + context "when the upload-pack service is requested" do + let(:params) { { service: 'git-upload-pack' } } + before { get path, params } + + it "redirects to the .git suffix version" do + expect(response).to redirect_to("/#{project.path_with_namespace}.git/info/refs?service=#{params[:service]}") + end + end + + context "when the receive-pack service is requested" do + let(:params) { { service: 'git-receive-pack' } } + before { get path, params } + + it "redirects to the .git suffix version" do + expect(response).to redirect_to("/#{project.path_with_namespace}.git/info/refs?service=#{params[:service]}") + end + end + + context "when the params are anything else" do + let(:params) { { service: 'git-implode-pack' } } + before { get path, params } + + it "redirects to the sign-in page" do + expect(response).to redirect_to(new_user_session_path) + end + end + end + + context "POST git-upload-pack" do + it "fails to find a route" do + expect { clone_post(project.path_with_namespace) }.to raise_error(ActionController::RoutingError) + end + end + + context "POST git-receive-pack" do + it "failes to find a route" do + expect { push_post(project.path_with_namespace) }.to raise_error(ActionController::RoutingError) + end + end + end + + context "retrieving an info/refs file" do + before { project.update_attribute(:visibility_level, Project::PUBLIC) } + + context "when the file exists" do + before do + # Provide a dummy file in its place + allow_any_instance_of(Repository).to receive(:blob_at).and_call_original + allow_any_instance_of(Repository).to receive(:blob_at).with('5937ac0a7beb003549fc5fd26fc247adbce4a52e', 'info/refs') do + Gitlab::Git::Blob.find(project.repository, 'master', '.gitignore') + end + + get "/#{project.path_with_namespace}/blob/master/info/refs" + end + + it "returns the file" do + expect(response.status).to eq(200) + end + end + + context "when the file exists" do + before { get "/#{project.path_with_namespace}/blob/master/info/refs" } + + it "returns not found" do + expect(response.status).to eq(404) + end + end + end + + def clone_get(project, options={}) + get "/#{project}/info/refs", { service: 'git-upload-pack' }, auth_env(*options.values_at(:user, :password)) + end + + def clone_post(project, options={}) + post "/#{project}/git-upload-pack", {}, auth_env(*options.values_at(:user, :password)) + end + + def push_get(project, options={}) + get "/#{project}/info/refs", { service: 'git-receive-pack' }, auth_env(*options.values_at(:user, :password)) + end + + def push_post(project, options={}) + post "/#{project}/git-receive-pack", {}, auth_env(*options.values_at(:user, :password)) + end + + def download(project, user: nil, password: nil) + args = [project, { user: user, password: password }] + + clone_get(*args) + yield response + + clone_post(*args) + yield response + end + + def upload(project, user: nil, password: nil) + args = [project, { user: user, password: password }] + + push_get(*args) + yield response + + push_post(*args) + yield response + end + + def auth_env(user, password) + if user && password + { 'HTTP_AUTHORIZATION' => ActionController::HttpAuthentication::Basic.encode_credentials(user, password) } + else + {} + end + end +end diff --git a/spec/requests/jwt_controller_spec.rb b/spec/requests/jwt_controller_spec.rb new file mode 100644 index 00000000000..d2d4a9eca18 --- /dev/null +++ b/spec/requests/jwt_controller_spec.rb @@ -0,0 +1,72 @@ +require 'spec_helper' + +describe JwtController do + let(:service) { double(execute: {}) } + let(:service_class) { double(new: service) } + let(:service_name) { 'test' } + let(:parameters) { { service: service_name } } + + before { stub_const('JwtController::SERVICES', service_name => service_class) } + + context 'existing service' do + subject! { get '/jwt/auth', parameters } + + it { expect(response.status).to eq(200) } + + context 'returning custom http code' do + let(:service) { double(execute: { http_status: 505 }) } + + it { expect(response.status).to eq(505) } + end + end + + context 'when using authorized request' do + context 'using CI token' do + let(:project) { create(:empty_project, runners_token: 'token', builds_enabled: builds_enabled) } + let(:headers) { { authorization: credentials('gitlab-ci-token', project.runners_token) } } + + subject! { get '/jwt/auth', parameters, headers } + + context 'project with enabled CI' do + let(:builds_enabled) { true } + + it { expect(service_class).to have_received(:new).with(project, nil, parameters) } + end + + context 'project with disabled CI' do + let(:builds_enabled) { false } + + it { expect(response.status).to eq(403) } + end + end + + context 'using User login' do + let(:user) { create(:user) } + let(:headers) { { authorization: credentials('user', 'password') } } + + before { expect(Gitlab::Auth).to receive(:find_with_user_password).with('user', 'password').and_return(user) } + + subject! { get '/jwt/auth', parameters, headers } + + it { expect(service_class).to have_received(:new).with(nil, user, parameters) } + end + + context 'using invalid login' do + let(:headers) { { authorization: credentials('invalid', 'password') } } + + subject! { get '/jwt/auth', parameters, headers } + + it { expect(response.status).to eq(403) } + end + end + + context 'unknown service' do + subject! { get '/jwt/auth', service: 'unknown' } + + it { expect(response.status).to eq(404) } + end + + def credentials(login, password) + ActionController::HttpAuthentication::Basic.encode_credentials(login, password) + end +end |