diff options
author | Phil Hughes <me@iamphill.com> | 2018-06-01 14:12:18 +0300 |
---|---|---|
committer | Phil Hughes <me@iamphill.com> | 2018-06-01 14:12:18 +0300 |
commit | 9ffb60acd8ab646eabfa32e6faaa3802c2aef2a6 (patch) | |
tree | 71322166368986c869e5607ec9f2f1066d0c4e3e /spec | |
parent | 8868c919a3c798f7d5753e250723df6d285de7b0 (diff) | |
parent | e206e32881e4fbfcbe647d7b2ee713c99ef1bf99 (diff) |
Merge branch 'master' into ide-list-merge-requests
Diffstat (limited to 'spec')
150 files changed, 2499 insertions, 11579 deletions
diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb index f0caac40afd..b048da1991c 100644 --- a/spec/controllers/application_controller_spec.rb +++ b/spec/controllers/application_controller_spec.rb @@ -146,35 +146,43 @@ describe ApplicationController do end end - describe '#authenticate_user_from_rss_token' do - describe "authenticating a user from an RSS token" do + describe '#authenticate_sessionless_user!' do + describe 'authenticating a user from a feed token' do controller(described_class) do def index render text: 'authenticated' end end - context "when the 'rss_token' param is populated with the RSS token" do + context "when the 'feed_token' param is populated with the feed token" do context 'when the request format is atom' do it "logs the user in" do - get :index, rss_token: user.rss_token, format: :atom + get :index, feed_token: user.feed_token, format: :atom expect(response).to have_gitlab_http_status 200 expect(response.body).to eq 'authenticated' end end - context 'when the request format is not atom' do + context 'when the request format is ics' do + it "logs the user in" do + get :index, feed_token: user.feed_token, format: :ics + expect(response).to have_gitlab_http_status 200 + expect(response.body).to eq 'authenticated' + end + end + + context 'when the request format is neither atom nor ics' do it "doesn't log the user in" do - get :index, rss_token: user.rss_token + get :index, feed_token: user.feed_token expect(response.status).not_to have_gitlab_http_status 200 expect(response.body).not_to eq 'authenticated' end end end - context "when the 'rss_token' param is populated with an invalid RSS token" do + context "when the 'feed_token' param is populated with an invalid feed token" do it "doesn't log the user" do - get :index, rss_token: "token" + get :index, feed_token: 'token', format: :atom expect(response.status).not_to eq 200 expect(response.body).not_to eq 'authenticated' end @@ -454,7 +462,7 @@ describe ApplicationController do end it 'renders a 403 when the sessionless user did not accept the terms' do - get :index, rss_token: user.rss_token, format: :atom + get :index, feed_token: user.feed_token, format: :atom expect(response).to have_gitlab_http_status(403) end @@ -462,7 +470,7 @@ describe ApplicationController do it 'renders a 200 when the sessionless user accepted the terms' do accept_terms(user) - get :index, rss_token: user.rss_token, format: :atom + get :index, feed_token: user.feed_token, format: :atom expect(response).to have_gitlab_http_status(200) end diff --git a/spec/controllers/groups/shared_projects_controller_spec.rb b/spec/controllers/groups/shared_projects_controller_spec.rb new file mode 100644 index 00000000000..d8fa41abb18 --- /dev/null +++ b/spec/controllers/groups/shared_projects_controller_spec.rb @@ -0,0 +1,59 @@ +require 'spec_helper' + +describe Groups::SharedProjectsController do + def get_shared_projects(params = {}) + get :index, params.reverse_merge(format: :json, group_id: group.full_path) + end + + def share_project(project) + Projects::GroupLinks::CreateService.new( + project, + user, + link_group_access: ProjectGroupLink::DEVELOPER + ).execute(group) + end + + set(:group) { create(:group) } + set(:user) { create(:user) } + set(:shared_project) do + shared_project = create(:project, namespace: user.namespace) + share_project(shared_project) + + shared_project + end + + let(:json_project_ids) { json_response.map { |project_info| project_info['id'] } } + + before do + sign_in(user) + end + + describe 'GET #index' do + it 'returns only projects shared with the group' do + create(:project, namespace: group) + + get_shared_projects + + expect(json_project_ids).to contain_exactly(shared_project.id) + end + + it 'allows filtering shared projects' do + project = create(:project, :archived, namespace: user.namespace, name: "Searching for") + share_project(project) + + get_shared_projects(filter: 'search') + + expect(json_project_ids).to contain_exactly(project.id) + end + + it 'allows sorting projects' do + shared_project.update!(name: 'bbb') + second_project = create(:project, namespace: user.namespace, name: 'aaaa') + share_project(second_project) + + get_shared_projects(sort: 'name_asc') + + expect(json_project_ids).to eq([second_project.id, shared_project.id]) + end + end +end diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb index 5efaaf2bb3a..6e8de6db9c3 100644 --- a/spec/controllers/projects/merge_requests_controller_spec.rb +++ b/spec/controllers/projects/merge_requests_controller_spec.rb @@ -326,8 +326,8 @@ describe Projects::MergeRequestsController do expect(json_response).to eq('status' => 'success') end - it 'starts the merge immediately' do - expect(MergeWorker).to receive(:perform_async).with(merge_request.id, anything, anything) + it 'starts the merge immediately with permitted params' do + expect(MergeWorker).to receive(:perform_async).with(merge_request.id, anything, { 'squash' => false }) merge_with_sha end diff --git a/spec/factories/clusters/applications/helm.rb b/spec/factories/clusters/applications/helm.rb index 3deca103578..3e4277e4ba6 100644 --- a/spec/factories/clusters/applications/helm.rb +++ b/spec/factories/clusters/applications/helm.rb @@ -35,5 +35,8 @@ FactoryBot.define do factory :clusters_applications_ingress, class: Clusters::Applications::Ingress factory :clusters_applications_prometheus, class: Clusters::Applications::Prometheus factory :clusters_applications_runner, class: Clusters::Applications::Runner + factory :clusters_applications_jupyter, class: Clusters::Applications::Jupyter do + oauth_application factory: :oauth_application + end end end diff --git a/spec/factories/users.rb b/spec/factories/users.rb index 769fd656e7a..59db8cdc34b 100644 --- a/spec/factories/users.rb +++ b/spec/factories/users.rb @@ -12,10 +12,6 @@ FactoryBot.define do user.notification_email = user.email end - before(:create) do |user| - user.ensure_rss_token - end - trait :admin do admin true end diff --git a/spec/features/atom/dashboard_issues_spec.rb b/spec/features/atom/dashboard_issues_spec.rb index fb6c71ce997..da7749b42d2 100644 --- a/spec/features/atom/dashboard_issues_spec.rb +++ b/spec/features/atom/dashboard_issues_spec.rb @@ -31,20 +31,20 @@ describe "Dashboard Issues Feed" do expect(body).to have_selector('title', text: "#{user.name} issues") end - it "renders atom feed via RSS token" do - visit issues_dashboard_path(:atom, rss_token: user.rss_token, assignee_id: user.id) + it "renders atom feed via feed token" do + visit issues_dashboard_path(:atom, feed_token: user.feed_token, assignee_id: user.id) expect(response_headers['Content-Type']).to have_content('application/atom+xml') expect(body).to have_selector('title', text: "#{user.name} issues") end it "renders atom feed with url parameters" do - visit issues_dashboard_path(:atom, rss_token: user.rss_token, state: 'opened', assignee_id: user.id) + visit issues_dashboard_path(:atom, feed_token: user.feed_token, state: 'opened', assignee_id: user.id) link = find('link[type="application/atom+xml"]') params = CGI.parse(URI.parse(link[:href]).query) - expect(params).to include('rss_token' => [user.rss_token]) + expect(params).to include('feed_token' => [user.feed_token]) expect(params).to include('state' => ['opened']) expect(params).to include('assignee_id' => [user.id.to_s]) end @@ -53,7 +53,7 @@ describe "Dashboard Issues Feed" do let!(:issue2) { create(:issue, author: user, assignees: [assignee], project: project2, description: 'test desc') } it "renders issue fields" do - visit issues_dashboard_path(:atom, rss_token: user.rss_token, assignee_id: assignee.id) + visit issues_dashboard_path(:atom, feed_token: user.feed_token, assignee_id: assignee.id) entry = find(:xpath, "//feed/entry[contains(summary/text(),'#{issue2.title}')]") @@ -76,7 +76,7 @@ describe "Dashboard Issues Feed" do end it "renders issue label and milestone info" do - visit issues_dashboard_path(:atom, rss_token: user.rss_token, assignee_id: assignee.id) + visit issues_dashboard_path(:atom, feed_token: user.feed_token, assignee_id: assignee.id) entry = find(:xpath, "//feed/entry[contains(summary/text(),'#{issue1.title}')]") diff --git a/spec/features/atom/dashboard_spec.rb b/spec/features/atom/dashboard_spec.rb index c6683bb3bc9..462eab07a75 100644 --- a/spec/features/atom/dashboard_spec.rb +++ b/spec/features/atom/dashboard_spec.rb @@ -13,9 +13,9 @@ describe "Dashboard Feed" do end end - context "projects atom feed via RSS token" do + context "projects atom feed via feed token" do it "renders projects atom feed" do - visit dashboard_projects_path(:atom, rss_token: user.rss_token) + visit dashboard_projects_path(:atom, feed_token: user.feed_token) expect(body).to have_selector('feed title') end end @@ -29,7 +29,7 @@ describe "Dashboard Feed" do project.add_master(user) issue_event(issue, user) note_event(note, user) - visit dashboard_projects_path(:atom, rss_token: user.rss_token) + visit dashboard_projects_path(:atom, feed_token: user.feed_token) end it "has issue opened event" do diff --git a/spec/features/atom/issues_spec.rb b/spec/features/atom/issues_spec.rb index 525ce23aa56..ee3570a5b2b 100644 --- a/spec/features/atom/issues_spec.rb +++ b/spec/features/atom/issues_spec.rb @@ -45,10 +45,10 @@ describe 'Issues Feed' do end end - context 'when authenticated via RSS token' do + context 'when authenticated via feed token' do it 'renders atom feed' do visit project_issues_path(project, :atom, - rss_token: user.rss_token) + feed_token: user.feed_token) expect(response_headers['Content-Type']) .to have_content('application/atom+xml') @@ -61,24 +61,23 @@ describe 'Issues Feed' do end it "renders atom feed with url parameters for project issues" do - visit project_issues_path(project, - :atom, rss_token: user.rss_token, state: 'opened', assignee_id: user.id) + visit project_issues_path(project, :atom, feed_token: user.feed_token, state: 'opened', assignee_id: user.id) link = find('link[type="application/atom+xml"]') params = CGI.parse(URI.parse(link[:href]).query) - expect(params).to include('rss_token' => [user.rss_token]) + expect(params).to include('feed_token' => [user.feed_token]) expect(params).to include('state' => ['opened']) expect(params).to include('assignee_id' => [user.id.to_s]) end it "renders atom feed with url parameters for group issues" do - visit issues_group_path(group, :atom, rss_token: user.rss_token, state: 'opened', assignee_id: user.id) + visit issues_group_path(group, :atom, feed_token: user.feed_token, state: 'opened', assignee_id: user.id) link = find('link[type="application/atom+xml"]') params = CGI.parse(URI.parse(link[:href]).query) - expect(params).to include('rss_token' => [user.rss_token]) + expect(params).to include('feed_token' => [user.feed_token]) expect(params).to include('state' => ['opened']) expect(params).to include('assignee_id' => [user.id.to_s]) end diff --git a/spec/features/atom/users_spec.rb b/spec/features/atom/users_spec.rb index 2d074c115dd..eeaaa40fe21 100644 --- a/spec/features/atom/users_spec.rb +++ b/spec/features/atom/users_spec.rb @@ -13,9 +13,9 @@ describe "User Feed" do end end - context 'user atom feed via RSS token' do + context 'user atom feed via feed token' do it "renders user atom feed" do - visit user_path(user, :atom, rss_token: user.rss_token) + visit user_path(user, :atom, feed_token: user.feed_token) expect(body).to have_selector('feed title') end end @@ -51,7 +51,7 @@ describe "User Feed" do issue_event(issue, user) note_event(note, user) merge_request_event(merge_request, user) - visit user_path(user, :atom, rss_token: user.rss_token) + visit user_path(user, :atom, feed_token: user.feed_token) end it 'has issue opened event' do diff --git a/spec/features/boards/issue_ordering_spec.rb b/spec/features/boards/issue_ordering_spec.rb index 193b1dfabbd..32bd7b88840 100644 --- a/spec/features/boards/issue_ordering_spec.rb +++ b/spec/features/boards/issue_ordering_spec.rb @@ -35,7 +35,7 @@ describe 'Issue Boards', :js do end it 'moves un-ordered issue to top of list' do - drag(from_index: 3, to_index: 0) + drag(from_index: 3, to_index: 0, duration: 1180) wait_for_requests @@ -156,12 +156,13 @@ describe 'Issue Boards', :js do end end - def drag(selector: '.board-list', list_from_index: 1, from_index: 0, to_index: 0, list_to_index: 1) + def drag(selector: '.board-list', list_from_index: 1, from_index: 0, to_index: 0, list_to_index: 1, duration: 1000) drag_to(selector: selector, scrollable: '#board-app', list_from_index: list_from_index, from_index: from_index, to_index: to_index, - list_to_index: list_to_index) + list_to_index: list_to_index, + duration: duration) end end diff --git a/spec/features/dashboard/activity_spec.rb b/spec/features/dashboard/activity_spec.rb index a74a8aac2b2..941208fa244 100644 --- a/spec/features/dashboard/activity_spec.rb +++ b/spec/features/dashboard/activity_spec.rb @@ -12,8 +12,8 @@ feature 'Dashboard > Activity' do visit activity_dashboard_path end - it_behaves_like "it has an RSS button with current_user's RSS token" - it_behaves_like "an autodiscoverable RSS feed with current_user's RSS token" + it_behaves_like "it has an RSS button with current_user's feed token" + it_behaves_like "an autodiscoverable RSS feed with current_user's feed token" end context 'event filters', :js do diff --git a/spec/features/dashboard/issues_filter_spec.rb b/spec/features/dashboard/issues_filter_spec.rb index bab34ac9346..8d0b0be1bd4 100644 --- a/spec/features/dashboard/issues_filter_spec.rb +++ b/spec/features/dashboard/issues_filter_spec.rb @@ -47,15 +47,15 @@ feature 'Dashboard Issues filtering', :js do it 'updates atom feed link' do visit_issues(milestone_title: '', assignee_id: user.id) - link = find('.nav-controls a[title="Subscribe"]') + link = find('.nav-controls a[title="Subscribe to RSS feed"]') params = CGI.parse(URI.parse(link[:href]).query) auto_discovery_link = find('link[type="application/atom+xml"]', visible: false) auto_discovery_params = CGI.parse(URI.parse(auto_discovery_link[:href]).query) - expect(params).to include('rss_token' => [user.rss_token]) + expect(params).to include('feed_token' => [user.feed_token]) expect(params).to include('milestone_title' => ['']) expect(params).to include('assignee_id' => [user.id.to_s]) - expect(auto_discovery_params).to include('rss_token' => [user.rss_token]) + expect(auto_discovery_params).to include('feed_token' => [user.feed_token]) expect(auto_discovery_params).to include('milestone_title' => ['']) expect(auto_discovery_params).to include('assignee_id' => [user.id.to_s]) end diff --git a/spec/features/dashboard/issues_spec.rb b/spec/features/dashboard/issues_spec.rb index e41a2e4ce09..3cc7b38550d 100644 --- a/spec/features/dashboard/issues_spec.rb +++ b/spec/features/dashboard/issues_spec.rb @@ -56,8 +56,8 @@ RSpec.describe 'Dashboard Issues' do expect(page).to have_current_path(issues_dashboard_url(assignee_id: current_user.id, state: 'closed'), url: true) end - it_behaves_like "it has an RSS button with current_user's RSS token" - it_behaves_like "an autodiscoverable RSS feed with current_user's RSS token" + it_behaves_like "it has an RSS button with current_user's feed token" + it_behaves_like "an autodiscoverable RSS feed with current_user's feed token" end describe 'new issue dropdown' do diff --git a/spec/features/dashboard/projects_spec.rb b/spec/features/dashboard/projects_spec.rb index 257a3822503..ef2f0b5b31a 100644 --- a/spec/features/dashboard/projects_spec.rb +++ b/spec/features/dashboard/projects_spec.rb @@ -10,7 +10,7 @@ feature 'Dashboard Projects' do sign_in(user) end - it_behaves_like "an autodiscoverable RSS feed with current_user's RSS token" do + it_behaves_like "an autodiscoverable RSS feed with current_user's feed token" do before do visit dashboard_projects_path end diff --git a/spec/features/error_pages_spec.rb b/spec/features/error_pages_spec.rb new file mode 100644 index 00000000000..cd7bcf29cc9 --- /dev/null +++ b/spec/features/error_pages_spec.rb @@ -0,0 +1,42 @@ +require 'spec_helper' + +describe 'Error Pages' do + let(:user) { create(:user) } + let(:project) { create(:project, :public) } + + before do + sign_in(user) + end + + shared_examples 'shows nav links' do + it 'shows nav links' do + expect(page).to have_link("Home", href: root_path) + expect(page).to have_link("Help", href: help_path) + expect(page).to have_link(nil, href: destroy_user_session_path) + end + end + + describe '404' do + before do + visit '/not-a-real-page' + end + + it 'allows user to search' do + fill_in 'search', with: 'something' + click_button 'Search' + + expect(page).to have_current_path(%r{^/search\?.*search=something.*}) + end + + it_behaves_like 'shows nav links' + end + + describe '403' do + before do + visit '/' + visit edit_project_path(project) + end + + it_behaves_like 'shows nav links' + end +end diff --git a/spec/features/groups/activity_spec.rb b/spec/features/groups/activity_spec.rb index 7bc809b3104..0d7d3771071 100644 --- a/spec/features/groups/activity_spec.rb +++ b/spec/features/groups/activity_spec.rb @@ -15,8 +15,8 @@ feature 'Group activity page' do visit path end - it_behaves_like "it has an RSS button with current_user's RSS token" - it_behaves_like "an autodiscoverable RSS feed with current_user's RSS token" + it_behaves_like "it has an RSS button with current_user's feed token" + it_behaves_like "an autodiscoverable RSS feed with current_user's feed token" end context 'when project is in the group', :js do @@ -39,7 +39,7 @@ feature 'Group activity page' do visit path end - it_behaves_like "it has an RSS button without an RSS token" - it_behaves_like "an autodiscoverable RSS feed without an RSS token" + it_behaves_like "it has an RSS button without a feed token" + it_behaves_like "an autodiscoverable RSS feed without a feed token" end end diff --git a/spec/features/groups/issues_spec.rb b/spec/features/groups/issues_spec.rb index 90bf7ba49f6..111a24c0d94 100644 --- a/spec/features/groups/issues_spec.rb +++ b/spec/features/groups/issues_spec.rb @@ -16,17 +16,21 @@ feature 'Group issues page' do let(:access_level) { ProjectFeature::ENABLED } context 'when signed in' do - let(:user) { user_in_group } - - it_behaves_like "it has an RSS button with current_user's RSS token" - it_behaves_like "an autodiscoverable RSS feed with current_user's RSS token" + let(:user) do + user_in_group.ensure_feed_token + user_in_group.save! + user_in_group + end + + it_behaves_like "it has an RSS button with current_user's feed token" + it_behaves_like "an autodiscoverable RSS feed with current_user's feed token" end context 'when signed out' do let(:user) { nil } - it_behaves_like "it has an RSS button without an RSS token" - it_behaves_like "an autodiscoverable RSS feed without an RSS token" + it_behaves_like "it has an RSS button without a feed token" + it_behaves_like "an autodiscoverable RSS feed without a feed token" end end diff --git a/spec/features/groups/show_spec.rb b/spec/features/groups/show_spec.rb index 3a0424d60f8..b7a7aa0e174 100644 --- a/spec/features/groups/show_spec.rb +++ b/spec/features/groups/show_spec.rb @@ -14,7 +14,7 @@ feature 'Group show page' do visit path end - it_behaves_like "an autodiscoverable RSS feed with current_user's RSS token" + it_behaves_like "an autodiscoverable RSS feed with current_user's feed token" context 'when group does not exist' do let(:path) { group_path('not-exist') } @@ -29,7 +29,7 @@ feature 'Group show page' do visit path end - it_behaves_like "an autodiscoverable RSS feed without an RSS token" + it_behaves_like "an autodiscoverable RSS feed without a feed token" end context 'when group has a public project', :js do diff --git a/spec/features/ics/dashboard_issues_spec.rb b/spec/features/ics/dashboard_issues_spec.rb new file mode 100644 index 00000000000..5d6cd44ad1c --- /dev/null +++ b/spec/features/ics/dashboard_issues_spec.rb @@ -0,0 +1,65 @@ +require 'spec_helper' + +describe 'Dashboard Issues Calendar Feed' do + describe 'GET /issues' do + let!(:user) { create(:user, email: 'private1@example.com', public_email: 'public1@example.com') } + let!(:assignee) { create(:user, email: 'private2@example.com', public_email: 'public2@example.com') } + let!(:project) { create(:project) } + + before do + project.add_master(user) + end + + context 'when authenticated' do + it 'renders calendar feed' do + sign_in user + visit issues_dashboard_path(:ics) + + expect(response_headers['Content-Type']).to have_content('text/calendar') + expect(response_headers['Content-Disposition']).to have_content('inline') + expect(body).to have_text('BEGIN:VCALENDAR') + end + end + + context 'when authenticated via personal access token' do + it 'renders calendar feed' do + personal_access_token = create(:personal_access_token, user: user) + + visit issues_dashboard_path(:ics, private_token: personal_access_token.token) + + expect(response_headers['Content-Type']).to have_content('text/calendar') + expect(response_headers['Content-Disposition']).to have_content('inline') + expect(body).to have_text('BEGIN:VCALENDAR') + end + end + + context 'when authenticated via feed token' do + it 'renders calendar feed' do + visit issues_dashboard_path(:ics, feed_token: user.feed_token) + + expect(response_headers['Content-Type']).to have_content('text/calendar') + expect(response_headers['Content-Disposition']).to have_content('inline') + expect(body).to have_text('BEGIN:VCALENDAR') + end + end + + context 'issue with due date' do + let!(:issue) do + create(:issue, author: user, assignees: [assignee], project: project, title: 'test title', + description: 'test desc', due_date: Date.tomorrow) + end + + it 'renders issue fields' do + visit issues_dashboard_path(:ics, feed_token: user.feed_token) + + expect(body).to have_text("SUMMARY:test title (in #{project.full_path})") + # line length for ics is 75 chars + expected_description = "DESCRIPTION:Find out more at #{issue_url(issue)}".insert(75, "\r\n") + expect(body).to have_text(expected_description) + expect(body).to have_text("DTSTART;VALUE=DATE:#{Date.tomorrow.strftime('%Y%m%d')}") + expect(body).to have_text("URL:#{issue_url(issue)}") + expect(body).to have_text('TRANSP:TRANSPARENT') + end + end + end +end diff --git a/spec/features/ics/group_issues_spec.rb b/spec/features/ics/group_issues_spec.rb new file mode 100644 index 00000000000..0a049be2ffe --- /dev/null +++ b/spec/features/ics/group_issues_spec.rb @@ -0,0 +1,67 @@ +require 'spec_helper' + +describe 'Group Issues Calendar Feed' do + describe 'GET /issues' do + let!(:user) { create(:user, email: 'private1@example.com', public_email: 'public1@example.com') } + let!(:assignee) { create(:user, email: 'private2@example.com', public_email: 'public2@example.com') } + let!(:group) { create(:group) } + let!(:project) { create(:project, group: group) } + + before do + project.add_developer(user) + group.add_developer(user) + end + + context 'when authenticated' do + it 'renders calendar feed' do + sign_in user + visit issues_group_path(group, :ics) + + expect(response_headers['Content-Type']).to have_content('text/calendar') + expect(response_headers['Content-Disposition']).to have_content('inline') + expect(body).to have_text('BEGIN:VCALENDAR') + end + end + + context 'when authenticated via personal access token' do + it 'renders calendar feed' do + personal_access_token = create(:personal_access_token, user: user) + + visit issues_group_path(group, :ics, private_token: personal_access_token.token) + + expect(response_headers['Content-Type']).to have_content('text/calendar') + expect(response_headers['Content-Disposition']).to have_content('inline') + expect(body).to have_text('BEGIN:VCALENDAR') + end + end + + context 'when authenticated via feed token' do + it 'renders calendar feed' do + visit issues_group_path(group, :ics, feed_token: user.feed_token) + + expect(response_headers['Content-Type']).to have_content('text/calendar') + expect(response_headers['Content-Disposition']).to have_content('inline') + expect(body).to have_text('BEGIN:VCALENDAR') + end + end + + context 'issue with due date' do + let!(:issue) do + create(:issue, author: user, assignees: [assignee], project: project, title: 'test title', + description: 'test desc', due_date: Date.tomorrow) + end + + it 'renders issue fields' do + visit issues_group_path(group, :ics, feed_token: user.feed_token) + + expect(body).to have_text("SUMMARY:test title (in #{project.full_path})") + # line length for ics is 75 chars + expected_description = "DESCRIPTION:Find out more at #{issue_url(issue)}".insert(75, "\r\n") + expect(body).to have_text(expected_description) + expect(body).to have_text("DTSTART;VALUE=DATE:#{Date.tomorrow.strftime('%Y%m%d')}") + expect(body).to have_text("URL:#{issue_url(issue)}") + expect(body).to have_text('TRANSP:TRANSPARENT') + end + end + end +end diff --git a/spec/features/ics/project_issues_spec.rb b/spec/features/ics/project_issues_spec.rb new file mode 100644 index 00000000000..b99e9607f1d --- /dev/null +++ b/spec/features/ics/project_issues_spec.rb @@ -0,0 +1,66 @@ +require 'spec_helper' + +describe 'Project Issues Calendar Feed' do + describe 'GET /issues' do + let!(:user) { create(:user, email: 'private1@example.com', public_email: 'public1@example.com') } + let!(:assignee) { create(:user, email: 'private2@example.com', public_email: 'public2@example.com') } + let!(:project) { create(:project) } + let!(:issue) { create(:issue, author: user, assignees: [assignee], project: project) } + + before do + project.add_developer(user) + end + + context 'when authenticated' do + it 'renders calendar feed' do + sign_in user + visit project_issues_path(project, :ics) + + expect(response_headers['Content-Type']).to have_content('text/calendar') + expect(response_headers['Content-Disposition']).to have_content('inline') + expect(body).to have_text('BEGIN:VCALENDAR') + end + end + + context 'when authenticated via personal access token' do + it 'renders calendar feed' do + personal_access_token = create(:personal_access_token, user: user) + + visit project_issues_path(project, :ics, private_token: personal_access_token.token) + + expect(response_headers['Content-Type']).to have_content('text/calendar') + expect(response_headers['Content-Disposition']).to have_content('inline') + expect(body).to have_text('BEGIN:VCALENDAR') + end + end + + context 'when authenticated via feed token' do + it 'renders calendar feed' do + visit project_issues_path(project, :ics, feed_token: user.feed_token) + + expect(response_headers['Content-Type']).to have_content('text/calendar') + expect(response_headers['Content-Disposition']).to have_content('inline') + expect(body).to have_text('BEGIN:VCALENDAR') + end + end + + context 'issue with due date' do + let!(:issue) do + create(:issue, author: user, assignees: [assignee], project: project, title: 'test title', + description: 'test desc', due_date: Date.tomorrow) + end + + it 'renders issue fields' do + visit project_issues_path(project, :ics, feed_token: user.feed_token) + + expect(body).to have_text("SUMMARY:test title (in #{project.full_path})") + # line length for ics is 75 chars + expected_description = "DESCRIPTION:Find out more at #{issue_url(issue)}".insert(75, "\r\n") + expect(body).to have_text(expected_description) + expect(body).to have_text("DTSTART;VALUE=DATE:#{Date.tomorrow.strftime('%Y%m%d')}") + expect(body).to have_text("URL:#{issue_url(issue)}") + expect(body).to have_text('TRANSP:TRANSPARENT') + end + end + end +end diff --git a/spec/features/issues/filtered_search/filter_issues_spec.rb b/spec/features/issues/filtered_search/filter_issues_spec.rb index 483122ae463..bc42618306f 100644 --- a/spec/features/issues/filtered_search/filter_issues_spec.rb +++ b/spec/features/issues/filtered_search/filter_issues_spec.rb @@ -468,13 +468,13 @@ describe 'Filter issues', :js do it "for #{type}" do visit path - link = find_link('Subscribe') + link = find_link('Subscribe to RSS feed') params = CGI.parse(URI.parse(link[:href]).query) auto_discovery_link = find('link[type="application/atom+xml"]', visible: false) auto_discovery_params = CGI.parse(URI.parse(auto_discovery_link[:href]).query) expected = { - 'rss_token' => [user.rss_token], + 'feed_token' => [user.feed_token], 'milestone_title' => [milestone.title], 'assignee_id' => [user.id.to_s] } diff --git a/spec/features/issues_spec.rb b/spec/features/issues_spec.rb index 314bd19f586..c6dcd97631d 100644 --- a/spec/features/issues_spec.rb +++ b/spec/features/issues_spec.rb @@ -340,6 +340,20 @@ describe 'Issues' do expect(page).to have_content('baz') end end + + it 'filters by due next month and previous two weeks' do + foo.update(due_date: Date.today - 4.weeks) + bar.update(due_date: (Date.today + 2.months).beginning_of_month) + baz.update(due_date: Date.yesterday) + + visit project_issues_path(project, due_date: Issue::DueNextMonthAndPreviousTwoWeeks.name) + + page.within '.issues-holder' do + expect(page).not_to have_content('foo') + expect(page).not_to have_content('bar') + expect(page).to have_content('baz') + end + end end describe 'sorting by milestone' do @@ -591,6 +605,20 @@ describe 'Issues' do end end + it 'clears local storage after creating a new issue', :js do + 2.times do + visit new_project_issue_path(project) + wait_for_requests + + expect(page).to have_field('Title', with: '') + + fill_in 'issue_title', with: 'bug 345' + fill_in 'issue_description', with: 'bug description' + + click_button 'Submit issue' + end + end + context 'dropzone upload file', :js do before do visit new_project_issue_path(project) diff --git a/spec/features/profile_spec.rb b/spec/features/profile_spec.rb index 15dcb30cbdd..2e0753c3bfb 100644 --- a/spec/features/profile_spec.rb +++ b/spec/features/profile_spec.rb @@ -56,21 +56,21 @@ describe 'Profile account page', :js do end end - describe 'when I reset RSS token' do + describe 'when I reset feed token' do before do visit profile_personal_access_tokens_path end - it 'resets RSS token' do - within('.rss-token-reset') do - previous_token = find("#rss_token").value + it 'resets feed token' do + within('.feed-token-reset') do + previous_token = find("#feed_token").value accept_confirm { click_link('reset it') } - expect(find('#rss_token').value).not_to eq(previous_token) + expect(find('#feed_token').value).not_to eq(previous_token) end - expect(page).to have_content 'RSS token was successfully reset' + expect(page).to have_content 'Feed token was successfully reset' end end diff --git a/spec/features/projects/activity/rss_spec.rb b/spec/features/projects/activity/rss_spec.rb index cd1cfe07998..4ac34adde0e 100644 --- a/spec/features/projects/activity/rss_spec.rb +++ b/spec/features/projects/activity/rss_spec.rb @@ -15,7 +15,7 @@ feature 'Project Activity RSS' do visit path end - it_behaves_like "it has an RSS button with current_user's RSS token" + it_behaves_like "it has an RSS button with current_user's feed token" end context 'when signed out' do @@ -23,6 +23,6 @@ feature 'Project Activity RSS' do visit path end - it_behaves_like "it has an RSS button without an RSS token" + it_behaves_like "it has an RSS button without a feed token" end end diff --git a/spec/features/projects/commits/rss_spec.rb b/spec/features/projects/commits/rss_spec.rb index 0d9c7355ddd..0bc207da970 100644 --- a/spec/features/projects/commits/rss_spec.rb +++ b/spec/features/projects/commits/rss_spec.rb @@ -12,8 +12,8 @@ feature 'Project Commits RSS' do visit path end - it_behaves_like "it has an RSS button with current_user's RSS token" - it_behaves_like "an autodiscoverable RSS feed with current_user's RSS token" + it_behaves_like "it has an RSS button with current_user's feed token" + it_behaves_like "an autodiscoverable RSS feed with current_user's feed token" end context 'when signed out' do @@ -21,7 +21,7 @@ feature 'Project Commits RSS' do visit path end - it_behaves_like "it has an RSS button without an RSS token" - it_behaves_like "an autodiscoverable RSS feed without an RSS token" + it_behaves_like "it has an RSS button without a feed token" + it_behaves_like "an autodiscoverable RSS feed without a feed token" end end diff --git a/spec/features/projects/issues/rss_spec.rb b/spec/features/projects/issues/rss_spec.rb index ff91aabc311..8b1f7d432ee 100644 --- a/spec/features/projects/issues/rss_spec.rb +++ b/spec/features/projects/issues/rss_spec.rb @@ -17,8 +17,8 @@ feature 'Project Issues RSS' do visit path end - it_behaves_like "it has an RSS button with current_user's RSS token" - it_behaves_like "an autodiscoverable RSS feed with current_user's RSS token" + it_behaves_like "it has an RSS button with current_user's feed token" + it_behaves_like "an autodiscoverable RSS feed with current_user's feed token" end context 'when signed out' do @@ -26,7 +26,7 @@ feature 'Project Issues RSS' do visit path end - it_behaves_like "it has an RSS button without an RSS token" - it_behaves_like "an autodiscoverable RSS feed without an RSS token" + it_behaves_like "it has an RSS button without a feed token" + it_behaves_like "an autodiscoverable RSS feed without a feed token" end end diff --git a/spec/features/projects/milestones/milestone_spec.rb b/spec/features/projects/milestones/milestone_spec.rb index 30de3e83fbb..20a52d6011f 100644 --- a/spec/features/projects/milestones/milestone_spec.rb +++ b/spec/features/projects/milestones/milestone_spec.rb @@ -17,8 +17,8 @@ feature 'Project milestone' do it 'shows issues tab' do within('#content-body') do expect(page).to have_link 'Issues', href: '#tab-issues' - expect(page).to have_selector '.nav-links li.active', count: 1 - expect(find('.nav-links li.active')).to have_content 'Issues' + expect(page).to have_selector '.nav-links li a.active', count: 1 + expect(find('.nav-links li a.active')).to have_content 'Issues' end end @@ -44,8 +44,8 @@ feature 'Project milestone' do it 'hides issues tab' do within('#content-body') do expect(page).not_to have_link 'Issues', href: '#tab-issues' - expect(page).to have_selector '.nav-links li.active', count: 1 - expect(find('.nav-links li.active')).to have_content 'Merge Requests' + expect(page).to have_selector '.nav-links li a.active', count: 1 + expect(find('.nav-links li a.active')).to have_content 'Merge Requests' end end diff --git a/spec/features/projects/pipelines/pipeline_spec.rb b/spec/features/projects/pipelines/pipeline_spec.rb index af2a9567a47..35776a5f23b 100644 --- a/spec/features/projects/pipelines/pipeline_spec.rb +++ b/spec/features/projects/pipelines/pipeline_spec.rb @@ -379,7 +379,7 @@ describe 'Pipeline', :js do end it 'fails to access the page' do - expect(page).to have_content('Access Denied') + expect(page).to have_title('Access Denied') end end end diff --git a/spec/features/projects/services/user_activates_mattermost_slash_command_spec.rb b/spec/features/projects/services/user_activates_mattermost_slash_command_spec.rb index b2906e315f7..fce41ce347f 100644 --- a/spec/features/projects/services/user_activates_mattermost_slash_command_spec.rb +++ b/spec/features/projects/services/user_activates_mattermost_slash_command_spec.rb @@ -64,7 +64,7 @@ feature 'Setup Mattermost slash commands', :js do click_link 'Add to Mattermost' expect(page).to have_content('The team where the slash commands will be used in') - expect(page).to have_content('This is the only available team.') + expect(page).to have_content('This is the only available team that you are a member of.') end it 'shows a disabled prefilled select if user is a member of 1 team' do @@ -94,7 +94,7 @@ feature 'Setup Mattermost slash commands', :js do click_link 'Add to Mattermost' expect(page).to have_content('Select the team where the slash commands will be used in') - expect(page).to have_content('The list shows all available teams.') + expect(page).to have_content('The list shows all available teams that you are a member of.') end it 'shows a select with team options user is a member of multiple teams' do diff --git a/spec/features/projects/show/rss_spec.rb b/spec/features/projects/show/rss_spec.rb index d02eaf34533..52164d30c40 100644 --- a/spec/features/projects/show/rss_spec.rb +++ b/spec/features/projects/show/rss_spec.rb @@ -12,7 +12,7 @@ feature 'Projects > Show > RSS' do visit path end - it_behaves_like "an autodiscoverable RSS feed with current_user's RSS token" + it_behaves_like "an autodiscoverable RSS feed with current_user's feed token" end context 'when signed out' do @@ -20,6 +20,6 @@ feature 'Projects > Show > RSS' do visit path end - it_behaves_like "an autodiscoverable RSS feed without an RSS token" + it_behaves_like "an autodiscoverable RSS feed without a feed token" end end diff --git a/spec/features/projects/tree/rss_spec.rb b/spec/features/projects/tree/rss_spec.rb index 6407370ac0d..f52b3cc1d86 100644 --- a/spec/features/projects/tree/rss_spec.rb +++ b/spec/features/projects/tree/rss_spec.rb @@ -12,7 +12,7 @@ feature 'Project Tree RSS' do visit path end - it_behaves_like "an autodiscoverable RSS feed with current_user's RSS token" + it_behaves_like "an autodiscoverable RSS feed with current_user's feed token" end context 'when signed out' do @@ -20,6 +20,6 @@ feature 'Project Tree RSS' do visit path end - it_behaves_like "an autodiscoverable RSS feed without an RSS token" + it_behaves_like "an autodiscoverable RSS feed without a feed token" end end diff --git a/spec/features/users/rss_spec.rb b/spec/features/users/rss_spec.rb index 7c5abe54d56..c3734b5c808 100644 --- a/spec/features/users/rss_spec.rb +++ b/spec/features/users/rss_spec.rb @@ -10,7 +10,7 @@ feature 'User RSS' do visit path end - it_behaves_like "it has an RSS button with current_user's RSS token" + it_behaves_like "it has an RSS button with current_user's feed token" end context 'when signed out' do @@ -18,6 +18,6 @@ feature 'User RSS' do visit path end - it_behaves_like "it has an RSS button without an RSS token" + it_behaves_like "it has an RSS button without a feed token" end end diff --git a/spec/features/users/terms_spec.rb b/spec/features/users/terms_spec.rb index f9469adbfe3..1efa5cd5490 100644 --- a/spec/features/users/terms_spec.rb +++ b/spec/features/users/terms_spec.rb @@ -62,7 +62,8 @@ describe 'Users > Terms' do expect(current_path).to eq(project_issues_path(project)) end - it 'redirects back to the page the user was trying to save' do + # Disabled until https://gitlab.com/gitlab-org/gitlab-ce/issues/37162 is solved properly + xit 'redirects back to the page the user was trying to save' do visit new_project_issue_path(project) fill_in :issue_title, with: 'Hello world, a new issue' diff --git a/spec/fixtures/api/schemas/cluster_status.json b/spec/fixtures/api/schemas/cluster_status.json index d27c12e43f2..ccef17a6615 100644 --- a/spec/fixtures/api/schemas/cluster_status.json +++ b/spec/fixtures/api/schemas/cluster_status.json @@ -31,7 +31,8 @@ } }, "status_reason": { "type": ["string", "null"] }, - "external_ip": { "type": ["string", "null"] } + "external_ip": { "type": ["string", "null"] }, + "hostname": { "type": ["string", "null"] } }, "required" : [ "name", "status" ] } diff --git a/spec/fixtures/api/schemas/public_api/v3/issues.json b/spec/fixtures/api/schemas/public_api/v3/issues.json deleted file mode 100644 index 51b0822bc66..00000000000 --- a/spec/fixtures/api/schemas/public_api/v3/issues.json +++ /dev/null @@ -1,78 +0,0 @@ -{ - "type": "array", - "items": { - "type": "object", - "properties" : { - "id": { "type": "integer" }, - "iid": { "type": "integer" }, - "project_id": { "type": "integer" }, - "title": { "type": "string" }, - "description": { "type": ["string", "null"] }, - "state": { "type": "string" }, - "created_at": { "type": "date" }, - "updated_at": { "type": "date" }, - "labels": { - "type": "array", - "items": { - "type": "string" - } - }, - "milestone": { - "type": "object", - "properties": { - "id": { "type": "integer" }, - "iid": { "type": "integer" }, - "project_id": { "type": ["integer", "null"] }, - "group_id": { "type": ["integer", "null"] }, - "title": { "type": "string" }, - "description": { "type": ["string", "null"] }, - "state": { "type": "string" }, - "created_at": { "type": "date" }, - "updated_at": { "type": "date" }, - "due_date": { "type": "date" }, - "start_date": { "type": "date" } - }, - "additionalProperties": false - }, - "assignee": { - "type": ["object", "null"], - "properties": { - "name": { "type": "string" }, - "username": { "type": "string" }, - "id": { "type": "integer" }, - "state": { "type": "string" }, - "avatar_url": { "type": "uri" }, - "web_url": { "type": "uri" } - }, - "additionalProperties": false - }, - "author": { - "type": "object", - "properties": { - "name": { "type": "string" }, - "username": { "type": "string" }, - "id": { "type": "integer" }, - "state": { "type": "string" }, - "avatar_url": { "type": "uri" }, - "web_url": { "type": "uri" } - }, - "additionalProperties": false - }, - "user_notes_count": { "type": "integer" }, - "upvotes": { "type": "integer" }, - "downvotes": { "type": "integer" }, - "due_date": { "type": ["date", "null"] }, - "confidential": { "type": "boolean" }, - "web_url": { "type": "uri" }, - "subscribed": { "type": ["boolean"] } - }, - "required": [ - "id", "iid", "project_id", "title", "description", - "state", "created_at", "updated_at", "labels", - "milestone", "assignee", "author", "user_notes_count", - "upvotes", "downvotes", "due_date", "confidential", - "web_url", "subscribed" - ], - "additionalProperties": false - } -} diff --git a/spec/fixtures/api/schemas/public_api/v3/merge_requests.json b/spec/fixtures/api/schemas/public_api/v3/merge_requests.json deleted file mode 100644 index 9af7a8c9f4f..00000000000 --- a/spec/fixtures/api/schemas/public_api/v3/merge_requests.json +++ /dev/null @@ -1,91 +0,0 @@ -{ - "type": "array", - "items": { - "type": "object", - "properties" : { - "id": { "type": "integer" }, - "iid": { "type": "integer" }, - "project_id": { "type": "integer" }, - "title": { "type": "string" }, - "description": { "type": ["string", "null"] }, - "state": { "type": "string" }, - "created_at": { "type": "date" }, - "updated_at": { "type": "date" }, - "target_branch": { "type": "string" }, - "source_branch": { "type": "string" }, - "upvotes": { "type": "integer" }, - "downvotes": { "type": "integer" }, - "author": { - "type": "object", - "properties": { - "name": { "type": "string" }, - "username": { "type": "string" }, - "id": { "type": "integer" }, - "state": { "type": "string" }, - "avatar_url": { "type": "uri" }, - "web_url": { "type": "uri" } - }, - "additionalProperties": false - }, - "assignee": { - "type": "object", - "properties": { - "name": { "type": "string" }, - "username": { "type": "string" }, - "id": { "type": "integer" }, - "state": { "type": "string" }, - "avatar_url": { "type": "uri" }, - "web_url": { "type": "uri" } - }, - "additionalProperties": false - }, - "source_project_id": { "type": "integer" }, - "target_project_id": { "type": "integer" }, - "labels": { - "type": "array", - "items": { - "type": "string" - } - }, - "work_in_progress": { "type": "boolean" }, - "milestone": { - "type": ["object", "null"], - "properties": { - "id": { "type": "integer" }, - "iid": { "type": "integer" }, - "project_id": { "type": ["integer", "null"] }, - "group_id": { "type": ["integer", "null"] }, - "title": { "type": "string" }, - "description": { "type": ["string", "null"] }, - "state": { "type": "string" }, - "created_at": { "type": "date" }, - "updated_at": { "type": "date" }, - "due_date": { "type": "date" }, - "start_date": { "type": "date" } - }, - "additionalProperties": false - }, - "merge_when_build_succeeds": { "type": "boolean" }, - "merge_status": { "type": "string" }, - "sha": { "type": "string" }, - "merge_commit_sha": { "type": ["string", "null"] }, - "user_notes_count": { "type": "integer" }, - "should_remove_source_branch": { "type": ["boolean", "null"] }, - "force_remove_source_branch": { "type": ["boolean", "null"] }, - "web_url": { "type": "uri" }, - "subscribed": { "type": ["boolean"] }, - "squash": { "type": "boolean" } - }, - "required": [ - "id", "iid", "project_id", "title", "description", - "state", "created_at", "updated_at", "target_branch", - "source_branch", "upvotes", "downvotes", "author", - "assignee", "source_project_id", "target_project_id", - "labels", "work_in_progress", "milestone", "merge_when_build_succeeds", - "merge_status", "sha", "merge_commit_sha", "user_notes_count", - "should_remove_source_branch", "force_remove_source_branch", - "web_url", "subscribed", "squash" - ], - "additionalProperties": false - } -} diff --git a/spec/helpers/calendar_helper_spec.rb b/spec/helpers/calendar_helper_spec.rb new file mode 100644 index 00000000000..828a9d9fea0 --- /dev/null +++ b/spec/helpers/calendar_helper_spec.rb @@ -0,0 +1,20 @@ +require 'spec_helper' + +describe CalendarHelper do + describe '#calendar_url_options' do + context 'when signed in' do + it "includes the current_user's feed_token" do + current_user = create(:user) + allow(helper).to receive(:current_user).and_return(current_user) + expect(helper.calendar_url_options).to include feed_token: current_user.feed_token + end + end + + context 'when signed out' do + it "does not have a feed_token" do + allow(helper).to receive(:current_user).and_return(nil) + expect(helper.calendar_url_options[:feed_token]).to be_nil + end + end + end +end diff --git a/spec/helpers/rss_helper_spec.rb b/spec/helpers/rss_helper_spec.rb index 269e1057e8d..a7f9bdf07e4 100644 --- a/spec/helpers/rss_helper_spec.rb +++ b/spec/helpers/rss_helper_spec.rb @@ -3,17 +3,17 @@ require 'spec_helper' describe RssHelper do describe '#rss_url_options' do context 'when signed in' do - it "includes the current_user's rss_token" do + it "includes the current_user's feed_token" do current_user = create(:user) allow(helper).to receive(:current_user).and_return(current_user) - expect(helper.rss_url_options).to include rss_token: current_user.rss_token + expect(helper.rss_url_options).to include feed_token: current_user.feed_token end end context 'when signed out' do - it "does not have an rss_token" do + it "does not have a feed_token" do allow(helper).to receive(:current_user).and_return(nil) - expect(helper.rss_url_options[:rss_token]).to be_nil + expect(helper.rss_url_options[:feed_token]).to be_nil end end end diff --git a/spec/initializers/grape_route_helpers_fix_spec.rb b/spec/initializers/grape_route_helpers_fix_spec.rb deleted file mode 100644 index 2cf5924128f..00000000000 --- a/spec/initializers/grape_route_helpers_fix_spec.rb +++ /dev/null @@ -1,14 +0,0 @@ -require 'spec_helper' -require_relative '../../config/initializers/grape_route_helpers_fix' - -describe 'route shadowing' do - include GrapeRouteHelpers::NamedRouteMatcher - - it 'does not occur' do - path = api_v4_projects_merge_requests_path(id: 1) - expect(path).to eq('/api/v4/projects/1/merge_requests') - - path = api_v4_projects_merge_requests_path(id: 1, merge_request_iid: 3) - expect(path).to eq('/api/v4/projects/1/merge_requests/3') - end -end diff --git a/spec/javascripts/.eslintrc b/spec/javascripts/.eslintrc deleted file mode 100644 index 9eb0e732572..00000000000 --- a/spec/javascripts/.eslintrc +++ /dev/null @@ -1,33 +0,0 @@ -{ - "env": { - "jasmine": true - }, - "extends": "plugin:jasmine/recommended", - "globals": { - "appendLoadFixtures": false, - "appendLoadStyleFixtures": false, - "appendSetFixtures": false, - "appendSetStyleFixtures": false, - "getJSONFixture": false, - "loadFixtures": false, - "loadJSONFixtures": false, - "loadStyleFixtures": false, - "preloadFixtures": false, - "preloadStyleFixtures": false, - "readFixtures": false, - "sandbox": false, - "setFixtures": false, - "setStyleFixtures": false, - "spyOnDependency": false, - "spyOnEvent": false, - "ClassSpecHelper": false - }, - "plugins": ["jasmine"], - "rules": { - "func-names": 0, - "jasmine/no-suite-dupes": [1, "branch"], - "jasmine/no-spec-dupes": [1, "branch"], - "no-console": 0, - "prefer-arrow-callback": 0 - } -} diff --git a/spec/javascripts/.eslintrc.yml b/spec/javascripts/.eslintrc.yml new file mode 100644 index 00000000000..8bceb2c50fc --- /dev/null +++ b/spec/javascripts/.eslintrc.yml @@ -0,0 +1,34 @@ +--- +env: + jasmine: true +extends: plugin:jasmine/recommended +globals: + appendLoadFixtures: false + appendLoadStyleFixtures: false + appendSetFixtures: false + appendSetStyleFixtures: false + getJSONFixture: false + loadFixtures: false + loadJSONFixtures: false + loadStyleFixtures: false + preloadFixtures: false + preloadStyleFixtures: false + readFixtures: false + sandbox: false + setFixtures: false + setStyleFixtures: false + spyOnDependency: false + spyOnEvent: false + ClassSpecHelper: false +plugins: + - jasmine +rules: + func-names: off + jasmine/no-suite-dupes: + - warn + - branch + jasmine/no-spec-dupes: + - warn + - branch + no-console: off + prefer-arrow-callback: off diff --git a/spec/javascripts/blob/notebook/index_spec.js b/spec/javascripts/blob/notebook/index_spec.js index a143fc827d5..80c09a544d6 100644 --- a/spec/javascripts/blob/notebook/index_spec.js +++ b/spec/javascripts/blob/notebook/index_spec.js @@ -84,9 +84,14 @@ describe('iPython notebook renderer', () => { describe('error in JSON response', () => { let mock; - beforeEach((done) => { + beforeEach(done => { mock = new MockAdapter(axios); - mock.onGet('/test').reply(() => Promise.reject({ status: 200, data: '{ "cells": [{"cell_type": "markdown"} }' })); + mock + .onGet('/test') + .reply(() => + // eslint-disable-next-line prefer-promise-reject-errors + Promise.reject({ status: 200, data: '{ "cells": [{"cell_type": "markdown"} }' }), + ); renderNotebook(); diff --git a/spec/javascripts/boards/board_blank_state_spec.js b/spec/javascripts/boards/board_blank_state_spec.js index 664ea202e93..89a4fae4b59 100644 --- a/spec/javascripts/boards/board_blank_state_spec.js +++ b/spec/javascripts/boards/board_blank_state_spec.js @@ -1,4 +1,3 @@ -/* global BoardService */ import Vue from 'vue'; import '~/boards/stores/boards_store'; import BoardBlankState from '~/boards/components/board_blank_state.vue'; diff --git a/spec/javascripts/boards/board_card_spec.js b/spec/javascripts/boards/board_card_spec.js index 13d607a06d2..9b4db774b63 100644 --- a/spec/javascripts/boards/board_card_spec.js +++ b/spec/javascripts/boards/board_card_spec.js @@ -1,7 +1,6 @@ /* global List */ /* global ListAssignee */ /* global ListLabel */ -/* global BoardService */ import Vue from 'vue'; import MockAdapter from 'axios-mock-adapter'; diff --git a/spec/javascripts/boards/board_list_spec.js b/spec/javascripts/boards/board_list_spec.js index c06b2f60813..de261d36c61 100644 --- a/spec/javascripts/boards/board_list_spec.js +++ b/spec/javascripts/boards/board_list_spec.js @@ -1,10 +1,9 @@ -/* global BoardService */ /* global List */ /* global ListIssue */ import Vue from 'vue'; import MockAdapter from 'axios-mock-adapter'; import axios from '~/lib/utils/axios_utils'; -import Sortable from 'vendor/Sortable'; +import Sortable from 'sortablejs'; import BoardList from '~/boards/components/board_list.vue'; import eventHub from '~/boards/eventhub'; import '~/boards/mixins/sortable_default_options'; diff --git a/spec/javascripts/boards/board_new_issue_spec.js b/spec/javascripts/boards/board_new_issue_spec.js index d5fbfdeaa91..ee37821ad08 100644 --- a/spec/javascripts/boards/board_new_issue_spec.js +++ b/spec/javascripts/boards/board_new_issue_spec.js @@ -1,4 +1,3 @@ -/* global BoardService */ /* global List */ import Vue from 'vue'; diff --git a/spec/javascripts/boards/boards_store_spec.js b/spec/javascripts/boards/boards_store_spec.js index 0cf9e4c9ba1..46fa10e1789 100644 --- a/spec/javascripts/boards/boards_store_spec.js +++ b/spec/javascripts/boards/boards_store_spec.js @@ -1,5 +1,4 @@ /* eslint-disable comma-dangle, one-var, no-unused-vars */ -/* global BoardService */ /* global ListIssue */ import Vue from 'vue'; diff --git a/spec/javascripts/boards/issue_spec.js b/spec/javascripts/boards/issue_spec.js index 4a11131b55c..d90f9a41231 100644 --- a/spec/javascripts/boards/issue_spec.js +++ b/spec/javascripts/boards/issue_spec.js @@ -1,5 +1,4 @@ /* eslint-disable comma-dangle */ -/* global BoardService */ /* global ListIssue */ import Vue from 'vue'; diff --git a/spec/javascripts/boards/list_spec.js b/spec/javascripts/boards/list_spec.js index d9a1d692949..d5d1139de15 100644 --- a/spec/javascripts/boards/list_spec.js +++ b/spec/javascripts/boards/list_spec.js @@ -1,5 +1,4 @@ /* eslint-disable comma-dangle */ -/* global BoardService */ /* global List */ /* global ListIssue */ diff --git a/spec/javascripts/clusters/clusters_bundle_spec.js b/spec/javascripts/clusters/clusters_bundle_spec.js index a5cd247b689..abe2954d506 100644 --- a/spec/javascripts/clusters/clusters_bundle_spec.js +++ b/spec/javascripts/clusters/clusters_bundle_spec.js @@ -207,11 +207,11 @@ describe('Clusters', () => { spyOn(cluster.service, 'installApplication').and.returnValue(Promise.resolve()); expect(cluster.store.state.applications.helm.requestStatus).toEqual(null); - cluster.installApplication('helm'); + cluster.installApplication({ id: 'helm' }); expect(cluster.store.state.applications.helm.requestStatus).toEqual(REQUEST_LOADING); expect(cluster.store.state.applications.helm.requestReason).toEqual(null); - expect(cluster.service.installApplication).toHaveBeenCalledWith('helm'); + expect(cluster.service.installApplication).toHaveBeenCalledWith('helm', undefined); getSetTimeoutPromise() .then(() => { @@ -226,11 +226,11 @@ describe('Clusters', () => { spyOn(cluster.service, 'installApplication').and.returnValue(Promise.resolve()); expect(cluster.store.state.applications.ingress.requestStatus).toEqual(null); - cluster.installApplication('ingress'); + cluster.installApplication({ id: 'ingress' }); expect(cluster.store.state.applications.ingress.requestStatus).toEqual(REQUEST_LOADING); expect(cluster.store.state.applications.ingress.requestReason).toEqual(null); - expect(cluster.service.installApplication).toHaveBeenCalledWith('ingress'); + expect(cluster.service.installApplication).toHaveBeenCalledWith('ingress', undefined); getSetTimeoutPromise() .then(() => { @@ -245,11 +245,11 @@ describe('Clusters', () => { spyOn(cluster.service, 'installApplication').and.returnValue(Promise.resolve()); expect(cluster.store.state.applications.runner.requestStatus).toEqual(null); - cluster.installApplication('runner'); + cluster.installApplication({ id: 'runner' }); expect(cluster.store.state.applications.runner.requestStatus).toEqual(REQUEST_LOADING); expect(cluster.store.state.applications.runner.requestReason).toEqual(null); - expect(cluster.service.installApplication).toHaveBeenCalledWith('runner'); + expect(cluster.service.installApplication).toHaveBeenCalledWith('runner', undefined); getSetTimeoutPromise() .then(() => { @@ -260,11 +260,29 @@ describe('Clusters', () => { .catch(done.fail); }); + it('tries to install jupyter', (done) => { + spyOn(cluster.service, 'installApplication').and.returnValue(Promise.resolve()); + expect(cluster.store.state.applications.jupyter.requestStatus).toEqual(null); + cluster.installApplication({ id: 'jupyter', params: { hostname: cluster.store.state.applications.jupyter.hostname } }); + + expect(cluster.store.state.applications.jupyter.requestStatus).toEqual(REQUEST_LOADING); + expect(cluster.store.state.applications.jupyter.requestReason).toEqual(null); + expect(cluster.service.installApplication).toHaveBeenCalledWith('jupyter', { hostname: cluster.store.state.applications.jupyter.hostname }); + + getSetTimeoutPromise() + .then(() => { + expect(cluster.store.state.applications.jupyter.requestStatus).toEqual(REQUEST_SUCCESS); + expect(cluster.store.state.applications.jupyter.requestReason).toEqual(null); + }) + .then(done) + .catch(done.fail); + }); + it('sets error request status when the request fails', (done) => { spyOn(cluster.service, 'installApplication').and.returnValue(Promise.reject(new Error('STUBBED ERROR'))); expect(cluster.store.state.applications.helm.requestStatus).toEqual(null); - cluster.installApplication('helm'); + cluster.installApplication({ id: 'helm' }); expect(cluster.store.state.applications.helm.requestStatus).toEqual(REQUEST_LOADING); expect(cluster.store.state.applications.helm.requestReason).toEqual(null); diff --git a/spec/javascripts/clusters/components/application_row_spec.js b/spec/javascripts/clusters/components/application_row_spec.js index 2c4707bb856..c83cbe90a57 100644 --- a/spec/javascripts/clusters/components/application_row_spec.js +++ b/spec/javascripts/clusters/components/application_row_spec.js @@ -174,7 +174,27 @@ describe('Application Row', () => { installButton.click(); - expect(eventHub.$emit).toHaveBeenCalledWith('installApplication', DEFAULT_APPLICATION_STATE.id); + expect(eventHub.$emit).toHaveBeenCalledWith('installApplication', { + id: DEFAULT_APPLICATION_STATE.id, + params: {}, + }); + }); + + it('clicking install button when installApplicationRequestParams are provided emits event', () => { + spyOn(eventHub, '$emit'); + vm = mountComponent(ApplicationRow, { + ...DEFAULT_APPLICATION_STATE, + status: APPLICATION_INSTALLABLE, + installApplicationRequestParams: { hostname: 'jupyter' }, + }); + const installButton = vm.$el.querySelector('.js-cluster-application-install-button'); + + installButton.click(); + + expect(eventHub.$emit).toHaveBeenCalledWith('installApplication', { + id: DEFAULT_APPLICATION_STATE.id, + params: { hostname: 'jupyter' }, + }); }); it('clicking disabled install button emits nothing', () => { diff --git a/spec/javascripts/clusters/components/applications_spec.js b/spec/javascripts/clusters/components/applications_spec.js index d546543d273..a70138c7eee 100644 --- a/spec/javascripts/clusters/components/applications_spec.js +++ b/spec/javascripts/clusters/components/applications_spec.js @@ -22,6 +22,7 @@ describe('Applications', () => { ingress: { title: 'Ingress' }, runner: { title: 'GitLab Runner' }, prometheus: { title: 'Prometheus' }, + jupyter: { title: 'JupyterHub' }, }, }); }); @@ -41,6 +42,10 @@ describe('Applications', () => { it('renders a row for GitLab Runner', () => { expect(vm.$el.querySelector('.js-cluster-application-row-runner')).toBeDefined(); }); + + it('renders a row for Jupyter', () => { + expect(vm.$el.querySelector('.js-cluster-application-row-jupyter')).not.toBe(null); + }); }); describe('Ingress application', () => { @@ -57,12 +62,11 @@ describe('Applications', () => { helm: { title: 'Helm Tiller' }, runner: { title: 'GitLab Runner' }, prometheus: { title: 'Prometheus' }, + jupyter: { title: 'JupyterHub', hostname: '' }, }, }); - expect( - vm.$el.querySelector('.js-ip-address').value, - ).toEqual('0.0.0.0'); + expect(vm.$el.querySelector('.js-ip-address').value).toEqual('0.0.0.0'); expect( vm.$el.querySelector('.js-clipboard-btn').getAttribute('data-clipboard-text'), @@ -81,12 +85,11 @@ describe('Applications', () => { helm: { title: 'Helm Tiller' }, runner: { title: 'GitLab Runner' }, prometheus: { title: 'Prometheus' }, + jupyter: { title: 'JupyterHub', hostname: '' }, }, }); - expect( - vm.$el.querySelector('.js-ip-address').value, - ).toEqual('?'); + expect(vm.$el.querySelector('.js-ip-address').value).toEqual('?'); expect(vm.$el.querySelector('.js-no-ip-message')).not.toBe(null); }); @@ -101,6 +104,7 @@ describe('Applications', () => { ingress: { title: 'Ingress' }, runner: { title: 'GitLab Runner' }, prometheus: { title: 'Prometheus' }, + jupyter: { title: 'JupyterHub', hostname: '' }, }, }); @@ -108,5 +112,83 @@ describe('Applications', () => { expect(vm.$el.querySelector('.js-ip-address')).toBe(null); }); }); + + describe('Jupyter application', () => { + describe('with ingress installed with ip & jupyter installable', () => { + it('renders hostname active input', () => { + vm = mountComponent(Applications, { + applications: { + helm: { title: 'Helm Tiller', status: 'installed' }, + ingress: { title: 'Ingress', status: 'installed', externalIp: '1.1.1.1' }, + runner: { title: 'GitLab Runner' }, + prometheus: { title: 'Prometheus' }, + jupyter: { title: 'JupyterHub', hostname: '', status: 'installable' }, + }, + }); + + expect(vm.$el.querySelector('.js-hostname').getAttribute('readonly')).toEqual(null); + }); + }); + + describe('with ingress installed without external ip', () => { + it('does not render hostname input', () => { + vm = mountComponent(Applications, { + applications: { + helm: { title: 'Helm Tiller', status: 'installed' }, + ingress: { title: 'Ingress', status: 'installed' }, + runner: { title: 'GitLab Runner' }, + prometheus: { title: 'Prometheus' }, + jupyter: { title: 'JupyterHub', hostname: '', status: 'installable' }, + }, + }); + + expect(vm.$el.querySelector('.js-hostname')).toBe(null); + }); + }); + + describe('with ingress & jupyter installed', () => { + it('renders readonly input', () => { + vm = mountComponent(Applications, { + applications: { + helm: { title: 'Helm Tiller', status: 'installed' }, + ingress: { title: 'Ingress', status: 'installed', externalIp: '1.1.1.1' }, + runner: { title: 'GitLab Runner' }, + prometheus: { title: 'Prometheus' }, + jupyter: { title: 'JupyterHub', status: 'installed', hostname: '' }, + }, + }); + + expect(vm.$el.querySelector('.js-hostname').getAttribute('readonly')).toEqual('readonly'); + }); + }); + + describe('without ingress installed', () => { + beforeEach(() => { + vm = mountComponent(Applications, { + applications: { + helm: { title: 'Helm Tiller' }, + ingress: { title: 'Ingress' }, + runner: { title: 'GitLab Runner' }, + prometheus: { title: 'Prometheus' }, + jupyter: { title: 'JupyterHub', status: 'not_installable' }, + }, + }); + }); + + it('does not render input', () => { + expect(vm.$el.querySelector('.js-hostname')).toBe(null); + }); + + it('renders disabled install button', () => { + expect( + vm.$el + .querySelector( + '.js-cluster-application-row-jupyter .js-cluster-application-install-button', + ) + .getAttribute('disabled'), + ).toEqual('disabled'); + }); + }); + }); }); }); diff --git a/spec/javascripts/clusters/services/mock_data.js b/spec/javascripts/clusters/services/mock_data.js index 6ae7a792329..b2b0ebf840b 100644 --- a/spec/javascripts/clusters/services/mock_data.js +++ b/spec/javascripts/clusters/services/mock_data.js @@ -1,4 +1,5 @@ import { + APPLICATION_INSTALLED, APPLICATION_INSTALLABLE, APPLICATION_INSTALLING, APPLICATION_ERROR, @@ -28,6 +29,39 @@ const CLUSTERS_MOCK_DATA = { name: 'prometheus', status: APPLICATION_ERROR, status_reason: 'Cannot connect', + }, { + name: 'jupyter', + status: APPLICATION_INSTALLING, + status_reason: 'Cannot connect', + }], + }, + }, + '/gitlab-org/gitlab-shell/clusters/2/status.json': { + data: { + status: 'errored', + status_reason: 'Failed to request to CloudPlatform.', + applications: [{ + name: 'helm', + status: APPLICATION_INSTALLED, + status_reason: null, + }, { + name: 'ingress', + status: APPLICATION_INSTALLED, + status_reason: 'Cannot connect', + external_ip: '1.1.1.1', + }, { + name: 'runner', + status: APPLICATION_INSTALLING, + status_reason: null, + }, + { + name: 'prometheus', + status: APPLICATION_ERROR, + status_reason: 'Cannot connect', + }, { + name: 'jupyter', + status: APPLICATION_INSTALLABLE, + status_reason: 'Cannot connect', }], }, }, @@ -37,6 +71,7 @@ const CLUSTERS_MOCK_DATA = { '/gitlab-org/gitlab-shell/clusters/1/applications/ingress': { }, '/gitlab-org/gitlab-shell/clusters/1/applications/runner': { }, '/gitlab-org/gitlab-shell/clusters/1/applications/prometheus': { }, + '/gitlab-org/gitlab-shell/clusters/1/applications/jupyter': { }, }, }; diff --git a/spec/javascripts/clusters/stores/clusters_store_spec.js b/spec/javascripts/clusters/stores/clusters_store_spec.js index 8028faf2f02..6854b016852 100644 --- a/spec/javascripts/clusters/stores/clusters_store_spec.js +++ b/spec/javascripts/clusters/stores/clusters_store_spec.js @@ -91,8 +91,26 @@ describe('Clusters Store', () => { requestStatus: null, requestReason: null, }, + jupyter: { + title: 'JupyterHub', + status: mockResponseData.applications[4].status, + statusReason: mockResponseData.applications[4].status_reason, + requestStatus: null, + requestReason: null, + hostname: '', + }, }, }); }); + + it('sets default hostname for jupyter when ingress has a ip address', () => { + const mockResponseData = CLUSTERS_MOCK_DATA.GET['/gitlab-org/gitlab-shell/clusters/2/status.json'].data; + + store.updateStateFromServer(mockResponseData); + + expect( + store.state.applications.jupyter.hostname, + ).toEqual(`jupyter.${store.state.applications.ingress.externalIp}.xip.io`); + }); }); }); diff --git a/spec/javascripts/commit/commit_pipeline_status_component_spec.js b/spec/javascripts/commit/commit_pipeline_status_component_spec.js index 421fe62a1e7..d3776d0c3cf 100644 --- a/spec/javascripts/commit/commit_pipeline_status_component_spec.js +++ b/spec/javascripts/commit/commit_pipeline_status_component_spec.js @@ -75,10 +75,7 @@ describe('Commit pipeline status component', () => { describe('When polling data was not succesful', () => { beforeEach(() => { mock = new MockAdapter(axios); - mock.onGet('/dummy/endpoint').reply(() => { - const res = Promise.reject([502, { }]); - return res; - }); + mock.onGet('/dummy/endpoint').reply(502, {}); vm = new Component({ props: { endpoint: '/dummy/endpoint', diff --git a/spec/javascripts/helpers/vue_mount_component_helper.js b/spec/javascripts/helpers/vue_mount_component_helper.js index a34a1add4e0..5ba17ecf5b5 100644 --- a/spec/javascripts/helpers/vue_mount_component_helper.js +++ b/spec/javascripts/helpers/vue_mount_component_helper.js @@ -1,30 +1,18 @@ -import Vue from 'vue'; - -const mountComponent = (Component, props = {}, el = null) => new Component({ - propsData: props, -}).$mount(el); - -export const createComponentWithStore = (Component, store, propsData = {}) => new Component({ - store, - propsData, -}); +const mountComponent = (Component, props = {}, el = null) => + new Component({ + propsData: props, + }).$mount(el); -export const createComponentWithMixin = (mixins = [], state = {}, props = {}, template = '<div></div>') => { - const Component = Vue.extend({ - template, - mixins, - data() { - return props; - }, +export const createComponentWithStore = (Component, store, propsData = {}) => + new Component({ + store, + propsData, }); - return mountComponent(Component, props); -}; - export const mountComponentWithStore = (Component, { el, props, store }) => new Component({ store, - propsData: props || { }, + propsData: props || {}, }).$mount(el); export default mountComponent; diff --git a/spec/javascripts/ide/components/external_link_spec.js b/spec/javascripts/ide/components/external_link_spec.js new file mode 100644 index 00000000000..b3d94c041fa --- /dev/null +++ b/spec/javascripts/ide/components/external_link_spec.js @@ -0,0 +1,35 @@ +import Vue from 'vue'; +import externalLink from '~/ide/components/external_link.vue'; +import createVueComponent from '../../helpers/vue_mount_component_helper'; +import { file } from '../helpers'; + +describe('ExternalLink', () => { + const activeFile = file(); + let vm; + + function createComponent() { + const ExternalLink = Vue.extend(externalLink); + + activeFile.permalink = 'test'; + + return createVueComponent(ExternalLink, { + file: activeFile, + }); + } + + afterEach(() => { + vm.$destroy(); + }); + + it('renders the external link with the correct href', done => { + activeFile.binary = true; + vm = createComponent(); + + vm.$nextTick(() => { + const openLink = vm.$el.querySelector('a'); + + expect(openLink.href).toMatch(`/${activeFile.permalink}`); + done(); + }); + }); +}); diff --git a/spec/javascripts/ide/components/ide_file_buttons_spec.js b/spec/javascripts/ide/components/ide_file_buttons_spec.js deleted file mode 100644 index 8ac8d1b2acf..00000000000 --- a/spec/javascripts/ide/components/ide_file_buttons_spec.js +++ /dev/null @@ -1,61 +0,0 @@ -import Vue from 'vue'; -import repoFileButtons from '~/ide/components/ide_file_buttons.vue'; -import createVueComponent from '../../helpers/vue_mount_component_helper'; -import { file } from '../helpers'; - -describe('RepoFileButtons', () => { - const activeFile = file(); - let vm; - - function createComponent() { - const RepoFileButtons = Vue.extend(repoFileButtons); - - activeFile.rawPath = 'test'; - activeFile.blamePath = 'test'; - activeFile.commitsPath = 'test'; - - return createVueComponent(RepoFileButtons, { - file: activeFile, - }); - } - - afterEach(() => { - vm.$destroy(); - }); - - it('renders Raw, Blame, History and Permalink', done => { - vm = createComponent(); - - vm.$nextTick(() => { - const raw = vm.$el.querySelector('.raw'); - const blame = vm.$el.querySelector('.blame'); - const history = vm.$el.querySelector('.history'); - - expect(raw.href).toMatch(`/${activeFile.rawPath}`); - expect(raw.getAttribute('data-original-title')).toEqual('Raw'); - expect(blame.href).toMatch(`/${activeFile.blamePath}`); - expect(blame.getAttribute('data-original-title')).toEqual('Blame'); - expect(history.href).toMatch(`/${activeFile.commitsPath}`); - expect(history.getAttribute('data-original-title')).toEqual('History'); - expect(vm.$el.querySelector('.permalink').getAttribute('data-original-title')).toEqual( - 'Permalink', - ); - - done(); - }); - }); - - it('renders Download', done => { - activeFile.binary = true; - vm = createComponent(); - - vm.$nextTick(() => { - const raw = vm.$el.querySelector('.raw'); - - expect(raw.href).toMatch(`/${activeFile.rawPath}`); - expect(raw.getAttribute('data-original-title')).toEqual('Download'); - - done(); - }); - }); -}); diff --git a/spec/javascripts/ide/components/jobs/item_spec.js b/spec/javascripts/ide/components/jobs/item_spec.js new file mode 100644 index 00000000000..7c1dd4e475c --- /dev/null +++ b/spec/javascripts/ide/components/jobs/item_spec.js @@ -0,0 +1,29 @@ +import Vue from 'vue'; +import JobItem from '~/ide/components/jobs/item.vue'; +import mountComponent from '../../../helpers/vue_mount_component_helper'; +import { jobs } from '../../mock_data'; + +describe('IDE jobs item', () => { + const Component = Vue.extend(JobItem); + const job = jobs[0]; + let vm; + + beforeEach(() => { + vm = mountComponent(Component, { + job, + }); + }); + + afterEach(() => { + vm.$destroy(); + }); + + it('renders job details', () => { + expect(vm.$el.textContent).toContain(job.name); + expect(vm.$el.textContent).toContain(`#${job.id}`); + }); + + it('renders CI icon', () => { + expect(vm.$el.querySelector('.ic-status_passed_borderless')).not.toBe(null); + }); +}); diff --git a/spec/javascripts/ide/components/jobs/list_spec.js b/spec/javascripts/ide/components/jobs/list_spec.js new file mode 100644 index 00000000000..b24853c56fa --- /dev/null +++ b/spec/javascripts/ide/components/jobs/list_spec.js @@ -0,0 +1,67 @@ +import Vue from 'vue'; +import StageList from '~/ide/components/jobs/list.vue'; +import { createStore } from '~/ide/stores'; +import { createComponentWithStore } from '../../../helpers/vue_mount_component_helper'; +import { stages, jobs } from '../../mock_data'; + +describe('IDE stages list', () => { + const Component = Vue.extend(StageList); + let vm; + + beforeEach(() => { + const store = createStore(); + + vm = createComponentWithStore(Component, store, { + stages: stages.map((mappedState, i) => ({ + ...mappedState, + id: i, + dropdownPath: mappedState.dropdown_path, + jobs: [...jobs], + isLoading: false, + isCollapsed: false, + })), + loading: false, + }); + + spyOn(vm, 'fetchJobs'); + spyOn(vm, 'toggleStageCollapsed'); + + vm.$mount(); + }); + + afterEach(() => { + vm.$destroy(); + }); + + it('renders list of stages', () => { + expect(vm.$el.querySelectorAll('.card').length).toBe(2); + }); + + it('renders loading icon when no stages & is loading', done => { + vm.stages = []; + vm.loading = true; + + vm.$nextTick(() => { + expect(vm.$el.querySelector('.loading-container')).not.toBe(null); + + done(); + }); + }); + + it('calls toggleStageCollapsed when clicking stage header', done => { + vm.$el.querySelector('.card-header').click(); + + vm.$nextTick(() => { + expect(vm.toggleStageCollapsed).toHaveBeenCalledWith(0); + + done(); + }); + }); + + it('calls fetchJobs when stage is mounted', () => { + expect(vm.fetchJobs.calls.count()).toBe(stages.length); + + expect(vm.fetchJobs.calls.argsFor(0)).toEqual([vm.stages[0]]); + expect(vm.fetchJobs.calls.argsFor(1)).toEqual([vm.stages[1]]); + }); +}); diff --git a/spec/javascripts/ide/components/jobs/stage_spec.js b/spec/javascripts/ide/components/jobs/stage_spec.js new file mode 100644 index 00000000000..fc3831f2d05 --- /dev/null +++ b/spec/javascripts/ide/components/jobs/stage_spec.js @@ -0,0 +1,95 @@ +import Vue from 'vue'; +import Stage from '~/ide/components/jobs/stage.vue'; +import { stages, jobs } from '../../mock_data'; + +describe('IDE pipeline stage', () => { + const Component = Vue.extend(Stage); + let vm; + let stage; + + beforeEach(() => { + stage = { + ...stages[0], + id: 0, + dropdownPath: stages[0].dropdown_path, + jobs: [...jobs], + isLoading: false, + isCollapsed: false, + }; + + vm = new Component({ + propsData: { stage }, + }); + + spyOn(vm, '$emit'); + + vm.$mount(); + }); + + afterEach(() => { + vm.$destroy(); + }); + + it('emits fetch event when mounted', () => { + expect(vm.$emit).toHaveBeenCalledWith('fetch', vm.stage); + }); + + it('renders stages details', () => { + expect(vm.$el.textContent).toContain(vm.stage.name); + }); + + it('renders CI icon', () => { + expect(vm.$el.querySelector('.ic-status_failed')).not.toBe(null); + }); + + describe('collapsed', () => { + it('emits event when clicking header', done => { + vm.$el.querySelector('.card-header').click(); + + vm.$nextTick(() => { + expect(vm.$emit).toHaveBeenCalledWith('toggleCollapsed', vm.stage.id); + + done(); + }); + }); + + it('toggles collapse status when collapsed', done => { + vm.stage.isCollapsed = true; + + vm.$nextTick(() => { + expect(vm.$el.querySelector('.card-body').style.display).toBe('none'); + + done(); + }); + }); + + it('sets border bottom class when collapsed', done => { + vm.stage.isCollapsed = true; + + vm.$nextTick(() => { + expect(vm.$el.querySelector('.card-header').classList).toContain('border-bottom-0'); + + done(); + }); + }); + }); + + it('renders jobs count', () => { + expect(vm.$el.querySelector('.badge').textContent).toContain('4'); + }); + + it('renders loading icon when no jobs and isLoading is true', done => { + vm.stage.isLoading = true; + vm.stage.jobs = []; + + vm.$nextTick(() => { + expect(vm.$el.querySelector('.loading-container')).not.toBe(null); + + done(); + }); + }); + + it('renders list of jobs', () => { + expect(vm.$el.querySelectorAll('.ide-job-item').length).toBe(4); + }); +}); diff --git a/spec/javascripts/ide/components/pipelines/list_spec.js b/spec/javascripts/ide/components/pipelines/list_spec.js new file mode 100644 index 00000000000..2bb5aa08c3b --- /dev/null +++ b/spec/javascripts/ide/components/pipelines/list_spec.js @@ -0,0 +1,117 @@ +import Vue from 'vue'; +import MockAdapter from 'axios-mock-adapter'; +import axios from '~/lib/utils/axios_utils'; +import { createStore } from '~/ide/stores'; +import List from '~/ide/components/pipelines/list.vue'; +import { createComponentWithStore } from '../../../helpers/vue_mount_component_helper'; +import { pipelines, projectData, stages, jobs } from '../../mock_data'; + +describe('IDE pipelines list', () => { + const Component = Vue.extend(List); + let vm; + let mock; + + beforeEach(done => { + const store = createStore(); + + mock = new MockAdapter(axios); + + store.state.currentProjectId = 'abc/def'; + store.state.currentBranchId = 'master'; + store.state.projects['abc/def'] = { + ...projectData, + path_with_namespace: 'abc/def', + branches: { + master: { commit: { id: '123' } }, + }, + }; + store.state.links = { ciHelpPagePath: gl.TEST_HOST }; + store.state.pipelinesEmptyStateSvgPath = gl.TEST_HOST; + store.state.pipelines.stages = stages.map((mappedState, i) => ({ + ...mappedState, + id: i, + dropdownPath: mappedState.dropdown_path, + jobs: [...jobs], + isLoading: false, + isCollapsed: false, + })); + + mock + .onGet('/abc/def/commit/123/pipelines') + .replyOnce(200, { pipelines: [...pipelines] }, { 'poll-interval': '-1' }); + + vm = createComponentWithStore(Component, store).$mount(); + + setTimeout(done); + }); + + afterEach(() => { + vm.$store.dispatch('pipelines/stopPipelinePolling'); + vm.$store.dispatch('pipelines/clearEtagPoll'); + + vm.$destroy(); + mock.restore(); + }); + + it('renders pipeline data', () => { + expect(vm.$el.textContent).toContain('#1'); + }); + + it('renders CI icon', () => { + expect(vm.$el.querySelector('.ci-status-icon-failed')).not.toBe(null); + }); + + it('renders list of jobs', () => { + expect(vm.$el.querySelectorAll('.tab-pane:first-child .ide-job-item').length).toBe( + jobs.length * stages.length, + ); + }); + + it('renders list of failed jobs on failed jobs tab', done => { + vm.$el.querySelectorAll('.tab-links a')[1].click(); + + vm.$nextTick(() => { + expect(vm.$el.querySelectorAll('.tab-pane.active .ide-job-item').length).toBe(2); + + done(); + }); + }); + + describe('YAML error', () => { + it('renders YAML error', done => { + vm.$store.state.pipelines.latestPipeline.yamlError = 'test yaml error'; + + vm.$nextTick(() => { + expect(vm.$el.textContent).toContain('Found errors in your .gitlab-ci.yml:'); + expect(vm.$el.textContent).toContain('test yaml error'); + + done(); + }); + }); + }); + + describe('empty state', () => { + it('renders pipelines empty state', done => { + vm.$store.state.pipelines.latestPipeline = false; + + vm.$nextTick(() => { + expect(vm.$el.querySelector('.empty-state')).not.toBe(null); + + done(); + }); + }); + }); + + describe('loading state', () => { + it('renders loading state when there is no latest pipeline', done => { + vm.$store.state.pipelines.latestPipeline = null; + vm.$store.state.pipelines.isLoadingPipeline = true; + + vm.$nextTick(() => { + expect(vm.$el.querySelector('.loading-container')).not.toBe(null); + + done(); + }); + }); + }); +}); diff --git a/spec/javascripts/ide/helpers.js b/spec/javascripts/ide/helpers.js index bc9a7f97b37..9312e17704e 100644 --- a/spec/javascripts/ide/helpers.js +++ b/spec/javascripts/ide/helpers.js @@ -2,12 +2,14 @@ import { decorateData } from '~/ide/stores/utils'; import state from '~/ide/stores/state'; import commitState from '~/ide/stores/modules/commit/state'; import mergeRequestsState from '~/ide/stores/modules/merge_requests/state'; +import pipelinesState from '~/ide/stores/modules/pipelines/state'; export const resetStore = store => { const newState = { ...state(), commit: commitState(), mergeRequests: mergeRequestsState(), + pipelines: pipelinesState(), }; store.replaceState(newState); }; diff --git a/spec/javascripts/ide/mock_data.js b/spec/javascripts/ide/mock_data.js index 8f24114de26..dcf857f7e04 100644 --- a/spec/javascripts/ide/mock_data.js +++ b/spec/javascripts/ide/mock_data.js @@ -19,13 +19,48 @@ export const pipelines = [ id: 1, ref: 'master', sha: '123', - status: 'failed', + details: { + status: { + icon: 'status_failed', + group: 'failed', + text: 'Failed', + }, + }, + commit: { id: '123' }, }, { id: 2, ref: 'master', sha: '213', - status: 'success', + details: { + status: { + icon: 'status_failed', + group: 'failed', + text: 'Failed', + }, + }, + commit: { id: '213' }, + }, +]; + +export const stages = [ + { + dropdown_path: `${gl.TEST_HOST}/testing`, + name: 'build', + status: { + icon: 'status_failed', + group: 'failed', + text: 'failed', + }, + }, + { + dropdown_path: 'testing', + name: 'test', + status: { + icon: 'status_failed', + group: 'failed', + text: 'failed', + }, }, ]; @@ -33,28 +68,44 @@ export const jobs = [ { id: 1, name: 'test', - status: 'failed', + path: 'testing', + status: { + icon: 'status_passed', + text: 'passed', + }, stage: 'test', duration: 1, }, { id: 2, name: 'test 2', - status: 'failed', + path: 'testing2', + status: { + icon: 'status_passed', + text: 'passed', + }, stage: 'test', duration: 1, }, { id: 3, name: 'test 3', - status: 'failed', + path: 'testing3', + status: { + icon: 'status_passed', + text: 'passed', + }, stage: 'test', duration: 1, }, { id: 4, - name: 'test 3', - status: 'failed', + name: 'test 4', + path: 'testing4', + status: { + icon: 'status_failed', + text: 'failed', + }, stage: 'build', duration: 1, }, @@ -68,14 +119,16 @@ export const fullPipelinesResponse = { pipelines: [ { id: '51', + path: 'test', commit: { - id: 'xxxxxxxxxxxxxxxxxxxx', + id: '123', }, details: { status: { icon: 'status_failed', text: 'failed', }, + stages: [...stages], }, }, { @@ -88,6 +141,7 @@ export const fullPipelinesResponse = { icon: 'status_passed', text: 'passed', }, + stages: [...stages], }, }, ], diff --git a/spec/javascripts/ide/stores/actions/project_spec.js b/spec/javascripts/ide/stores/actions/project_spec.js index 8e078ae7138..d71fc0e035e 100644 --- a/spec/javascripts/ide/stores/actions/project_spec.js +++ b/spec/javascripts/ide/stores/actions/project_spec.js @@ -1,31 +1,10 @@ -import Visibility from 'visibilityjs'; -import MockAdapter from 'axios-mock-adapter'; -import { refreshLastCommitData, pollSuccessCallBack } from '~/ide/stores/actions'; +import { refreshLastCommitData } from '~/ide/stores/actions'; import store from '~/ide/stores'; import service from '~/ide/services'; -import axios from '~/lib/utils/axios_utils'; -import { fullPipelinesResponse } from '../../mock_data'; import { resetStore } from '../../helpers'; import testAction from '../../../helpers/vuex_action_helper'; describe('IDE store project actions', () => { - const setProjectState = () => { - store.state.currentProjectId = 'abc/def'; - store.state.currentBranchId = 'master'; - store.state.projects['abc/def'] = { - id: 4, - path_with_namespace: 'abc/def', - branches: { - master: { - commit: { - id: 'abc123def456ghi789jkl', - title: 'example', - }, - }, - }, - }; - }; - beforeEach(() => { store.state.projects['abc/def'] = {}; }); @@ -101,92 +80,4 @@ describe('IDE store project actions', () => { ); }); }); - - describe('pipelinePoll', () => { - let mock; - - beforeEach(() => { - setProjectState(); - jasmine.clock().install(); - mock = new MockAdapter(axios); - mock - .onGet('/abc/def/commit/abc123def456ghi789jkl/pipelines') - .reply(200, { data: { foo: 'bar' } }, { 'poll-interval': '10000' }); - }); - - afterEach(() => { - jasmine.clock().uninstall(); - mock.restore(); - store.dispatch('stopPipelinePolling'); - }); - - it('calls service periodically', done => { - spyOn(axios, 'get').and.callThrough(); - spyOn(Visibility, 'hidden').and.returnValue(false); - - store - .dispatch('pipelinePoll') - .then(() => { - jasmine.clock().tick(1000); - - expect(axios.get).toHaveBeenCalled(); - expect(axios.get.calls.count()).toBe(1); - }) - .then(() => new Promise(resolve => requestAnimationFrame(resolve))) - .then(() => { - jasmine.clock().tick(10000); - expect(axios.get.calls.count()).toBe(2); - }) - .then(() => new Promise(resolve => requestAnimationFrame(resolve))) - .then(() => { - jasmine.clock().tick(10000); - expect(axios.get.calls.count()).toBe(3); - }) - .then(() => new Promise(resolve => requestAnimationFrame(resolve))) - .then(() => { - jasmine.clock().tick(10000); - expect(axios.get.calls.count()).toBe(4); - }) - - .then(done) - .catch(done.fail); - }); - }); - - describe('pollSuccessCallBack', () => { - beforeEach(() => { - setProjectState(); - }); - - it('commits correct pipeline', done => { - testAction( - pollSuccessCallBack, - fullPipelinesResponse, - store.state, - [ - { - type: 'SET_LAST_COMMIT_PIPELINE', - payload: { - projectId: 'abc/def', - branchId: 'master', - pipeline: { - id: '50', - commit: { - id: 'abc123def456ghi789jkl', - }, - details: { - status: { - icon: 'status_passed', - text: 'passed', - }, - }, - }, - }, - }, - ], // mutations - [], // action - done, - ); - }); - }); }); diff --git a/spec/javascripts/ide/stores/modules/pipelines/actions_spec.js b/spec/javascripts/ide/stores/modules/pipelines/actions_spec.js index 85fbcf8084b..f26eaf9c81f 100644 --- a/spec/javascripts/ide/stores/modules/pipelines/actions_spec.js +++ b/spec/javascripts/ide/stores/modules/pipelines/actions_spec.js @@ -1,3 +1,4 @@ +import Visibility from 'visibilityjs'; import MockAdapter from 'axios-mock-adapter'; import axios from '~/lib/utils/axios_utils'; import actions, { @@ -5,10 +6,13 @@ import actions, { receiveLatestPipelineError, receiveLatestPipelineSuccess, fetchLatestPipeline, + stopPipelinePolling, + clearEtagPoll, requestJobs, receiveJobsError, receiveJobsSuccess, fetchJobs, + toggleStageCollapsed, } from '~/ide/stores/modules/pipelines/actions'; import state from '~/ide/stores/modules/pipelines/state'; import * as types from '~/ide/stores/modules/pipelines/mutation_types'; @@ -51,7 +55,7 @@ describe('IDE pipelines actions', () => { null, mockedState, [{ type: types.RECEIVE_LASTEST_PIPELINE_ERROR }], - [], + [{ type: 'stopPipelinePolling' }], done, ); }); @@ -59,91 +63,128 @@ describe('IDE pipelines actions', () => { it('creates flash message', () => { const flashSpy = spyOnDependency(actions, 'flash'); - receiveLatestPipelineError({ commit() {} }); + receiveLatestPipelineError({ commit() {}, dispatch() {} }); expect(flashSpy).toHaveBeenCalled(); }); }); describe('receiveLatestPipelineSuccess', () => { - it('commits pipeline', done => { - testAction( - receiveLatestPipelineSuccess, + const rootGetters = { + lastCommit: { id: '123' }, + }; + let commit; + + beforeEach(() => { + commit = jasmine.createSpy('commit'); + }); + + it('commits pipeline', () => { + receiveLatestPipelineSuccess({ rootGetters, commit }, { pipelines }); + + expect(commit.calls.argsFor(0)).toEqual([ + types.RECEIVE_LASTEST_PIPELINE_SUCCESS, pipelines[0], - mockedState, - [{ type: types.RECEIVE_LASTEST_PIPELINE_SUCCESS, payload: pipelines[0] }], - [], - done, - ); + ]); + }); + + it('commits false when there are no pipelines', () => { + receiveLatestPipelineSuccess({ rootGetters, commit }, { pipelines: [] }); + + expect(commit.calls.argsFor(0)).toEqual([types.RECEIVE_LASTEST_PIPELINE_SUCCESS, false]); }); }); describe('fetchLatestPipeline', () => { + beforeEach(() => { + jasmine.clock().install(); + }); + + afterEach(() => { + jasmine.clock().uninstall(); + stopPipelinePolling(); + clearEtagPoll(); + }); + describe('success', () => { beforeEach(() => { - mock.onGet(/\/api\/v4\/projects\/(.*)\/pipelines(.*)/).replyOnce(200, pipelines); + mock + .onGet('/abc/def/commit/abc123def456ghi789jkl/pipelines') + .reply(200, { data: { foo: 'bar' } }, { 'poll-interval': '10000' }); }); it('dispatches request', done => { - testAction( - fetchLatestPipeline, - '123', - mockedState, - [], - [{ type: 'requestLatestPipeline' }, { type: 'receiveLatestPipelineSuccess' }], - done, - ); - }); - - it('dispatches success with latest pipeline', done => { - testAction( - fetchLatestPipeline, - '123', - mockedState, - [], - [ - { type: 'requestLatestPipeline' }, - { type: 'receiveLatestPipelineSuccess', payload: pipelines[0] }, - ], - done, - ); - }); - - it('calls axios with correct params', () => { - const apiSpy = spyOn(axios, 'get').and.callThrough(); - - fetchLatestPipeline({ dispatch() {}, rootState: state }, '123'); - - expect(apiSpy).toHaveBeenCalledWith(jasmine.anything(), { - params: { - sha: '123', - per_page: '1', - }, - }); + spyOn(axios, 'get').and.callThrough(); + spyOn(Visibility, 'hidden').and.returnValue(false); + + const dispatch = jasmine.createSpy('dispatch'); + const rootGetters = { + lastCommit: { id: 'abc123def456ghi789jkl' }, + currentProject: { path_with_namespace: 'abc/def' }, + }; + + fetchLatestPipeline({ dispatch, rootGetters }); + + expect(dispatch.calls.argsFor(0)).toEqual(['requestLatestPipeline']); + + jasmine.clock().tick(1000); + + new Promise(resolve => requestAnimationFrame(resolve)) + .then(() => { + expect(axios.get).toHaveBeenCalled(); + expect(axios.get.calls.count()).toBe(1); + + expect(dispatch.calls.argsFor(1)).toEqual([ + 'receiveLatestPipelineSuccess', + jasmine.anything(), + ]); + + jasmine.clock().tick(10000); + }) + .then(() => new Promise(resolve => requestAnimationFrame(resolve))) + .then(() => { + expect(axios.get).toHaveBeenCalled(); + expect(axios.get.calls.count()).toBe(2); + + expect(dispatch.calls.argsFor(2)).toEqual([ + 'receiveLatestPipelineSuccess', + jasmine.anything(), + ]); + }) + .then(done) + .catch(done.fail); }); }); describe('error', () => { beforeEach(() => { - mock.onGet(/\/api\/v4\/projects\/(.*)\/pipelines(.*)/).replyOnce(500); + mock.onGet('/abc/def/commit/abc123def456ghi789jkl/pipelines').reply(500); }); it('dispatches error', done => { - testAction( - fetchLatestPipeline, - '123', - mockedState, - [], - [{ type: 'requestLatestPipeline' }, { type: 'receiveLatestPipelineError' }], - done, - ); + const dispatch = jasmine.createSpy('dispatch'); + const rootGetters = { + lastCommit: { id: 'abc123def456ghi789jkl' }, + currentProject: { path_with_namespace: 'abc/def' }, + }; + + fetchLatestPipeline({ dispatch, rootGetters }); + + jasmine.clock().tick(1500); + + new Promise(resolve => requestAnimationFrame(resolve)) + .then(() => { + expect(dispatch.calls.argsFor(1)).toEqual(['receiveLatestPipelineError']); + }) + .then(done) + .catch(done.fail); }); }); }); describe('requestJobs', () => { it('commits request', done => { - testAction(requestJobs, null, mockedState, [{ type: types.REQUEST_JOBS }], [], done); + testAction(requestJobs, 1, mockedState, [{ type: types.REQUEST_JOBS, payload: 1 }], [], done); }); }); @@ -151,9 +192,9 @@ describe('IDE pipelines actions', () => { it('commits error', done => { testAction( receiveJobsError, - null, + 1, mockedState, - [{ type: types.RECEIVE_JOBS_ERROR }], + [{ type: types.RECEIVE_JOBS_ERROR, payload: 1 }], [], done, ); @@ -162,19 +203,19 @@ describe('IDE pipelines actions', () => { it('creates flash message', () => { const flashSpy = spyOnDependency(actions, 'flash'); - receiveJobsError({ commit() {} }); + receiveJobsError({ commit() {} }, 1); expect(flashSpy).toHaveBeenCalled(); }); }); describe('receiveJobsSuccess', () => { - it('commits jobs', done => { + it('commits data', done => { testAction( receiveJobsSuccess, - jobs, + { id: 1, data: jobs }, mockedState, - [{ type: types.RECEIVE_JOBS_SUCCESS, payload: jobs }], + [{ type: types.RECEIVE_JOBS_SUCCESS, payload: { id: 1, data: jobs } }], [], done, ); @@ -182,108 +223,62 @@ describe('IDE pipelines actions', () => { }); describe('fetchJobs', () => { - let page = ''; - - beforeEach(() => { - mockedState.latestPipeline = pipelines[0]; - }); + const stage = { + id: 1, + dropdownPath: `${gl.TEST_HOST}/jobs`, + }; describe('success', () => { beforeEach(() => { - mock.onGet(/\/api\/v4\/projects\/(.*)\/pipelines\/(.*)\/jobs/).replyOnce(() => [ - 200, - jobs, - { - 'x-next-page': page, - }, - ]); + mock.onGet(stage.dropdownPath).replyOnce(200, jobs); }); it('dispatches request', done => { testAction( fetchJobs, - null, - mockedState, - [], - [{ type: 'requestJobs' }, { type: 'receiveJobsSuccess' }], - done, - ); - }); - - it('dispatches success with latest pipeline', done => { - testAction( - fetchJobs, - null, - mockedState, - [], - [{ type: 'requestJobs' }, { type: 'receiveJobsSuccess', payload: jobs }], - done, - ); - }); - - it('dispatches twice for both pages', done => { - page = '2'; - - testAction( - fetchJobs, - null, + stage, mockedState, [], [ - { type: 'requestJobs' }, - { type: 'receiveJobsSuccess', payload: jobs }, - { type: 'fetchJobs', payload: '2' }, - { type: 'requestJobs' }, - { type: 'receiveJobsSuccess', payload: jobs }, + { type: 'requestJobs', payload: stage.id }, + { type: 'receiveJobsSuccess', payload: { id: stage.id, data: jobs } }, ], done, ); }); - - it('calls axios with correct URL', () => { - const apiSpy = spyOn(axios, 'get').and.callThrough(); - - fetchJobs({ dispatch() {}, state: mockedState, rootState: mockedState }); - - expect(apiSpy).toHaveBeenCalledWith('/api/v4/projects/test%2Fproject/pipelines/1/jobs', { - params: { page: '1' }, - }); - }); - - it('calls axios with page next page', () => { - const apiSpy = spyOn(axios, 'get').and.callThrough(); - - fetchJobs({ dispatch() {}, state: mockedState, rootState: mockedState }); - - expect(apiSpy).toHaveBeenCalledWith('/api/v4/projects/test%2Fproject/pipelines/1/jobs', { - params: { page: '1' }, - }); - - page = '2'; - - fetchJobs({ dispatch() {}, state: mockedState, rootState: mockedState }, page); - - expect(apiSpy).toHaveBeenCalledWith('/api/v4/projects/test%2Fproject/pipelines/1/jobs', { - params: { page: '2' }, - }); - }); }); describe('error', () => { beforeEach(() => { - mock.onGet(/\/api\/v4\/projects\/(.*)\/pipelines(.*)/).replyOnce(500); + mock.onGet(stage.dropdownPath).replyOnce(500); }); it('dispatches error', done => { testAction( fetchJobs, - null, + stage, mockedState, [], - [{ type: 'requestJobs' }, { type: 'receiveJobsError' }], + [ + { type: 'requestJobs', payload: stage.id }, + { type: 'receiveJobsError', payload: stage.id }, + ], done, ); }); }); }); + + describe('toggleStageCollapsed', () => { + it('commits collapse', done => { + testAction( + toggleStageCollapsed, + 1, + mockedState, + [{ type: types.TOGGLE_STAGE_COLLAPSE, payload: 1 }], + [], + done, + ); + }); + }); }); diff --git a/spec/javascripts/ide/stores/modules/pipelines/getters_spec.js b/spec/javascripts/ide/stores/modules/pipelines/getters_spec.js index b2a7e8a9025..4514896b5ea 100644 --- a/spec/javascripts/ide/stores/modules/pipelines/getters_spec.js +++ b/spec/javascripts/ide/stores/modules/pipelines/getters_spec.js @@ -37,35 +37,4 @@ describe('IDE pipeline getters', () => { expect(getters.hasLatestPipeline(mockedState)).toBe(true); }); }); - - describe('failedJobs', () => { - it('returns array of failed jobs', () => { - mockedState.stages = [ - { - title: 'test', - jobs: [{ id: 1, status: 'failed' }, { id: 2, status: 'success' }], - }, - { - title: 'build', - jobs: [{ id: 3, status: 'failed' }, { id: 4, status: 'failed' }], - }, - ]; - - expect(getters.failedJobs(mockedState).length).toBe(3); - expect(getters.failedJobs(mockedState)).toEqual([ - { - id: 1, - status: jasmine.anything(), - }, - { - id: 3, - status: jasmine.anything(), - }, - { - id: 4, - status: jasmine.anything(), - }, - ]); - }); - }); }); diff --git a/spec/javascripts/ide/stores/modules/pipelines/mutations_spec.js b/spec/javascripts/ide/stores/modules/pipelines/mutations_spec.js index 8262e916243..6285c01d483 100644 --- a/spec/javascripts/ide/stores/modules/pipelines/mutations_spec.js +++ b/spec/javascripts/ide/stores/modules/pipelines/mutations_spec.js @@ -1,7 +1,7 @@ import mutations from '~/ide/stores/modules/pipelines/mutations'; import state from '~/ide/stores/modules/pipelines/state'; import * as types from '~/ide/stores/modules/pipelines/mutation_types'; -import { pipelines, jobs } from '../../../mock_data'; +import { fullPipelinesResponse, stages, jobs } from '../../../mock_data'; describe('IDE pipelines mutations', () => { let mockedState; @@ -28,93 +28,147 @@ describe('IDE pipelines mutations', () => { describe(types.RECEIVE_LASTEST_PIPELINE_SUCCESS, () => { it('sets loading to false on success', () => { - mutations[types.RECEIVE_LASTEST_PIPELINE_SUCCESS](mockedState, pipelines[0]); + mutations[types.RECEIVE_LASTEST_PIPELINE_SUCCESS]( + mockedState, + fullPipelinesResponse.data.pipelines[0], + ); expect(mockedState.isLoadingPipeline).toBe(false); }); it('sets latestPipeline', () => { - mutations[types.RECEIVE_LASTEST_PIPELINE_SUCCESS](mockedState, pipelines[0]); + mutations[types.RECEIVE_LASTEST_PIPELINE_SUCCESS]( + mockedState, + fullPipelinesResponse.data.pipelines[0], + ); expect(mockedState.latestPipeline).toEqual({ - id: pipelines[0].id, - status: pipelines[0].status, + id: '51', + path: 'test', + commit: { id: '123' }, + details: { status: jasmine.any(Object) }, + yamlError: undefined, }); }); it('does not set latest pipeline if pipeline is null', () => { mutations[types.RECEIVE_LASTEST_PIPELINE_SUCCESS](mockedState, null); - expect(mockedState.latestPipeline).toEqual(null); + expect(mockedState.latestPipeline).toEqual(false); + }); + + it('sets stages', () => { + mutations[types.RECEIVE_LASTEST_PIPELINE_SUCCESS]( + mockedState, + fullPipelinesResponse.data.pipelines[0], + ); + + expect(mockedState.stages.length).toBe(2); + expect(mockedState.stages).toEqual([ + { + id: 0, + dropdownPath: stages[0].dropdown_path, + name: stages[0].name, + status: stages[0].status, + isCollapsed: false, + isLoading: false, + jobs: [], + }, + { + id: 1, + dropdownPath: stages[1].dropdown_path, + name: stages[1].name, + status: stages[1].status, + isCollapsed: false, + isLoading: false, + jobs: [], + }, + ]); }); }); describe(types.REQUEST_JOBS, () => { - it('sets jobs loading to true', () => { - mutations[types.REQUEST_JOBS](mockedState); + beforeEach(() => { + mockedState.stages = stages.map((stage, i) => ({ + ...stage, + id: i, + })); + }); + + it('sets isLoading on stage', () => { + mutations[types.REQUEST_JOBS](mockedState, mockedState.stages[0].id); - expect(mockedState.isLoadingJobs).toBe(true); + expect(mockedState.stages[0].isLoading).toBe(true); }); }); describe(types.RECEIVE_JOBS_ERROR, () => { - it('sets jobs loading to false', () => { - mutations[types.RECEIVE_JOBS_ERROR](mockedState); + beforeEach(() => { + mockedState.stages = stages.map((stage, i) => ({ + ...stage, + id: i, + })); + }); + + it('sets isLoading on stage after error', () => { + mutations[types.RECEIVE_JOBS_ERROR](mockedState, mockedState.stages[0].id); - expect(mockedState.isLoadingJobs).toBe(false); + expect(mockedState.stages[0].isLoading).toBe(false); }); }); describe(types.RECEIVE_JOBS_SUCCESS, () => { - it('sets jobs loading to false on success', () => { - mutations[types.RECEIVE_JOBS_SUCCESS](mockedState, jobs); + let data; - expect(mockedState.isLoadingJobs).toBe(false); + beforeEach(() => { + mockedState.stages = stages.map((stage, i) => ({ + ...stage, + id: i, + })); + + data = { + latest_statuses: [...jobs], + }; }); - it('sets stages', () => { - mutations[types.RECEIVE_JOBS_SUCCESS](mockedState, jobs); + it('updates loading', () => { + mutations[types.RECEIVE_JOBS_SUCCESS](mockedState, { id: mockedState.stages[0].id, data }); - expect(mockedState.stages.length).toBe(2); - expect(mockedState.stages).toEqual([ - { - title: 'test', - jobs: jasmine.anything(), - }, - { - title: 'build', - jobs: jasmine.anything(), - }, - ]); + expect(mockedState.stages[0].isLoading).toBe(false); }); - it('sets jobs in stages', () => { - mutations[types.RECEIVE_JOBS_SUCCESS](mockedState, jobs); + it('sets jobs on stage', () => { + mutations[types.RECEIVE_JOBS_SUCCESS](mockedState, { id: mockedState.stages[0].id, data }); + + expect(mockedState.stages[0].jobs.length).toBe(jobs.length); + expect(mockedState.stages[0].jobs).toEqual( + jobs.map(job => ({ + id: job.id, + name: job.name, + status: job.status, + path: job.build_path, + })), + ); + }); + }); - expect(mockedState.stages[0].jobs.length).toBe(3); - expect(mockedState.stages[1].jobs.length).toBe(1); - expect(mockedState.stages).toEqual([ - { - title: jasmine.anything(), - jobs: jobs.filter(job => job.stage === 'test').map(job => ({ - id: job.id, - name: job.name, - status: job.status, - stage: job.stage, - duration: job.duration, - })), - }, - { - title: jasmine.anything(), - jobs: jobs.filter(job => job.stage === 'build').map(job => ({ - id: job.id, - name: job.name, - status: job.status, - stage: job.stage, - duration: job.duration, - })), - }, - ]); + describe(types.TOGGLE_STAGE_COLLAPSE, () => { + beforeEach(() => { + mockedState.stages = stages.map((stage, i) => ({ + ...stage, + id: i, + isCollapsed: false, + })); + }); + + it('toggles collapsed state', () => { + mutations[types.TOGGLE_STAGE_COLLAPSE](mockedState, mockedState.stages[0].id); + + expect(mockedState.stages[0].isCollapsed).toBe(true); + + mutations[types.TOGGLE_STAGE_COLLAPSE](mockedState, mockedState.stages[0].id); + + expect(mockedState.stages[0].isCollapsed).toBe(false); }); }); }); diff --git a/spec/javascripts/ide/stores/mutations/branch_spec.js b/spec/javascripts/ide/stores/mutations/branch_spec.js index f2f1f2a9a2e..29eb859ddaf 100644 --- a/spec/javascripts/ide/stores/mutations/branch_spec.js +++ b/spec/javascripts/ide/stores/mutations/branch_spec.js @@ -37,40 +37,4 @@ describe('Multi-file store branch mutations', () => { expect(localState.projects.Example.branches.master.commit.title).toBe('Example commit'); }); }); - - describe('SET_LAST_COMMIT_PIPELINE', () => { - it('sets the pipeline for the last commit on current project', () => { - localState.projects = { - Example: { - branches: { - master: { - commit: {}, - }, - }, - }, - }; - - mutations.SET_LAST_COMMIT_PIPELINE(localState, { - projectId: 'Example', - branchId: 'master', - pipeline: { - id: '50', - details: { - status: { - icon: 'status_passed', - text: 'passed', - }, - }, - }, - }); - - expect(localState.projects.Example.branches.master.commit.pipeline.id).toBe('50'); - expect(localState.projects.Example.branches.master.commit.pipeline.details.status.text).toBe( - 'passed', - ); - expect(localState.projects.Example.branches.master.commit.pipeline.details.status.icon).toBe( - 'status_passed', - ); - }); - }); }); diff --git a/spec/javascripts/sidebar/confidential_issue_sidebar_spec.js b/spec/javascripts/sidebar/confidential_issue_sidebar_spec.js index 0c173062835..6110d5d89ac 100644 --- a/spec/javascripts/sidebar/confidential_issue_sidebar_spec.js +++ b/spec/javascripts/sidebar/confidential_issue_sidebar_spec.js @@ -8,10 +8,7 @@ describe('Confidential Issue Sidebar Block', () => { beforeEach(() => { const Component = Vue.extend(confidentialIssueSidebar); const service = { - update: () => new Promise((resolve, reject) => { - resolve(true); - reject('failed!'); - }), + update: () => Promise.resolve(true), }; vm1 = new Component({ diff --git a/spec/javascripts/u2f/mock_u2f_device.js b/spec/javascripts/u2f/mock_u2f_device.js index 5a1ace2b4d6..8fec6ae3fa4 100644 --- a/spec/javascripts/u2f/mock_u2f_device.js +++ b/spec/javascripts/u2f/mock_u2f_device.js @@ -1,5 +1,5 @@ /* eslint-disable prefer-rest-params, wrap-iife, -no-unused-expressions, no-return-assign, no-param-reassign*/ +no-unused-expressions, no-return-assign, no-param-reassign */ export default class MockU2FDevice { constructor() { diff --git a/spec/javascripts/vue_shared/components/tabs/tab_spec.js b/spec/javascripts/vue_shared/components/tabs/tab_spec.js new file mode 100644 index 00000000000..8437fe37738 --- /dev/null +++ b/spec/javascripts/vue_shared/components/tabs/tab_spec.js @@ -0,0 +1,32 @@ +import Vue from 'vue'; +import mountComponent from 'spec/helpers/vue_mount_component_helper'; +import Tab from '~/vue_shared/components/tabs/tab.vue'; + +describe('Tab component', () => { + const Component = Vue.extend(Tab); + let vm; + + beforeEach(() => { + vm = mountComponent(Component); + }); + + it('sets localActive to equal active', done => { + vm.active = true; + + vm.$nextTick(() => { + expect(vm.localActive).toBe(true); + + done(); + }); + }); + + it('sets active class', done => { + vm.active = true; + + vm.$nextTick(() => { + expect(vm.$el.classList).toContain('active'); + + done(); + }); + }); +}); diff --git a/spec/javascripts/vue_shared/components/tabs/tabs_spec.js b/spec/javascripts/vue_shared/components/tabs/tabs_spec.js new file mode 100644 index 00000000000..50ba18cd338 --- /dev/null +++ b/spec/javascripts/vue_shared/components/tabs/tabs_spec.js @@ -0,0 +1,68 @@ +import Vue from 'vue'; +import Tabs from '~/vue_shared/components/tabs/tabs'; +import Tab from '~/vue_shared/components/tabs/tab.vue'; + +describe('Tabs component', () => { + let vm; + + beforeEach(done => { + vm = new Vue({ + components: { + Tabs, + Tab, + }, + template: ` + <div> + <tabs> + <tab title="Testing" active> + First tab + </tab> + <tab> + <template slot="title">Test slot</template> + Second tab + </tab> + </tabs> + </div> + `, + }).$mount(); + + setTimeout(done); + }); + + describe('tab links', () => { + it('renders links for tabs', () => { + expect(vm.$el.querySelectorAll('a').length).toBe(2); + }); + + it('renders link titles from props', () => { + expect(vm.$el.querySelector('a').textContent).toContain('Testing'); + }); + + it('renders link titles from slot', () => { + expect(vm.$el.querySelectorAll('a')[1].textContent).toContain('Test slot'); + }); + + it('renders active class', () => { + expect(vm.$el.querySelector('a').classList).toContain('active'); + }); + + it('updates active class on click', done => { + vm.$el.querySelectorAll('a')[1].click(); + + setTimeout(() => { + expect(vm.$el.querySelector('a').classList).not.toContain('active'); + expect(vm.$el.querySelectorAll('a')[1].classList).toContain('active'); + + done(); + }); + }); + }); + + describe('content', () => { + it('renders content panes', () => { + expect(vm.$el.querySelectorAll('.tab-pane').length).toBe(2); + expect(vm.$el.querySelectorAll('.tab-pane')[0].textContent).toContain('First tab'); + expect(vm.$el.querySelectorAll('.tab-pane')[1].textContent).toContain('Second tab'); + }); + }); +}); diff --git a/spec/lib/gitlab/auth/request_authenticator_spec.rb b/spec/lib/gitlab/auth/request_authenticator_spec.rb index ffcd90b9fcb..242ab4a91dd 100644 --- a/spec/lib/gitlab/auth/request_authenticator_spec.rb +++ b/spec/lib/gitlab/auth/request_authenticator_spec.rb @@ -39,19 +39,19 @@ describe Gitlab::Auth::RequestAuthenticator do describe '#find_sessionless_user' do let!(:access_token_user) { build(:user) } - let!(:rss_token_user) { build(:user) } + let!(:feed_token_user) { build(:user) } it 'returns access_token user first' do allow_any_instance_of(described_class).to receive(:find_user_from_access_token).and_return(access_token_user) - allow_any_instance_of(described_class).to receive(:find_user_from_rss_token).and_return(rss_token_user) + allow_any_instance_of(described_class).to receive(:find_user_from_feed_token).and_return(feed_token_user) expect(subject.find_sessionless_user).to eq access_token_user end - it 'returns rss_token user if no access_token user found' do - allow_any_instance_of(described_class).to receive(:find_user_from_rss_token).and_return(rss_token_user) + it 'returns feed_token user if no access_token user found' do + allow_any_instance_of(described_class).to receive(:find_user_from_feed_token).and_return(feed_token_user) - expect(subject.find_sessionless_user).to eq rss_token_user + expect(subject.find_sessionless_user).to eq feed_token_user end it 'returns nil if no user found' do diff --git a/spec/lib/gitlab/auth/user_auth_finders_spec.rb b/spec/lib/gitlab/auth/user_auth_finders_spec.rb index 2733eef6611..136646bd4ee 100644 --- a/spec/lib/gitlab/auth/user_auth_finders_spec.rb +++ b/spec/lib/gitlab/auth/user_auth_finders_spec.rb @@ -46,34 +46,54 @@ describe Gitlab::Auth::UserAuthFinders do end end - describe '#find_user_from_rss_token' do + describe '#find_user_from_feed_token' do context 'when the request format is atom' do before do env['HTTP_ACCEPT'] = 'application/atom+xml' end - it 'returns user if valid rss_token' do - set_param(:rss_token, user.rss_token) + context 'when feed_token param is provided' do + it 'returns user if valid feed_token' do + set_param(:feed_token, user.feed_token) - expect(find_user_from_rss_token).to eq user - end + expect(find_user_from_feed_token).to eq user + end + + it 'returns nil if feed_token is blank' do + expect(find_user_from_feed_token).to be_nil + end + + it 'returns exception if invalid feed_token' do + set_param(:feed_token, 'invalid_token') - it 'returns nil if rss_token is blank' do - expect(find_user_from_rss_token).to be_nil + expect { find_user_from_feed_token }.to raise_error(Gitlab::Auth::UnauthorizedError) + end end - it 'returns exception if invalid rss_token' do - set_param(:rss_token, 'invalid_token') + context 'when rss_token param is provided' do + it 'returns user if valid rssd_token' do + set_param(:rss_token, user.feed_token) - expect { find_user_from_rss_token }.to raise_error(Gitlab::Auth::UnauthorizedError) + expect(find_user_from_feed_token).to eq user + end + + it 'returns nil if rss_token is blank' do + expect(find_user_from_feed_token).to be_nil + end + + it 'returns exception if invalid rss_token' do + set_param(:rss_token, 'invalid_token') + + expect { find_user_from_feed_token }.to raise_error(Gitlab::Auth::UnauthorizedError) + end end end context 'when the request format is not atom' do it 'returns nil' do - set_param(:rss_token, user.rss_token) + set_param(:feed_token, user.feed_token) - expect(find_user_from_rss_token).to be_nil + expect(find_user_from_feed_token).to be_nil end end @@ -81,7 +101,7 @@ describe Gitlab::Auth::UserAuthFinders do it 'the method call does not modify the original value' do env['action_dispatch.request.formats'] = nil - find_user_from_rss_token + find_user_from_feed_token expect(env['action_dispatch.request.formats']).to be_nil end diff --git a/spec/lib/gitlab/bitbucket_import/importer_spec.rb b/spec/lib/gitlab/bitbucket_import/importer_spec.rb index c63120b0b29..05c232d22cf 100644 --- a/spec/lib/gitlab/bitbucket_import/importer_spec.rb +++ b/spec/lib/gitlab/bitbucket_import/importer_spec.rb @@ -19,6 +19,18 @@ describe Gitlab::BitbucketImport::Importer do ] end + let(:reporters) do + [ + nil, + { "username" => "reporter1" }, + nil, + { "username" => "reporter2" }, + { "username" => "reporter1" }, + nil, + { "username" => "reporter3" } + ] + end + let(:sample_issues_statuses) do issues = [] @@ -36,6 +48,10 @@ describe Gitlab::BitbucketImport::Importer do } end + reporters.map.with_index do |reporter, index| + issues[index]['reporter'] = reporter + end + issues end @@ -147,5 +163,19 @@ describe Gitlab::BitbucketImport::Importer do expect(importer.errors).to be_empty end end + + describe 'issue import' do + it 'maps reporters to anonymous if bitbucket reporter is nil' do + allow(importer).to receive(:import_wiki) + importer.execute + + expect(project.issues.size).to eq(7) + expect(project.issues.where("description LIKE ?", '%Anonymous%').size).to eq(3) + expect(project.issues.where("description LIKE ?", '%reporter1%').size).to eq(2) + expect(project.issues.where("description LIKE ?", '%reporter2%').size).to eq(1) + expect(project.issues.where("description LIKE ?", '%reporter3%').size).to eq(1) + expect(importer.errors).to be_empty + end + end end end diff --git a/spec/lib/gitlab/git/blob_spec.rb b/spec/lib/gitlab/git/blob_spec.rb index e2547ed0311..94eaf86ef80 100644 --- a/spec/lib/gitlab/git/blob_spec.rb +++ b/spec/lib/gitlab/git/blob_spec.rb @@ -22,6 +22,12 @@ describe Gitlab::Git::Blob, seed_helper: true do it { expect(blob).to eq(nil) } end + context 'utf-8 branch' do + let(:blob) { Gitlab::Git::Blob.find(repository, 'Ääh-test-utf-8', "files/ruby/popen.rb")} + + it { expect(blob.id).to eq(SeedRepo::RubyBlob::ID) } + end + context 'blank path' do let(:blob) { Gitlab::Git::Blob.find(repository, SeedRepo::Commit::ID, '') } diff --git a/spec/lib/gitlab/git/commit_spec.rb b/spec/lib/gitlab/git/commit_spec.rb index 08c6d1e55e9..89be8a1b7f2 100644 --- a/spec/lib/gitlab/git/commit_spec.rb +++ b/spec/lib/gitlab/git/commit_spec.rb @@ -603,8 +603,8 @@ describe Gitlab::Git::Commit, seed_helper: true do let(:commit) { described_class.find(repository, 'master') } subject { commit.ref_names(repository) } - it 'has 1 element' do - expect(subject.size).to eq(1) + it 'has 2 element' do + expect(subject.size).to eq(2) end it { is_expected.to include("master") } it { is_expected.not_to include("feature") } diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb index af6a486ab20..dd5c498706d 100644 --- a/spec/lib/gitlab/git/repository_spec.rb +++ b/spec/lib/gitlab/git/repository_spec.rb @@ -1374,7 +1374,7 @@ describe Gitlab::Git::Repository, seed_helper: true do describe '#branch_count' do it 'returns the number of branches' do - expect(repository.branch_count).to eq(10) + expect(repository.branch_count).to eq(11) end context 'with local and remote branches' do @@ -2248,7 +2248,7 @@ describe Gitlab::Git::Repository, seed_helper: true do describe '#checksum' do it 'calculates the checksum for non-empty repo' do - expect(repository.checksum).to eq '54f21be4c32c02f6788d72207fa03ad3bce725e4' + expect(repository.checksum).to eq '4be7d24ce7e8d845502d599b72d567d23e6a40c0' end it 'returns 0000000000000000000000000000000000000000 for an empty repo' do diff --git a/spec/lib/gitlab/gitlab_import/importer_spec.rb b/spec/lib/gitlab/gitlab_import/importer_spec.rb index e1d935602b5..200edceca8c 100644 --- a/spec/lib/gitlab/gitlab_import/importer_spec.rb +++ b/spec/lib/gitlab/gitlab_import/importer_spec.rb @@ -20,7 +20,7 @@ describe Gitlab::GitlabImport::Importer do } } ]) - stub_request('issues/2579857/notes', []) + stub_request('issues/3/notes', []) end it 'persists issues' do @@ -43,7 +43,7 @@ describe Gitlab::GitlabImport::Importer do end def stub_request(path, body) - url = "https://gitlab.com/api/v3/projects/asd%2Fvim/#{path}?page=1&per_page=100" + url = "https://gitlab.com/api/v4/projects/asd%2Fvim/#{path}?page=1&per_page=100" WebMock.stub_request(:get, url) .to_return( diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml index fb5fd300dbb..a129855dbd8 100644 --- a/spec/lib/gitlab/import_export/all_models.yml +++ b/spec/lib/gitlab/import_export/all_models.yml @@ -310,3 +310,8 @@ lfs_file_locks: - user project_badges: - project +metrics: +- merge_request +- latest_closed_by +- merged_by +- pipeline diff --git a/spec/lib/gitlab/import_export/relation_factory_spec.rb b/spec/lib/gitlab/import_export/relation_factory_spec.rb index 5f0dfd64b15..cf9e0f71910 100644 --- a/spec/lib/gitlab/import_export/relation_factory_spec.rb +++ b/spec/lib/gitlab/import_export/relation_factory_spec.rb @@ -119,6 +119,25 @@ describe Gitlab::ImportExport::RelationFactory do end end + context 'overrided model with pluralized name' do + let(:relation_sym) { :metrics } + + let(:relation_hash) do + { + 'id' => 99, + 'merge_request_id' => 99, + 'merged_at' => Time.now, + 'merged_by_id' => 99, + 'latest_closed_at' => nil, + 'latest_closed_by_id' => nil + } + end + + it 'does not raise errors' do + expect { created_object }.not_to raise_error + end + end + context 'Project references' do let(:relation_sym) { :project_foo_model } let(:relation_hash) do diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml index 3d5271cd030..74e7a45fd6c 100644 --- a/spec/lib/gitlab/import_export/safe_model_attributes.yml +++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml @@ -205,6 +205,19 @@ MergeRequestDiffFile: - b_mode - too_large - binary +MergeRequest::Metrics: +- id +- created_at +- updated_at +- merge_request_id +- pipeline_id +- latest_closed_by_id +- latest_closed_at +- merged_by_id +- merged_at +- latest_build_started_at +- latest_build_finished_at +- first_deployed_to_production_at Ci::Pipeline: - id - project_id diff --git a/spec/lib/mattermost/command_spec.rb b/spec/lib/mattermost/command_spec.rb index 8ba15ae0f38..7c194749dfb 100644 --- a/spec/lib/mattermost/command_spec.rb +++ b/spec/lib/mattermost/command_spec.rb @@ -21,13 +21,13 @@ describe Mattermost::Command do context 'for valid trigger word' do before do - stub_request(:post, 'http://mattermost.example.com/api/v3/teams/abc/commands/create') + stub_request(:post, 'http://mattermost.example.com/api/v4/commands') .with(body: { team_id: 'abc', trigger: 'gitlab' }.to_json) .to_return( - status: 200, + status: 201, headers: { 'Content-Type' => 'application/json' }, body: { token: 'token' }.to_json ) @@ -40,16 +40,16 @@ describe Mattermost::Command do context 'for error message' do before do - stub_request(:post, 'http://mattermost.example.com/api/v3/teams/abc/commands/create') + stub_request(:post, 'http://mattermost.example.com/api/v4/commands') .to_return( - status: 500, + status: 400, headers: { 'Content-Type' => 'application/json' }, body: { id: 'api.command.duplicate_trigger.app_error', message: 'This trigger word is already in use. Please choose another word.', detailed_error: '', request_id: 'obc374man7bx5r3dbc1q5qhf3r', - status_code: 500 + status_code: 400 }.to_json ) end diff --git a/spec/lib/mattermost/session_spec.rb b/spec/lib/mattermost/session_spec.rb index c855643c4d8..5410bfbeb31 100644 --- a/spec/lib/mattermost/session_spec.rb +++ b/spec/lib/mattermost/session_spec.rb @@ -22,8 +22,8 @@ describe Mattermost::Session, type: :request do let(:location) { 'http://location.tld' } let(:cookie_header) {'MMOAUTH=taskik8az7rq8k6rkpuas7htia; Path=/;'} let!(:stub) do - WebMock.stub_request(:get, "#{mattermost_url}/api/v3/oauth/gitlab/login") - .to_return(headers: { 'location' => location, 'Set-Cookie' => cookie_header }, status: 307) + WebMock.stub_request(:get, "#{mattermost_url}/oauth/gitlab/login") + .to_return(headers: { 'location' => location, 'Set-Cookie' => cookie_header }, status: 302) end context 'without oauth uri' do @@ -76,7 +76,7 @@ describe Mattermost::Session, type: :request do end end - WebMock.stub_request(:post, "#{mattermost_url}/api/v3/users/logout") + WebMock.stub_request(:post, "#{mattermost_url}/api/v4/users/logout") .to_return(headers: { Authorization: 'token thisworksnow' }, status: 200) end diff --git a/spec/lib/mattermost/team_spec.rb b/spec/lib/mattermost/team_spec.rb index 2cfa6802612..030aa5d06a8 100644 --- a/spec/lib/mattermost/team_spec.rb +++ b/spec/lib/mattermost/team_spec.rb @@ -12,26 +12,28 @@ describe Mattermost::Team do describe '#all' do subject { described_class.new(nil).all } + let(:test_team) do + { + "id" => "xiyro8huptfhdndadpz8r3wnbo", + "create_at" => 1482174222155, + "update_at" => 1482174222155, + "delete_at" => 0, + "display_name" => "chatops", + "name" => "chatops", + "email" => "admin@example.com", + "type" => "O", + "company_name" => "", + "allowed_domains" => "", + "invite_id" => "o4utakb9jtb7imctdfzbf9r5ro", + "allow_open_invite" => false + } + end + context 'for valid request' do - let(:response) do - { "xiyro8huptfhdndadpz8r3wnbo" => { - "id" => "xiyro8huptfhdndadpz8r3wnbo", - "create_at" => 1482174222155, - "update_at" => 1482174222155, - "delete_at" => 0, - "display_name" => "chatops", - "name" => "chatops", - "email" => "admin@example.com", - "type" => "O", - "company_name" => "", - "allowed_domains" => "", - "invite_id" => "o4utakb9jtb7imctdfzbf9r5ro", - "allow_open_invite" => false - } } - end + let(:response) { [test_team] } before do - stub_request(:get, 'http://mattermost.example.com/api/v3/teams/all') + stub_request(:get, 'http://mattermost.example.com/api/v4/users/me/teams') .to_return( status: 200, headers: { 'Content-Type' => 'application/json' }, @@ -39,14 +41,14 @@ describe Mattermost::Team do ) end - it 'returns a token' do - is_expected.to eq(response.values) + it 'returns teams' do + is_expected.to eq(response) end end context 'for error message' do before do - stub_request(:get, 'http://mattermost.example.com/api/v3/teams/all') + stub_request(:get, 'http://mattermost.example.com/api/v4/users/me/teams') .to_return( status: 500, headers: { 'Content-Type' => 'application/json' }, @@ -89,9 +91,9 @@ describe Mattermost::Team do end before do - stub_request(:post, "http://mattermost.example.com/api/v3/teams/create") + stub_request(:post, "http://mattermost.example.com/api/v4/teams") .to_return( - status: 200, + status: 201, body: response.to_json, headers: { 'Content-Type' => 'application/json' } ) @@ -104,7 +106,7 @@ describe Mattermost::Team do context 'for existing team' do before do - stub_request(:post, 'http://mattermost.example.com/api/v3/teams/create') + stub_request(:post, 'http://mattermost.example.com/api/v4/teams') .to_return( status: 400, headers: { 'Content-Type' => 'application/json' }, diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb index f310a6854d5..69eafbe4bbe 100644 --- a/spec/mailers/notify_spec.rb +++ b/spec/mailers/notify_spec.rb @@ -422,7 +422,7 @@ describe Notify do aggregate_failures do is_expected.to have_referable_subject(merge_request, reply: true) is_expected.to have_body_text(project_merge_request_path(project, merge_request)) - is_expected.to have_body_text('reasons:') + is_expected.to have_body_text('following reasons:') reasons.each do |reason| is_expected.to have_body_text(reason) end diff --git a/spec/migrations/migrate_remaining_mr_metrics_populating_background_migration_spec.rb b/spec/migrations/migrate_remaining_mr_metrics_populating_background_migration_spec.rb new file mode 100644 index 00000000000..47dab18183c --- /dev/null +++ b/spec/migrations/migrate_remaining_mr_metrics_populating_background_migration_spec.rb @@ -0,0 +1,36 @@ +require 'spec_helper' +require Rails.root.join('db', 'post_migrate', '20180521162137_migrate_remaining_mr_metrics_populating_background_migration.rb') + +describe MigrateRemainingMrMetricsPopulatingBackgroundMigration, :migration, :sidekiq do + let(:namespaces) { table(:namespaces) } + let(:projects) { table(:projects) } + let(:mrs) { table(:merge_requests) } + + before do + namespaces.create!(id: 1, name: 'foo', path: 'foo') + projects.create!(id: 123, name: 'gitlab1', path: 'gitlab1', namespace_id: 1) + projects.create!(id: 456, name: 'gitlab2', path: 'gitlab2', namespace_id: 1) + projects.create!(id: 789, name: 'gitlab3', path: 'gitlab3', namespace_id: 1) + mrs.create!(title: 'foo', target_branch: 'target', source_branch: 'source', target_project_id: 123) + mrs.create!(title: 'bar', target_branch: 'target', source_branch: 'source', target_project_id: 456) + mrs.create!(title: 'kux', target_branch: 'target', source_branch: 'source', target_project_id: 789) + end + + it 'correctly schedules background migrations' do + stub_const("#{described_class.name}::BATCH_SIZE", 2) + + Sidekiq::Testing.fake! do + Timecop.freeze do + migrate! + + expect(described_class::MIGRATION) + .to be_scheduled_delayed_migration(10.minutes, mrs.first.id, mrs.second.id) + + expect(described_class::MIGRATION) + .to be_scheduled_delayed_migration(20.minutes, mrs.third.id, mrs.third.id) + + expect(BackgroundMigrationWorker.jobs.size).to eq(2) + end + end + end +end diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb index f8f07205623..968267a6d24 100644 --- a/spec/models/application_setting_spec.rb +++ b/spec/models/application_setting_spec.rb @@ -195,22 +195,6 @@ describe ApplicationSetting do expect(setting.pick_repository_storage).to eq('random') end - - describe '#repository_storage' do - it 'returns the first storage' do - setting.repository_storages = %w(good bad) - - expect(setting.repository_storage).to eq('good') - end - end - - describe '#repository_storage=' do - it 'overwrites repository_storages' do - setting.repository_storage = 'overwritten' - - expect(setting.repository_storages).to eq(['overwritten']) - end - end end end diff --git a/spec/models/clusters/applications/jupyter_spec.rb b/spec/models/clusters/applications/jupyter_spec.rb new file mode 100644 index 00000000000..ca48a1d8072 --- /dev/null +++ b/spec/models/clusters/applications/jupyter_spec.rb @@ -0,0 +1,59 @@ +require 'rails_helper' + +describe Clusters::Applications::Jupyter do + include_examples 'cluster application core specs', :clusters_applications_jupyter + + it { is_expected.to belong_to(:oauth_application) } + + describe '#set_initial_status' do + before do + jupyter.set_initial_status + end + + context 'when ingress is not installed' do + let(:cluster) { create(:cluster, :provided_by_gcp) } + let(:jupyter) { create(:clusters_applications_jupyter, cluster: cluster) } + + it { expect(jupyter).to be_not_installable } + end + + context 'when ingress is installed and external_ip is assigned' do + let(:ingress) { create(:clusters_applications_ingress, :installed, external_ip: '127.0.0.1') } + let(:jupyter) { create(:clusters_applications_jupyter, cluster: ingress.cluster) } + + it { expect(jupyter).to be_installable } + end + end + + describe '#install_command' do + let!(:ingress) { create(:clusters_applications_ingress, :installed, external_ip: '127.0.0.1') } + let!(:jupyter) { create(:clusters_applications_jupyter, cluster: ingress.cluster) } + + subject { jupyter.install_command } + + it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::Helm::InstallCommand) } + + it 'should be initialized with 4 arguments' do + expect(subject.name).to eq('jupyter') + expect(subject.chart).to eq('jupyter/jupyterhub') + expect(subject.repository).to eq('https://jupyterhub.github.io/helm-chart/') + expect(subject.values).to eq(jupyter.values) + end + end + + describe '#values' do + let(:jupyter) { create(:clusters_applications_jupyter) } + + subject { jupyter.values } + + it 'should include valid values' do + is_expected.to include('ingress') + is_expected.to include('hub') + is_expected.to include('rbac') + is_expected.to include('proxy') + is_expected.to include('auth') + is_expected.to include("clientId: #{jupyter.oauth_application.uid}") + is_expected.to include("callbackUrl: #{jupyter.callback_url}") + end + end +end diff --git a/spec/models/clusters/cluster_spec.rb b/spec/models/clusters/cluster_spec.rb index b942554d67b..6f66515b45f 100644 --- a/spec/models/clusters/cluster_spec.rb +++ b/spec/models/clusters/cluster_spec.rb @@ -234,9 +234,10 @@ describe Clusters::Cluster do let!(:ingress) { create(:clusters_applications_ingress, cluster: cluster) } let!(:prometheus) { create(:clusters_applications_prometheus, cluster: cluster) } let!(:runner) { create(:clusters_applications_runner, cluster: cluster) } + let!(:jupyter) { create(:clusters_applications_jupyter, cluster: cluster) } it 'returns a list of created applications' do - is_expected.to contain_exactly(helm, ingress, prometheus, runner) + is_expected.to contain_exactly(helm, ingress, prometheus, runner, jupyter) end end end diff --git a/spec/models/concerns/token_authenticatable_spec.rb b/spec/models/concerns/token_authenticatable_spec.rb index dfb83578fce..9b804429138 100644 --- a/spec/models/concerns/token_authenticatable_spec.rb +++ b/spec/models/concerns/token_authenticatable_spec.rb @@ -12,7 +12,7 @@ shared_examples 'TokenAuthenticatable' do end describe User, 'TokenAuthenticatable' do - let(:token_field) { :rss_token } + let(:token_field) { :feed_token } it_behaves_like 'TokenAuthenticatable' describe 'ensures authentication token' do diff --git a/spec/models/project_services/kubernetes_service_spec.rb b/spec/models/project_services/kubernetes_service_spec.rb index 3be023a48c1..68ab9fd08ec 100644 --- a/spec/models/project_services/kubernetes_service_spec.rb +++ b/spec/models/project_services/kubernetes_service_spec.rb @@ -65,7 +65,7 @@ describe KubernetesService, :use_clean_rails_memory_store_caching do before do kubernetes_service.update_attribute(:active, false) - kubernetes_service.properties[:namespace] = "foo" + kubernetes_service.properties['namespace'] = "foo" end it 'should not update attributes' do @@ -82,7 +82,7 @@ describe KubernetesService, :use_clean_rails_memory_store_caching do let(:kubernetes_service) { create(:kubernetes_service) } it 'should update attributes' do - kubernetes_service.properties[:namespace] = 'foo' + kubernetes_service.properties['namespace'] = 'foo' expect(kubernetes_service.save).to be_truthy end end @@ -92,7 +92,7 @@ describe KubernetesService, :use_clean_rails_memory_store_caching do before do kubernetes_service.active = false - kubernetes_service.properties[:namespace] = 'foo' + kubernetes_service.properties['namespace'] = 'foo' kubernetes_service.save end @@ -105,7 +105,7 @@ describe KubernetesService, :use_clean_rails_memory_store_caching do end it 'should update attributes' do - expect(kubernetes_service.properties[:namespace]).to eq("foo") + expect(kubernetes_service.properties['namespace']).to eq("foo") end end @@ -113,12 +113,12 @@ describe KubernetesService, :use_clean_rails_memory_store_caching do let(:kubernetes_service) { create(:kubernetes_service, template: true, active: false) } before do - kubernetes_service.properties[:namespace] = 'foo' + kubernetes_service.properties['namespace'] = 'foo' end it 'should update attributes' do expect(kubernetes_service.save).to be_truthy - expect(kubernetes_service.properties[:namespace]).to eq('foo') + expect(kubernetes_service.properties['namespace']).to eq('foo') end end end diff --git a/spec/models/project_services/mattermost_slash_commands_service_spec.rb b/spec/models/project_services/mattermost_slash_commands_service_spec.rb index 05d33cd3874..1983e0cc967 100644 --- a/spec/models/project_services/mattermost_slash_commands_service_spec.rb +++ b/spec/models/project_services/mattermost_slash_commands_service_spec.rb @@ -25,7 +25,7 @@ describe MattermostSlashCommandsService do context 'the requests succeeds' do before do - stub_request(:post, 'http://mattermost.example.com/api/v3/teams/abc/commands/create') + stub_request(:post, 'http://mattermost.example.com/api/v4/commands') .with(body: { team_id: 'abc', trigger: 'gitlab', @@ -59,7 +59,7 @@ describe MattermostSlashCommandsService do context 'an error is received' do before do - stub_request(:post, 'http://mattermost.example.com/api/v3/teams/abc/commands/create') + stub_request(:post, 'http://mattermost.example.com/api/v4/commands') .to_return( status: 500, headers: { 'Content-Type' => 'application/json' }, @@ -89,11 +89,11 @@ describe MattermostSlashCommandsService do context 'the requests succeeds' do before do - stub_request(:get, 'http://mattermost.example.com/api/v3/teams/all') + stub_request(:get, 'http://mattermost.example.com/api/v4/users/me/teams') .to_return( status: 200, headers: { 'Content-Type' => 'application/json' }, - body: { 'list' => true }.to_json + body: [{ id: 'test_team_id' }].to_json ) end @@ -104,7 +104,7 @@ describe MattermostSlashCommandsService do context 'an error is received' do before do - stub_request(:get, 'http://mattermost.example.com/api/v3/teams/all') + stub_request(:get, 'http://mattermost.example.com/api/v4/users/me/teams') .to_return( status: 500, headers: { 'Content-Type' => 'application/json' }, diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 6a2f4a39f09..16b409844fa 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -644,13 +644,13 @@ describe User do end end - describe 'rss token' do - it 'ensures an rss token on read' do - user = create(:user, rss_token: nil) - rss_token = user.rss_token + describe 'feed token' do + it 'ensures a feed token on read' do + user = create(:user, feed_token: nil) + feed_token = user.feed_token - expect(rss_token).not_to be_blank - expect(user.reload.rss_token).to eq rss_token + expect(feed_token).not_to be_blank + expect(user.reload.feed_token).to eq feed_token end end diff --git a/spec/presenters/ci/build_presenter_spec.rb b/spec/presenters/ci/build_presenter_spec.rb index efd175247b5..6dfaa3b72f7 100644 --- a/spec/presenters/ci/build_presenter_spec.rb +++ b/spec/presenters/ci/build_presenter_spec.rb @@ -219,11 +219,11 @@ describe Ci::BuildPresenter do end describe '#callout_failure_message' do - let(:build) { create(:ci_build, :failed, :script_failure) } + let(:build) { create(:ci_build, :failed, :api_failure) } it 'returns a verbose failure reason' do description = subject.callout_failure_message - expect(description).to eq('There has been a script failure. Check the job log for more information') + expect(description).to eq('There has been an API failure, please try again') end end diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb index 6b91e48ae6a..605761867bf 100644 --- a/spec/requests/api/merge_requests_spec.rb +++ b/spec/requests/api/merge_requests_spec.rb @@ -29,6 +29,18 @@ describe API::MergeRequests do project.add_reporter(user) end + describe 'route shadowing' do + include GrapePathHelpers::NamedRouteMatcher + + it 'does not occur' do + path = api_v4_projects_merge_requests_path(id: 1) + expect(path).to eq('/api/v4/projects/1/merge_requests') + + path = api_v4_projects_merge_requests_path(id: 1, merge_request_iid: 3) + expect(path).to eq('/api/v4/projects/1/merge_requests/3') + end + end + describe 'GET /merge_requests' do context 'when unauthenticated' do it 'returns an array of all merge requests' do @@ -60,12 +72,6 @@ describe API::MergeRequests do expect(response).to have_gitlab_http_status(401) end - - it "returns authentication error when scope is created_by_me" do - get api("/merge_requests"), scope: 'created_by_me' - - expect(response).to have_gitlab_http_status(401) - end end context 'when authenticated' do @@ -217,224 +223,46 @@ describe API::MergeRequests do end describe "GET /projects/:id/merge_requests" do - context "when unauthenticated" do - it 'returns merge requests for public projects' do - get api("/projects/#{project.id}/merge_requests") - - expect_paginated_array_response - end - - it "returns 404 for non public projects" do - project = create(:project, :private) - get api("/projects/#{project.id}/merge_requests") - - expect(response).to have_gitlab_http_status(404) - end - end - - context "when authenticated" do - it 'avoids N+1 queries' do - control = ActiveRecord::QueryRecorder.new do - get api("/projects/#{project.id}/merge_requests", user) - end - - create(:merge_request, state: 'closed', milestone: milestone1, author: user, assignee: user, source_project: project, target_project: project, title: "Test", created_at: base_time) - - create(:merge_request, milestone: milestone1, author: user, assignee: user, source_project: project, target_project: project, title: "Test", created_at: base_time) - - expect do - get api("/projects/#{project.id}/merge_requests", user) - end.not_to exceed_query_limit(control) - end - - it "returns an array of all merge_requests" do - get api("/projects/#{project.id}/merge_requests", user) - - expect_response_ordered_exactly(merge_request_merged, merge_request_closed, merge_request) - expect(json_response.last['title']).to eq(merge_request.title) - expect(json_response.last).to have_key('web_url') - expect(json_response.last['sha']).to eq(merge_request.diff_head_sha) - expect(json_response.last['merge_commit_sha']).to be_nil - expect(json_response.last['merge_commit_sha']).to eq(merge_request.merge_commit_sha) - expect(json_response.last['downvotes']).to eq(1) - expect(json_response.last['upvotes']).to eq(1) - expect(json_response.last['labels']).to eq([label2.title, label.title]) - expect(json_response.first['title']).to eq(merge_request_merged.title) - expect(json_response.first['sha']).to eq(merge_request_merged.diff_head_sha) - expect(json_response.first['merge_commit_sha']).not_to be_nil - expect(json_response.first['merge_commit_sha']).to eq(merge_request_merged.merge_commit_sha) - expect(json_response.first['squash']).to eq(merge_request_merged.squash) - end - - it "returns an array of all merge_requests using simple mode" do - get api("/projects/#{project.id}/merge_requests?view=simple", user) - - expect_response_ordered_exactly(merge_request_merged, merge_request_closed, merge_request) - expect(json_response.last.keys).to match_array(%w(id iid title web_url created_at description project_id state updated_at)) - expect(json_response.last['iid']).to eq(merge_request.iid) - expect(json_response.last['title']).to eq(merge_request.title) - expect(json_response.last).to have_key('web_url') - expect(json_response.first['iid']).to eq(merge_request_merged.iid) - expect(json_response.first['title']).to eq(merge_request_merged.title) - expect(json_response.first).to have_key('web_url') - end - - it "returns an array of all merge_requests" do - get api("/projects/#{project.id}/merge_requests?state", user) - - expect_response_ordered_exactly(merge_request_merged, merge_request_closed, merge_request) - expect(json_response.last['title']).to eq(merge_request.title) - end - - it "returns an array of open merge_requests" do - get api("/projects/#{project.id}/merge_requests?state=opened", user) - - expect_response_ordered_exactly(merge_request) - expect(json_response.last['title']).to eq(merge_request.title) - end - - it "returns an array of closed merge_requests" do - get api("/projects/#{project.id}/merge_requests?state=closed", user) - - expect_response_ordered_exactly(merge_request_closed) - expect(json_response.first['title']).to eq(merge_request_closed.title) - end - - it "returns an array of merged merge_requests" do - get api("/projects/#{project.id}/merge_requests?state=merged", user) - - expect_response_ordered_exactly(merge_request_merged) - expect(json_response.first['title']).to eq(merge_request_merged.title) - end - - it 'returns merge_request by "iids" array' do - get api("/projects/#{project.id}/merge_requests", user), iids: [merge_request.iid, merge_request_closed.iid] - - expect_response_ordered_exactly(merge_request_closed, merge_request) - expect(json_response.first['title']).to eq merge_request_closed.title - end - - it 'matches V4 response schema' do - get api("/projects/#{project.id}/merge_requests", user) - - expect(response).to have_gitlab_http_status(200) - expect(response).to match_response_schema('public_api/v4/merge_requests') - end - - it 'returns an empty array if no issue matches milestone' do - get api("/projects/#{project.id}/merge_requests", user), milestone: '1.0.0' - - expect_paginated_array_response - expect(json_response.length).to eq(0) - end - - it 'returns an empty array if milestone does not exist' do - get api("/projects/#{project.id}/merge_requests", user), milestone: 'foo' - - expect_paginated_array_response - expect(json_response.length).to eq(0) - end - - it 'returns an array of merge requests in given milestone' do - get api("/projects/#{project.id}/merge_requests", user), milestone: '0.9' + let(:endpoint_path) { "/projects/#{project.id}/merge_requests" } - expect(json_response.first['title']).to eq merge_request_closed.title - expect(json_response.first['id']).to eq merge_request_closed.id - end - - it 'returns an array of merge requests matching state in milestone' do - get api("/projects/#{project.id}/merge_requests", user), milestone: '0.9', state: 'closed' - - expect_response_ordered_exactly(merge_request_closed) - end - - it 'returns an array of labeled merge requests' do - get api("/projects/#{project.id}/merge_requests?labels=#{label.title}", user) - - expect_paginated_array_response - expect(json_response.length).to eq(1) - expect(json_response.first['labels']).to eq([label2.title, label.title]) - end - - it 'returns an array of labeled merge requests where all labels match' do - get api("/projects/#{project.id}/merge_requests?labels=#{label.title},foo,bar", user) - - expect_paginated_array_response - expect(json_response.length).to eq(0) - end - - it 'returns an empty array if no merge request matches labels' do - get api("/projects/#{project.id}/merge_requests?labels=foo,bar", user) - - expect_paginated_array_response - expect(json_response.length).to eq(0) - end - - it 'returns an array of labeled merge requests that are merged for a milestone' do - bug_label = create(:label, title: 'bug', color: '#FFAABB', project: project) - - mr1 = create(:merge_request, state: "merged", source_project: project, target_project: project, milestone: milestone) - mr2 = create(:merge_request, state: "merged", source_project: project, target_project: project, milestone: milestone1) - mr3 = create(:merge_request, state: "closed", source_project: project, target_project: project, milestone: milestone1) - _mr = create(:merge_request, state: "merged", source_project: project, target_project: project, milestone: milestone1) - - create(:label_link, label: bug_label, target: mr1) - create(:label_link, label: bug_label, target: mr2) - create(:label_link, label: bug_label, target: mr3) - - get api("/projects/#{project.id}/merge_requests?labels=#{bug_label.title}&milestone=#{milestone1.title}&state=merged", user) - - expect_response_ordered_exactly(mr2) - end - - context "with ordering" do - let(:merge_requests) { [merge_request_merged, merge_request_closed, merge_request] } + it_behaves_like 'merge requests list' - before do - @mr_later = mr_with_later_created_and_updated_at_time - @mr_earlier = mr_with_earlier_created_and_updated_at_time - end - - it "returns an array of merge_requests in ascending order" do - get api("/projects/#{project.id}/merge_requests?sort=asc", user) - - expect_response_ordered_exactly(*merge_requests.sort_by { |mr| mr['created_at'] }) - end - - it "returns an array of merge_requests in descending order" do - get api("/projects/#{project.id}/merge_requests?sort=desc", user) + it "returns 404 for non public projects" do + project = create(:project, :private) - expect_response_ordered_exactly(*merge_requests.sort_by { |mr| mr['created_at'] }.reverse) - end + get api("/projects/#{project.id}/merge_requests") - it "returns an array of merge_requests ordered by updated_at" do - get api("/projects/#{project.id}/merge_requests?order_by=updated_at", user) + expect(response).to have_gitlab_http_status(404) + end - expect_response_ordered_exactly(*merge_requests.sort_by { |mr| mr['updated_at'] }.reverse) - end + it 'returns merge_request by "iids" array' do + get api(endpoint_path, user), iids: [merge_request.iid, merge_request_closed.iid] - it "returns an array of merge_requests ordered by created_at" do - get api("/projects/#{project.id}/merge_requests?order_by=created_at&sort=asc", user) + expect(response).to have_gitlab_http_status(200) + expect(json_response).to be_an Array + expect(json_response.length).to eq(2) + expect(json_response.first['title']).to eq merge_request_closed.title + expect(json_response.first['id']).to eq merge_request_closed.id + end + end - expect_response_ordered_exactly(*merge_requests.sort_by { |mr| mr['created_at'] }) - end - end + describe "GET /groups/:id/merge_requests" do + let!(:group) { create(:group, :public) } + let!(:project) { create(:project, :public, :repository, creator: user, namespace: group, only_allow_merge_if_pipeline_succeeds: false) } + let(:endpoint_path) { "/groups/#{group.id}/merge_requests" } - context 'source_branch param' do - it 'returns merge requests with the given source branch' do - get api('/merge_requests', user), source_branch: merge_request_closed.source_branch, state: 'all' + before do + group.add_reporter(user) + end - expect_response_contain_exactly(merge_request_closed, merge_request_merged) - end - end + it_behaves_like 'merge requests list' - context 'target_branch param' do - it 'returns merge requests with the given target branch' do - get api('/merge_requests', user), target_branch: merge_request_closed.target_branch, state: 'all' + context 'when have subgroups', :nested_groups do + let!(:group) { create(:group, :public) } + let!(:subgroup) { create(:group, parent: group) } + let!(:project) { create(:project, :public, :repository, creator: user, namespace: subgroup, only_allow_merge_if_pipeline_succeeds: false) } - expect_response_contain_exactly(merge_request_closed, merge_request_merged) - end - end + it_behaves_like 'merge requests list' end end diff --git a/spec/requests/api/v3/award_emoji_spec.rb b/spec/requests/api/v3/award_emoji_spec.rb deleted file mode 100644 index 6dc430676b0..00000000000 --- a/spec/requests/api/v3/award_emoji_spec.rb +++ /dev/null @@ -1,297 +0,0 @@ -require 'spec_helper' - -describe API::V3::AwardEmoji do - set(:user) { create(:user) } - set(:project) { create(:project) } - set(:issue) { create(:issue, project: project) } - set(:award_emoji) { create(:award_emoji, awardable: issue, user: user) } - let!(:merge_request) { create(:merge_request, source_project: project, target_project: project) } - let!(:downvote) { create(:award_emoji, :downvote, awardable: merge_request, user: user) } - set(:note) { create(:note, project: project, noteable: issue) } - - before { project.add_master(user) } - - describe "GET /projects/:id/awardable/:awardable_id/award_emoji" do - context 'on an issue' do - it "returns an array of award_emoji" do - get v3_api("/projects/#{project.id}/issues/#{issue.id}/award_emoji", user) - - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.first['name']).to eq(award_emoji.name) - end - - it "returns a 404 error when issue id not found" do - get v3_api("/projects/#{project.id}/issues/12345/award_emoji", user) - - expect(response).to have_gitlab_http_status(404) - end - end - - context 'on a merge request' do - it "returns an array of award_emoji" do - get v3_api("/projects/#{project.id}/merge_requests/#{merge_request.id}/award_emoji", user) - - expect(response).to have_gitlab_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array - expect(json_response.first['name']).to eq(downvote.name) - end - end - - context 'on a snippet' do - let(:snippet) { create(:project_snippet, :public, project: project) } - let!(:award) { create(:award_emoji, awardable: snippet) } - - it 'returns the awarded emoji' do - get v3_api("/projects/#{project.id}/snippets/#{snippet.id}/award_emoji", user) - - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.first['name']).to eq(award.name) - end - end - - context 'when the user has no access' do - it 'returns a status code 404' do - user1 = create(:user) - - get v3_api("/projects/#{project.id}/merge_requests/#{merge_request.id}/award_emoji", user1) - - expect(response).to have_gitlab_http_status(404) - end - end - end - - describe 'GET /projects/:id/awardable/:awardable_id/notes/:note_id/award_emoji' do - let!(:rocket) { create(:award_emoji, awardable: note, name: 'rocket') } - - it 'returns an array of award emoji' do - get v3_api("/projects/#{project.id}/issues/#{issue.id}/notes/#{note.id}/award_emoji", user) - - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.first['name']).to eq(rocket.name) - end - end - - describe "GET /projects/:id/awardable/:awardable_id/award_emoji/:award_id" do - context 'on an issue' do - it "returns the award emoji" do - get v3_api("/projects/#{project.id}/issues/#{issue.id}/award_emoji/#{award_emoji.id}", user) - - expect(response).to have_gitlab_http_status(200) - expect(json_response['name']).to eq(award_emoji.name) - expect(json_response['awardable_id']).to eq(issue.id) - expect(json_response['awardable_type']).to eq("Issue") - end - - it "returns a 404 error if the award is not found" do - get v3_api("/projects/#{project.id}/issues/#{issue.id}/award_emoji/12345", user) - - expect(response).to have_gitlab_http_status(404) - end - end - - context 'on a merge request' do - it 'returns the award emoji' do - get v3_api("/projects/#{project.id}/merge_requests/#{merge_request.id}/award_emoji/#{downvote.id}", user) - - expect(response).to have_gitlab_http_status(200) - expect(json_response['name']).to eq(downvote.name) - expect(json_response['awardable_id']).to eq(merge_request.id) - expect(json_response['awardable_type']).to eq("MergeRequest") - end - end - - context 'on a snippet' do - let(:snippet) { create(:project_snippet, :public, project: project) } - let!(:award) { create(:award_emoji, awardable: snippet) } - - it 'returns the awarded emoji' do - get v3_api("/projects/#{project.id}/snippets/#{snippet.id}/award_emoji/#{award.id}", user) - - expect(response).to have_gitlab_http_status(200) - expect(json_response['name']).to eq(award.name) - expect(json_response['awardable_id']).to eq(snippet.id) - expect(json_response['awardable_type']).to eq("Snippet") - end - end - - context 'when the user has no access' do - it 'returns a status code 404' do - user1 = create(:user) - - get v3_api("/projects/#{project.id}/merge_requests/#{merge_request.id}/award_emoji/#{downvote.id}", user1) - - expect(response).to have_gitlab_http_status(404) - end - end - end - - describe 'GET /projects/:id/awardable/:awardable_id/notes/:note_id/award_emoji/:award_id' do - let!(:rocket) { create(:award_emoji, awardable: note, name: 'rocket') } - - it 'returns an award emoji' do - get v3_api("/projects/#{project.id}/issues/#{issue.id}/notes/#{note.id}/award_emoji/#{rocket.id}", user) - - expect(response).to have_gitlab_http_status(200) - expect(json_response).not_to be_an Array - expect(json_response['name']).to eq(rocket.name) - end - end - - describe "POST /projects/:id/awardable/:awardable_id/award_emoji" do - let(:issue2) { create(:issue, project: project, author: user) } - - context "on an issue" do - it "creates a new award emoji" do - post v3_api("/projects/#{project.id}/issues/#{issue.id}/award_emoji", user), name: 'blowfish' - - expect(response).to have_gitlab_http_status(201) - expect(json_response['name']).to eq('blowfish') - expect(json_response['user']['username']).to eq(user.username) - end - - it "returns a 400 bad request error if the name is not given" do - post v3_api("/projects/#{project.id}/issues/#{issue.id}/award_emoji", user) - - expect(response).to have_gitlab_http_status(400) - end - - it "returns a 401 unauthorized error if the user is not authenticated" do - post v3_api("/projects/#{project.id}/issues/#{issue.id}/award_emoji"), name: 'thumbsup' - - expect(response).to have_gitlab_http_status(401) - end - - it "returns a 404 error if the user authored issue" do - post v3_api("/projects/#{project.id}/issues/#{issue2.id}/award_emoji", user), name: 'thumbsup' - - expect(response).to have_gitlab_http_status(404) - end - - it "normalizes +1 as thumbsup award" do - post v3_api("/projects/#{project.id}/issues/#{issue.id}/award_emoji", user), name: '+1' - - expect(issue.award_emoji.last.name).to eq("thumbsup") - end - - context 'when the emoji already has been awarded' do - it 'returns a 404 status code' do - post v3_api("/projects/#{project.id}/issues/#{issue.id}/award_emoji", user), name: 'thumbsup' - post v3_api("/projects/#{project.id}/issues/#{issue.id}/award_emoji", user), name: 'thumbsup' - - expect(response).to have_gitlab_http_status(404) - expect(json_response["message"]).to match("has already been taken") - end - end - end - - context 'on a snippet' do - it 'creates a new award emoji' do - snippet = create(:project_snippet, :public, project: project) - - post v3_api("/projects/#{project.id}/snippets/#{snippet.id}/award_emoji", user), name: 'blowfish' - - expect(response).to have_gitlab_http_status(201) - expect(json_response['name']).to eq('blowfish') - expect(json_response['user']['username']).to eq(user.username) - end - end - end - - describe "POST /projects/:id/awardable/:awardable_id/notes/:note_id/award_emoji" do - let(:note2) { create(:note, project: project, noteable: issue, author: user) } - - it 'creates a new award emoji' do - expect do - post v3_api("/projects/#{project.id}/issues/#{issue.id}/notes/#{note.id}/award_emoji", user), name: 'rocket' - end.to change { note.award_emoji.count }.from(0).to(1) - - expect(response).to have_gitlab_http_status(201) - expect(json_response['user']['username']).to eq(user.username) - end - - it "it returns 404 error when user authored note" do - post v3_api("/projects/#{project.id}/issues/#{issue.id}/notes/#{note2.id}/award_emoji", user), name: 'thumbsup' - - expect(response).to have_gitlab_http_status(404) - end - - it "normalizes +1 as thumbsup award" do - post v3_api("/projects/#{project.id}/issues/#{issue.id}/notes/#{note.id}/award_emoji", user), name: '+1' - - expect(note.award_emoji.last.name).to eq("thumbsup") - end - - context 'when the emoji already has been awarded' do - it 'returns a 404 status code' do - post v3_api("/projects/#{project.id}/issues/#{issue.id}/notes/#{note.id}/award_emoji", user), name: 'rocket' - post v3_api("/projects/#{project.id}/issues/#{issue.id}/notes/#{note.id}/award_emoji", user), name: 'rocket' - - expect(response).to have_gitlab_http_status(404) - expect(json_response["message"]).to match("has already been taken") - end - end - end - - describe 'DELETE /projects/:id/awardable/:awardable_id/award_emoji/:award_id' do - context 'when the awardable is an Issue' do - it 'deletes the award' do - expect do - delete v3_api("/projects/#{project.id}/issues/#{issue.id}/award_emoji/#{award_emoji.id}", user) - - expect(response).to have_gitlab_http_status(200) - end.to change { issue.award_emoji.count }.from(1).to(0) - end - - it 'returns a 404 error when the award emoji can not be found' do - delete v3_api("/projects/#{project.id}/issues/#{issue.id}/award_emoji/12345", user) - - expect(response).to have_gitlab_http_status(404) - end - end - - context 'when the awardable is a Merge Request' do - it 'deletes the award' do - expect do - delete v3_api("/projects/#{project.id}/merge_requests/#{merge_request.id}/award_emoji/#{downvote.id}", user) - - expect(response).to have_gitlab_http_status(200) - end.to change { merge_request.award_emoji.count }.from(1).to(0) - end - - it 'returns a 404 error when note id not found' do - delete v3_api("/projects/#{project.id}/merge_requests/#{merge_request.id}/notes/12345", user) - - expect(response).to have_gitlab_http_status(404) - end - end - - context 'when the awardable is a Snippet' do - let(:snippet) { create(:project_snippet, :public, project: project) } - let!(:award) { create(:award_emoji, awardable: snippet, user: user) } - - it 'deletes the award' do - expect do - delete v3_api("/projects/#{project.id}/snippets/#{snippet.id}/award_emoji/#{award.id}", user) - - expect(response).to have_gitlab_http_status(200) - end.to change { snippet.award_emoji.count }.from(1).to(0) - end - end - end - - describe 'DELETE /projects/:id/awardable/:awardable_id/award_emoji/:award_emoji_id' do - let!(:rocket) { create(:award_emoji, awardable: note, name: 'rocket', user: user) } - - it 'deletes the award' do - expect do - delete v3_api("/projects/#{project.id}/issues/#{issue.id}/notes/#{note.id}/award_emoji/#{rocket.id}", user) - - expect(response).to have_gitlab_http_status(200) - end.to change { note.award_emoji.count }.from(1).to(0) - end - end -end diff --git a/spec/requests/api/v3/boards_spec.rb b/spec/requests/api/v3/boards_spec.rb deleted file mode 100644 index dde4f096193..00000000000 --- a/spec/requests/api/v3/boards_spec.rb +++ /dev/null @@ -1,114 +0,0 @@ -require 'spec_helper' - -describe API::V3::Boards do - set(:user) { create(:user) } - set(:guest) { create(:user) } - set(:non_member) { create(:user) } - set(:project) { create(:project, :public, creator_id: user.id, namespace: user.namespace ) } - - set(:dev_label) do - create(:label, title: 'Development', color: '#FFAABB', project: project) - end - - set(:test_label) do - create(:label, title: 'Testing', color: '#FFAACC', project: project) - end - - set(:dev_list) do - create(:list, label: dev_label, position: 1) - end - - set(:test_list) do - create(:list, label: test_label, position: 2) - end - - set(:board) do - create(:board, project: project, lists: [dev_list, test_list]) - end - - before do - project.add_reporter(user) - project.add_guest(guest) - end - - describe "GET /projects/:id/boards" do - let(:base_url) { "/projects/#{project.id}/boards" } - - context "when unauthenticated" do - it "returns authentication error" do - get v3_api(base_url) - - expect(response).to have_gitlab_http_status(401) - end - end - - context "when authenticated" do - it "returns the project issue board" do - get v3_api(base_url, user) - - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.length).to eq(1) - expect(json_response.first['id']).to eq(board.id) - expect(json_response.first['lists']).to be_an Array - expect(json_response.first['lists'].length).to eq(2) - expect(json_response.first['lists'].last).to have_key('position') - end - end - end - - describe "GET /projects/:id/boards/:board_id/lists" do - let(:base_url) { "/projects/#{project.id}/boards/#{board.id}/lists" } - - it 'returns issue board lists' do - get v3_api(base_url, user) - - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.length).to eq(2) - expect(json_response.first['label']['name']).to eq(dev_label.title) - end - - it 'returns 404 if board not found' do - get v3_api("/projects/#{project.id}/boards/22343/lists", user) - - expect(response).to have_gitlab_http_status(404) - end - end - - describe "DELETE /projects/:id/board/lists/:list_id" do - let(:base_url) { "/projects/#{project.id}/boards/#{board.id}/lists" } - - it "rejects a non member from deleting a list" do - delete v3_api("#{base_url}/#{dev_list.id}", non_member) - - expect(response).to have_gitlab_http_status(403) - end - - it "rejects a user with guest role from deleting a list" do - delete v3_api("#{base_url}/#{dev_list.id}", guest) - - expect(response).to have_gitlab_http_status(403) - end - - it "returns 404 error if list id not found" do - delete v3_api("#{base_url}/44444", user) - - expect(response).to have_gitlab_http_status(404) - end - - context "when the user is project owner" do - set(:owner) { create(:user) } - - before do - project.update(namespace: owner.namespace) - end - - it "deletes the list if an admin requests it" do - delete v3_api("#{base_url}/#{dev_list.id}", owner) - - expect(response).to have_gitlab_http_status(200) - end - end - end -end diff --git a/spec/requests/api/v3/branches_spec.rb b/spec/requests/api/v3/branches_spec.rb deleted file mode 100644 index 1e038595a1f..00000000000 --- a/spec/requests/api/v3/branches_spec.rb +++ /dev/null @@ -1,120 +0,0 @@ -require 'spec_helper' -require 'mime/types' - -describe API::V3::Branches do - set(:user) { create(:user) } - set(:user2) { create(:user) } - set(:project) { create(:project, :repository, creator: user) } - set(:master) { create(:project_member, :master, user: user, project: project) } - set(:guest) { create(:project_member, :guest, user: user2, project: project) } - let!(:branch_name) { 'feature' } - let!(:branch_sha) { '0b4bc9a49b562e85de7cc9e834518ea6828729b9' } - let!(:branch_with_dot) { CreateBranchService.new(project, user).execute("with.1.2.3", "master") } - - describe "GET /projects/:id/repository/branches" do - it "returns an array of project branches" do - project.repository.expire_all_method_caches - - get v3_api("/projects/#{project.id}/repository/branches", user), per_page: 100 - - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - branch_names = json_response.map { |x| x['name'] } - expect(branch_names).to match_array(project.repository.branch_names) - end - end - - describe "DELETE /projects/:id/repository/branches/:branch" do - before do - allow_any_instance_of(Repository).to receive(:rm_branch).and_return(true) - end - - it "removes branch" do - delete v3_api("/projects/#{project.id}/repository/branches/#{branch_name}", user) - - expect(response).to have_gitlab_http_status(200) - expect(json_response['branch_name']).to eq(branch_name) - end - - it "removes a branch with dots in the branch name" do - delete v3_api("/projects/#{project.id}/repository/branches/with.1.2.3", user) - - expect(response).to have_gitlab_http_status(200) - expect(json_response['branch_name']).to eq("with.1.2.3") - end - - it 'returns 404 if branch not exists' do - delete v3_api("/projects/#{project.id}/repository/branches/foobar", user) - expect(response).to have_gitlab_http_status(404) - end - end - - describe "DELETE /projects/:id/repository/merged_branches" do - before do - allow_any_instance_of(Repository).to receive(:rm_branch).and_return(true) - end - - it 'returns 200' do - delete v3_api("/projects/#{project.id}/repository/merged_branches", user) - - expect(response).to have_gitlab_http_status(200) - end - - it 'returns a 403 error if guest' do - delete v3_api("/projects/#{project.id}/repository/merged_branches", user2) - - expect(response).to have_gitlab_http_status(403) - end - end - - describe "POST /projects/:id/repository/branches" do - it "creates a new branch" do - post v3_api("/projects/#{project.id}/repository/branches", user), - branch_name: 'feature1', - ref: branch_sha - - expect(response).to have_gitlab_http_status(201) - - expect(json_response['name']).to eq('feature1') - expect(json_response['commit']['id']).to eq(branch_sha) - end - - it "denies for user without push access" do - post v3_api("/projects/#{project.id}/repository/branches", user2), - branch_name: branch_name, - ref: branch_sha - expect(response).to have_gitlab_http_status(403) - end - - it 'returns 400 if branch name is invalid' do - post v3_api("/projects/#{project.id}/repository/branches", user), - branch_name: 'new design', - ref: branch_sha - expect(response).to have_gitlab_http_status(400) - expect(json_response['message']).to eq('Branch name is invalid') - end - - it 'returns 400 if branch already exists' do - post v3_api("/projects/#{project.id}/repository/branches", user), - branch_name: 'new_design1', - ref: branch_sha - expect(response).to have_gitlab_http_status(201) - - post v3_api("/projects/#{project.id}/repository/branches", user), - branch_name: 'new_design1', - ref: branch_sha - - expect(response).to have_gitlab_http_status(400) - expect(json_response['message']).to eq('Branch already exists') - end - - it 'returns 400 if ref name is invalid' do - post v3_api("/projects/#{project.id}/repository/branches", user), - branch_name: 'new_design3', - ref: 'foo' - - expect(response).to have_gitlab_http_status(400) - expect(json_response['message']).to eq('Invalid reference name') - end - end -end diff --git a/spec/requests/api/v3/broadcast_messages_spec.rb b/spec/requests/api/v3/broadcast_messages_spec.rb deleted file mode 100644 index d9641011491..00000000000 --- a/spec/requests/api/v3/broadcast_messages_spec.rb +++ /dev/null @@ -1,32 +0,0 @@ -require 'spec_helper' - -describe API::V3::BroadcastMessages do - set(:user) { create(:user) } - set(:admin) { create(:admin) } - - describe 'DELETE /broadcast_messages/:id' do - set(:message) { create(:broadcast_message) } - - it 'returns a 401 for anonymous users' do - delete v3_api("/broadcast_messages/#{message.id}"), - attributes_for(:broadcast_message) - - expect(response).to have_gitlab_http_status(401) - end - - it 'returns a 403 for users' do - delete v3_api("/broadcast_messages/#{message.id}", user), - attributes_for(:broadcast_message) - - expect(response).to have_gitlab_http_status(403) - end - - it 'deletes the broadcast message for admins' do - expect do - delete v3_api("/broadcast_messages/#{message.id}", admin) - - expect(response).to have_gitlab_http_status(200) - end.to change { BroadcastMessage.count }.by(-1) - end - end -end diff --git a/spec/requests/api/v3/builds_spec.rb b/spec/requests/api/v3/builds_spec.rb deleted file mode 100644 index 485d7c2cc43..00000000000 --- a/spec/requests/api/v3/builds_spec.rb +++ /dev/null @@ -1,550 +0,0 @@ -require 'spec_helper' - -describe API::V3::Builds do - set(:user) { create(:user) } - let(:api_user) { user } - set(:project) { create(:project, :repository, creator: user, public_builds: false) } - let!(:developer) { create(:project_member, :developer, user: user, project: project) } - let(:reporter) { create(:project_member, :reporter, project: project) } - let(:guest) { create(:project_member, :guest, project: project) } - let(:pipeline) { create(:ci_empty_pipeline, project: project, sha: project.commit.id, ref: project.default_branch) } - let(:build) { create(:ci_build, pipeline: pipeline) } - - describe 'GET /projects/:id/builds ' do - let(:query) { '' } - - before do |example| - build - - create(:ci_build, :skipped, pipeline: pipeline) - - unless example.metadata[:skip_before_request] - get v3_api("/projects/#{project.id}/builds?#{query}", api_user) - end - end - - context 'authorized user' do - it 'returns project builds' do - expect(response).to have_gitlab_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array - end - - it 'returns correct values' do - expect(json_response).not_to be_empty - expect(json_response.first['commit']['id']).to eq project.commit.id - end - - it 'returns pipeline data' do - json_build = json_response.first - expect(json_build['pipeline']).not_to be_empty - expect(json_build['pipeline']['id']).to eq build.pipeline.id - expect(json_build['pipeline']['ref']).to eq build.pipeline.ref - expect(json_build['pipeline']['sha']).to eq build.pipeline.sha - expect(json_build['pipeline']['status']).to eq build.pipeline.status - end - - it 'avoids N+1 queries', :skip_before_request do - first_build = create(:ci_build, :artifacts, pipeline: pipeline) - first_build.runner = create(:ci_runner) - first_build.user = create(:user) - first_build.save - - control_count = ActiveRecord::QueryRecorder.new { go }.count - - second_pipeline = create(:ci_empty_pipeline, project: project, sha: project.commit.id, ref: project.default_branch) - second_build = create(:ci_build, :artifacts, pipeline: second_pipeline) - second_build.runner = create(:ci_runner) - second_build.user = create(:user) - second_build.save - - expect { go }.not_to exceed_query_limit(control_count) - end - - context 'filter project with one scope element' do - let(:query) { 'scope=pending' } - - it do - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - end - end - - context 'filter project with scope skipped' do - let(:query) { 'scope=skipped' } - let(:json_build) { json_response.first } - - it 'return builds with status skipped' do - expect(response).to have_gitlab_http_status 200 - expect(json_response).to be_an Array - expect(json_response.length).to eq 1 - expect(json_build['status']).to eq 'skipped' - end - end - - context 'filter project with array of scope elements' do - let(:query) { 'scope[0]=pending&scope[1]=running' } - - it do - expect(response).to have_gitlab_http_status(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).to have_gitlab_http_status(400) } - end - end - - context 'unauthorized user' do - let(:api_user) { nil } - - it 'does not return project builds' do - expect(response).to have_gitlab_http_status(401) - end - end - - def go - get v3_api("/projects/#{project.id}/builds?#{query}", api_user) - end - end - - describe 'GET /projects/:id/repository/commits/:sha/builds' do - before do - build - end - - context 'when commit does not exist in repository' do - before do - get v3_api("/projects/#{project.id}/repository/commits/1a271fd1/builds", api_user) - end - - it 'responds with 404' do - expect(response).to have_gitlab_http_status(404) - end - end - - context 'when commit exists in repository' do - context 'when user is authorized' do - context 'when pipeline has jobs' do - before do - create(:ci_pipeline, project: project, sha: project.commit.id) - create(:ci_build, pipeline: pipeline) - create(:ci_build) - - get v3_api("/projects/#{project.id}/repository/commits/#{project.commit.id}/builds", api_user) - end - - it 'returns project jobs for specific commit' do - expect(response).to have_gitlab_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array - expect(json_response.size).to eq 2 - end - - it 'returns pipeline data' do - json_build = json_response.first - expect(json_build['pipeline']).not_to be_empty - expect(json_build['pipeline']['id']).to eq build.pipeline.id - expect(json_build['pipeline']['ref']).to eq build.pipeline.ref - expect(json_build['pipeline']['sha']).to eq build.pipeline.sha - expect(json_build['pipeline']['status']).to eq build.pipeline.status - end - end - - context 'when pipeline has no jobs' do - before do - branch_head = project.commit('feature').id - get v3_api("/projects/#{project.id}/repository/commits/#{branch_head}/builds", api_user) - end - - it 'returns an empty array' do - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response).to be_empty - end - end - end - - context 'when user is not authorized' do - before do - create(:ci_pipeline, project: project, sha: project.commit.id) - create(:ci_build, pipeline: pipeline) - - get v3_api("/projects/#{project.id}/repository/commits/#{project.commit.id}/builds", nil) - end - - it 'does not return project jobs' do - expect(response).to have_gitlab_http_status(401) - expect(json_response.except('message')).to be_empty - end - end - end - end - - describe 'GET /projects/:id/builds/:build_id' do - before do - get v3_api("/projects/#{project.id}/builds/#{build.id}", api_user) - end - - context 'authorized user' do - it 'returns specific job data' do - expect(response).to have_gitlab_http_status(200) - expect(json_response['name']).to eq('test') - end - - it 'returns pipeline data' do - json_build = json_response - expect(json_build['pipeline']).not_to be_empty - expect(json_build['pipeline']['id']).to eq build.pipeline.id - expect(json_build['pipeline']['ref']).to eq build.pipeline.ref - expect(json_build['pipeline']['sha']).to eq build.pipeline.sha - expect(json_build['pipeline']['status']).to eq build.pipeline.status - end - end - - context 'unauthorized user' do - let(:api_user) { nil } - - it 'does not return specific job data' do - expect(response).to have_gitlab_http_status(401) - end - end - end - - describe 'GET /projects/:id/builds/:build_id/artifacts' do - before do - stub_artifacts_object_storage - get v3_api("/projects/#{project.id}/builds/#{build.id}/artifacts", api_user) - end - - context 'job with artifacts' do - context 'when artifacts are stored locally' do - 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' } - end - - it 'returns specific job artifacts' do - expect(response).to have_http_status(200) - expect(response.headers.to_h).to include(download_headers) - expect(response.body).to match_file(build.artifacts_file.file.file) - end - end - end - - context 'when artifacts are stored remotely' do - let(:build) { create(:ci_build, pipeline: pipeline) } - let!(:artifact) { create(:ci_job_artifact, :archive, :remote_store, job: build) } - - it 'returns location redirect' do - get v3_api("/projects/#{project.id}/builds/#{build.id}/artifacts", api_user) - - expect(response).to have_gitlab_http_status(302) - end - end - - context 'unauthorized user' do - let(:api_user) { nil } - - it 'does not return specific job artifacts' do - expect(response).to have_gitlab_http_status(401) - end - end - end - - it 'does not return job artifacts if not uploaded' do - expect(response).to have_gitlab_http_status(404) - end - end - - describe 'GET /projects/:id/artifacts/:ref_name/download?job=name' do - let(:api_user) { reporter.user } - let(:build) { create(:ci_build, :artifacts, pipeline: pipeline) } - - before do - stub_artifacts_object_storage - build.success - end - - def path_for_ref(ref = pipeline.ref, job = build.name) - v3_api("/projects/#{project.id}/builds/artifacts/#{ref}/download?job=#{job}", api_user) - end - - context 'when not logged in' do - let(:api_user) { nil } - - before do - get path_for_ref - end - - it 'gives 401' do - expect(response).to have_gitlab_http_status(401) - end - end - - context 'when logging as guest' do - let(:api_user) { guest.user } - - before do - get path_for_ref - end - - it 'gives 403' do - expect(response).to have_gitlab_http_status(403) - end - end - - context 'non-existing job' do - shared_examples 'not found' do - it { expect(response).to have_gitlab_http_status(:not_found) } - end - - context 'has no such ref' do - before do - get path_for_ref('TAIL', build.name) - end - - it_behaves_like 'not found' - end - - context 'has no such job' do - before do - get path_for_ref(pipeline.ref, 'NOBUILD') - end - - it_behaves_like 'not found' - end - end - - context 'find proper job' do - shared_examples 'a valid file' do - context 'when artifacts are stored locally' do - let(:download_headers) do - { 'Content-Transfer-Encoding' => 'binary', - 'Content-Disposition' => - "attachment; filename=#{build.artifacts_file.filename}" } - end - - it { expect(response).to have_http_status(200) } - it { expect(response.headers.to_h).to include(download_headers) } - end - - context 'when artifacts are stored remotely' do - let(:build) { create(:ci_build, pipeline: pipeline) } - let!(:artifact) { create(:ci_job_artifact, :archive, :remote_store, job: build) } - - before do - build.reload - - get v3_api("/projects/#{project.id}/builds/#{build.id}/artifacts", api_user) - end - - it 'returns location redirect' do - expect(response).to have_http_status(302) - end - end - end - - context 'with regular branch' do - before do - pipeline.reload - pipeline.update(ref: 'master', - sha: project.commit('master').sha) - - get path_for_ref('master') - end - - it_behaves_like 'a valid file' - end - - context 'with branch name containing slash' do - before do - pipeline.reload - pipeline.update(ref: 'improve/awesome', - sha: project.commit('improve/awesome').sha) - end - - before do - get path_for_ref('improve/awesome') - end - - it_behaves_like 'a valid file' - end - end - end - - describe 'GET /projects/:id/builds/:build_id/trace' do - let(:build) { create(:ci_build, :trace_live, pipeline: pipeline) } - - before do - get v3_api("/projects/#{project.id}/builds/#{build.id}/trace", api_user) - end - - context 'authorized user' do - it 'returns specific job trace' do - expect(response).to have_gitlab_http_status(200) - expect(response.body).to eq(build.trace.raw) - end - end - - context 'unauthorized user' do - let(:api_user) { nil } - - it 'does not return specific job trace' do - expect(response).to have_gitlab_http_status(401) - end - end - end - - describe 'POST /projects/:id/builds/:build_id/cancel' do - before do - post v3_api("/projects/#{project.id}/builds/#{build.id}/cancel", api_user) - end - - context 'authorized user' do - context 'user with :update_build persmission' do - it 'cancels running or pending job' do - expect(response).to have_gitlab_http_status(201) - expect(project.builds.first.status).to eq('canceled') - end - end - - context 'user without :update_build permission' do - let(:api_user) { reporter.user } - - it 'does not cancel job' do - expect(response).to have_gitlab_http_status(403) - end - end - end - - context 'unauthorized user' do - let(:api_user) { nil } - - it 'does not cancel job' do - expect(response).to have_gitlab_http_status(401) - end - end - end - - describe 'POST /projects/:id/builds/:build_id/retry' do - let(:build) { create(:ci_build, :canceled, pipeline: pipeline) } - - before do - post v3_api("/projects/#{project.id}/builds/#{build.id}/retry", api_user) - end - - context 'authorized user' do - context 'user with :update_build permission' do - it 'retries non-running job' do - expect(response).to have_gitlab_http_status(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) { reporter.user } - - it 'does not retry job' do - expect(response).to have_gitlab_http_status(403) - end - end - end - - context 'unauthorized user' do - let(:api_user) { nil } - - it 'does not retry job' do - expect(response).to have_gitlab_http_status(401) - end - end - end - - describe 'POST /projects/:id/builds/:build_id/erase' do - before do - project.add_master(user) - - post v3_api("/projects/#{project.id}/builds/#{build.id}/erase", user) - end - - context 'job is erasable' do - let(:build) { create(:ci_build, :trace_artifact, :artifacts, :success, project: project, pipeline: pipeline) } - - it 'erases job content' do - expect(response.status).to eq 201 - expect(build).not_to have_trace - expect(build.artifacts_file.exists?).to be_falsy - expect(build.artifacts_metadata.exists?).to be_falsy - end - - it 'updates job' do - expect(build.reload.erased_at).to be_truthy - expect(build.reload.erased_by).to eq user - end - end - - context 'job is not erasable' do - let(:build) { create(:ci_build, :trace_live, project: project, pipeline: pipeline) } - - it 'responds with forbidden' do - expect(response.status).to eq 403 - end - end - end - - describe 'POST /projects/:id/builds/:build_id/artifacts/keep' do - before do - post v3_api("/projects/#{project.id}/builds/#{build.id}/artifacts/keep", user) - end - - context 'artifacts did not expire' do - let(:build) do - create(:ci_build, :trace_artifact, :artifacts, :success, - project: project, pipeline: pipeline, artifacts_expire_at: Time.now + 7.days) - end - - it 'keeps artifacts' do - expect(response.status).to eq 200 - expect(build.reload.artifacts_expire_at).to be_nil - end - end - - context 'no artifacts' do - let(:build) { create(:ci_build, project: project, pipeline: pipeline) } - - it 'responds with not found' do - expect(response.status).to eq 404 - end - end - end - - describe 'POST /projects/:id/builds/:build_id/play' do - before do - post v3_api("/projects/#{project.id}/builds/#{build.id}/play", user) - end - - context 'on an playable job' do - let(:build) { create(:ci_build, :manual, project: project, pipeline: pipeline) } - - it 'plays the job' do - expect(response).to have_gitlab_http_status 200 - expect(json_response['user']['id']).to eq(user.id) - expect(json_response['id']).to eq(build.id) - end - end - - context 'on a non-playable job' do - it 'returns a status code 400, Bad Request' do - expect(response).to have_gitlab_http_status 400 - expect(response.body).to match("Unplayable Job") - end - end - end -end diff --git a/spec/requests/api/v3/commits_spec.rb b/spec/requests/api/v3/commits_spec.rb deleted file mode 100644 index 9ef3b859001..00000000000 --- a/spec/requests/api/v3/commits_spec.rb +++ /dev/null @@ -1,603 +0,0 @@ -require 'spec_helper' -require 'mime/types' - -describe API::V3::Commits do - let(:user) { create(:user) } - let(:user2) { create(:user) } - let!(:project) { create(:project, :repository, creator: user, namespace: user.namespace) } - 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') } - - before { project.add_reporter(user) } - - describe "List repository commits" do - context "authorized user" do - before { project.add_reporter(user2) } - - it "returns project commits" do - commit = project.repository.commit - get v3_api("/projects/#{project.id}/repository/commits", user) - - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.first['id']).to eq(commit.id) - expect(json_response.first['committer_name']).to eq(commit.committer_name) - expect(json_response.first['committer_email']).to eq(commit.committer_email) - end - end - - context "unauthorized user" do - it "does not return project commits" do - get v3_api("/projects/#{project.id}/repository/commits") - expect(response).to have_gitlab_http_status(401) - end - end - - context "since optional parameter" do - it "returns project commits since provided parameter" do - commits = project.repository.commits("master", limit: 2) - since = commits.second.created_at - - get v3_api("/projects/#{project.id}/repository/commits?since=#{since.utc.iso8601}", user) - - expect(json_response.size).to eq 2 - expect(json_response.first["id"]).to eq(commits.first.id) - expect(json_response.second["id"]).to eq(commits.second.id) - end - end - - context "until optional parameter" do - it "returns project commits until provided parameter" do - commits = project.repository.commits("master", limit: 20) - before = commits.second.created_at - - get v3_api("/projects/#{project.id}/repository/commits?until=#{before.utc.iso8601}", user) - - if commits.size == 20 - expect(json_response.size).to eq(20) - else - expect(json_response.size).to eq(commits.size - 1) - end - - expect(json_response.first["id"]).to eq(commits.second.id) - expect(json_response.second["id"]).to eq(commits.third.id) - end - end - - context "invalid xmlschema date parameters" do - it "returns an invalid parameter error message" do - get v3_api("/projects/#{project.id}/repository/commits?since=invalid-date", user) - - expect(response).to have_gitlab_http_status(400) - expect(json_response['error']).to eq('since is invalid') - end - end - - context "path optional parameter" do - it "returns project commits matching provided path parameter" do - path = 'files/ruby/popen.rb' - - get v3_api("/projects/#{project.id}/repository/commits?path=#{path}", user) - - expect(json_response.size).to eq(3) - expect(json_response.first["id"]).to eq("570e7b2abdd848b95f2f578043fc23bd6f6fd24d") - end - end - end - - describe "POST /projects/:id/repository/commits" do - let!(:url) { "/projects/#{project.id}/repository/commits" } - - it 'returns a 403 unauthorized for user without permissions' do - post v3_api(url, user2) - - expect(response).to have_gitlab_http_status(403) - end - - it 'returns a 400 bad request if no params are given' do - post v3_api(url, user) - - expect(response).to have_gitlab_http_status(400) - end - - describe 'create' do - let(:message) { 'Created file' } - let!(:invalid_c_params) do - { - branch_name: 'master', - commit_message: message, - actions: [ - { - action: 'create', - file_path: 'files/ruby/popen.rb', - content: 'puts 8' - } - ] - } - end - let!(:valid_c_params) do - { - branch_name: 'master', - commit_message: message, - actions: [ - { - action: 'create', - file_path: 'foo/bar/baz.txt', - content: 'puts 8' - } - ] - } - end - - it 'a new file in project repo' do - post v3_api(url, user), valid_c_params - - expect(response).to have_gitlab_http_status(201) - expect(json_response['title']).to eq(message) - expect(json_response['committer_name']).to eq(user.name) - expect(json_response['committer_email']).to eq(user.email) - end - - it 'returns a 400 bad request if file exists' do - post v3_api(url, user), invalid_c_params - - expect(response).to have_gitlab_http_status(400) - end - - context 'with project path containing a dot in URL' do - let!(:user) { create(:user, username: 'foo.bar') } - let(:url) { "/projects/#{CGI.escape(project.full_path)}/repository/commits" } - - it 'a new file in project repo' do - post v3_api(url, user), valid_c_params - - expect(response).to have_gitlab_http_status(201) - end - end - end - - describe 'delete' do - let(:message) { 'Deleted file' } - let!(:invalid_d_params) do - { - branch_name: 'markdown', - commit_message: message, - actions: [ - { - action: 'delete', - file_path: 'doc/api/projects.md' - } - ] - } - end - let!(:valid_d_params) do - { - branch_name: 'markdown', - commit_message: message, - actions: [ - { - action: 'delete', - file_path: 'doc/api/users.md' - } - ] - } - end - - it 'an existing file in project repo' do - post v3_api(url, user), valid_d_params - - expect(response).to have_gitlab_http_status(201) - expect(json_response['title']).to eq(message) - end - - it 'returns a 400 bad request if file does not exist' do - post v3_api(url, user), invalid_d_params - - expect(response).to have_gitlab_http_status(400) - end - end - - describe 'move' do - let(:message) { 'Moved file' } - let!(:invalid_m_params) do - { - branch_name: 'feature', - commit_message: message, - actions: [ - { - action: 'move', - file_path: 'CHANGELOG', - previous_path: 'VERSION', - content: '6.7.0.pre' - } - ] - } - end - let!(:valid_m_params) do - { - branch_name: 'feature', - commit_message: message, - actions: [ - { - action: 'move', - file_path: 'VERSION.txt', - previous_path: 'VERSION', - content: '6.7.0.pre' - } - ] - } - end - - it 'an existing file in project repo' do - post v3_api(url, user), valid_m_params - - expect(response).to have_gitlab_http_status(201) - expect(json_response['title']).to eq(message) - end - - it 'returns a 400 bad request if file does not exist' do - post v3_api(url, user), invalid_m_params - - expect(response).to have_gitlab_http_status(400) - end - end - - describe 'update' do - let(:message) { 'Updated file' } - let!(:invalid_u_params) do - { - branch_name: 'master', - commit_message: message, - actions: [ - { - action: 'update', - file_path: 'foo/bar.baz', - content: 'puts 8' - } - ] - } - end - let!(:valid_u_params) do - { - branch_name: 'master', - commit_message: message, - actions: [ - { - action: 'update', - file_path: 'files/ruby/popen.rb', - content: 'puts 8' - } - ] - } - end - - it 'an existing file in project repo' do - post v3_api(url, user), valid_u_params - - expect(response).to have_gitlab_http_status(201) - expect(json_response['title']).to eq(message) - end - - it 'returns a 400 bad request if file does not exist' do - post v3_api(url, user), invalid_u_params - - expect(response).to have_gitlab_http_status(400) - end - end - - context "multiple operations" do - let(:message) { 'Multiple actions' } - let!(:invalid_mo_params) do - { - branch_name: 'master', - commit_message: message, - actions: [ - { - action: 'create', - file_path: 'files/ruby/popen.rb', - content: 'puts 8' - }, - { - action: 'delete', - file_path: 'doc/v3_api/projects.md' - }, - { - action: 'move', - file_path: 'CHANGELOG', - previous_path: 'VERSION', - content: '6.7.0.pre' - }, - { - action: 'update', - file_path: 'foo/bar.baz', - content: 'puts 8' - } - ] - } - end - let!(:valid_mo_params) do - { - branch_name: 'master', - commit_message: message, - actions: [ - { - action: 'create', - file_path: 'foo/bar/baz.txt', - content: 'puts 8' - }, - { - action: 'delete', - file_path: 'Gemfile.zip' - }, - { - action: 'move', - file_path: 'VERSION.txt', - previous_path: 'VERSION', - content: '6.7.0.pre' - }, - { - action: 'update', - file_path: 'files/ruby/popen.rb', - content: 'puts 8' - } - ] - } - end - - it 'are commited as one in project repo' do - post v3_api(url, user), valid_mo_params - - expect(response).to have_gitlab_http_status(201) - expect(json_response['title']).to eq(message) - end - - it 'return a 400 bad request if there are any issues' do - post v3_api(url, user), invalid_mo_params - - expect(response).to have_gitlab_http_status(400) - end - end - end - - describe "Get a single commit" do - context "authorized user" do - it "returns a commit by sha" do - get v3_api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}", user) - - expect(response).to have_gitlab_http_status(200) - expect(json_response['id']).to eq(project.repository.commit.id) - expect(json_response['title']).to eq(project.repository.commit.title) - expect(json_response['stats']['additions']).to eq(project.repository.commit.stats.additions) - expect(json_response['stats']['deletions']).to eq(project.repository.commit.stats.deletions) - expect(json_response['stats']['total']).to eq(project.repository.commit.stats.total) - end - - it "returns a 404 error if not found" do - get v3_api("/projects/#{project.id}/repository/commits/invalid_sha", user) - expect(response).to have_gitlab_http_status(404) - end - - it "returns nil for commit without CI" do - get v3_api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}", user) - - expect(response).to have_gitlab_http_status(200) - expect(json_response['status']).to be_nil - end - - it "returns status for CI" do - pipeline = project.pipelines.create(source: :push, ref: 'master', sha: project.repository.commit.sha, protected: false) - pipeline.update(status: 'success') - - get v3_api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}", user) - - expect(response).to have_gitlab_http_status(200) - expect(json_response['status']).to eq(pipeline.status) - end - - it "returns status for CI when pipeline is created" do - project.pipelines.create(source: :push, ref: 'master', sha: project.repository.commit.sha, protected: false) - - get v3_api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}", user) - - expect(response).to have_gitlab_http_status(200) - expect(json_response['status']).to eq("created") - end - - context 'when stat param' do - let(:project_id) { project.id } - let(:commit_id) { project.repository.commit.id } - let(:route) { "/projects/#{project_id}/repository/commits/#{commit_id}" } - - it 'is not present return stats by default' do - get v3_api(route, user) - - expect(response).to have_gitlab_http_status(200) - expect(json_response).to include 'stats' - end - - it "is false it does not include stats" do - get v3_api(route, user), stats: false - - expect(response).to have_gitlab_http_status(200) - expect(json_response).not_to include 'stats' - end - - it "is true it includes stats" do - get v3_api(route, user), stats: true - - expect(response).to have_gitlab_http_status(200) - expect(json_response).to include 'stats' - end - end - end - - context "unauthorized user" do - it "does not return the selected commit" do - get v3_api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}") - expect(response).to have_gitlab_http_status(401) - end - end - end - - describe "Get the diff of a commit" do - context "authorized user" do - before { project.add_reporter(user2) } - - it "returns the diff of the selected commit" do - get v3_api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}/diff", user) - expect(response).to have_gitlab_http_status(200) - - expect(json_response).to be_an Array - expect(json_response.length).to be >= 1 - expect(json_response.first.keys).to include "diff" - end - - it "returns a 404 error if invalid commit" do - get v3_api("/projects/#{project.id}/repository/commits/invalid_sha/diff", user) - expect(response).to have_gitlab_http_status(404) - end - end - - context "unauthorized user" do - it "does not return the diff of the selected commit" do - get v3_api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}/diff") - expect(response).to have_gitlab_http_status(401) - end - end - end - - describe 'Get the comments of a commit' do - context 'authorized user' do - it 'returns merge_request comments' do - get v3_api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}/comments", user) - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.length).to eq(2) - expect(json_response.first['note']).to eq('a comment on a commit') - expect(json_response.first['author']['id']).to eq(user.id) - end - - it 'returns a 404 error if merge_request_id not found' do - get v3_api("/projects/#{project.id}/repository/commits/1234ab/comments", user) - expect(response).to have_gitlab_http_status(404) - end - end - - context 'unauthorized user' do - it 'does not return the diff of the selected commit' do - get v3_api("/projects/#{project.id}/repository/commits/1234ab/comments") - expect(response).to have_gitlab_http_status(401) - end - end - end - - describe 'POST :id/repository/commits/:sha/cherry_pick' do - let(:master_pickable_commit) { project.commit('7d3b0f7cff5f37573aea97cebfd5692ea1689924') } - - context 'authorized user' do - it 'cherry picks a commit' do - post v3_api("/projects/#{project.id}/repository/commits/#{master_pickable_commit.id}/cherry_pick", user), branch: 'master' - - expect(response).to have_gitlab_http_status(201) - expect(json_response['title']).to eq(master_pickable_commit.title) - expect(json_response['message']).to eq(master_pickable_commit.cherry_pick_message(user)) - expect(json_response['author_name']).to eq(master_pickable_commit.author_name) - expect(json_response['committer_name']).to eq(user.name) - end - - it 'returns 400 if commit is already included in the target branch' do - post v3_api("/projects/#{project.id}/repository/commits/#{master_pickable_commit.id}/cherry_pick", user), branch: 'markdown' - - expect(response).to have_gitlab_http_status(400) - expect(json_response['message']).to include('Sorry, we cannot cherry-pick this commit automatically.') - end - - it 'returns 400 if you are not allowed to push to the target branch' do - project.add_developer(user2) - protected_branch = create(:protected_branch, project: project, name: 'feature') - - post v3_api("/projects/#{project.id}/repository/commits/#{master_pickable_commit.id}/cherry_pick", user2), branch: protected_branch.name - - expect(response).to have_gitlab_http_status(400) - expect(json_response['message']).to eq('You are not allowed to push into this branch') - end - - it 'returns 400 for missing parameters' do - post v3_api("/projects/#{project.id}/repository/commits/#{master_pickable_commit.id}/cherry_pick", user) - - expect(response).to have_gitlab_http_status(400) - expect(json_response['error']).to eq('branch is missing') - end - - it 'returns 404 if commit is not found' do - post v3_api("/projects/#{project.id}/repository/commits/abcd0123/cherry_pick", user), branch: 'master' - - expect(response).to have_gitlab_http_status(404) - expect(json_response['message']).to eq('404 Commit Not Found') - end - - it 'returns 404 if branch is not found' do - post v3_api("/projects/#{project.id}/repository/commits/#{master_pickable_commit.id}/cherry_pick", user), branch: 'foo' - - expect(response).to have_gitlab_http_status(404) - expect(json_response['message']).to eq('404 Branch Not Found') - end - - it 'returns 400 for missing parameters' do - post v3_api("/projects/#{project.id}/repository/commits/#{master_pickable_commit.id}/cherry_pick", user) - - expect(response).to have_gitlab_http_status(400) - expect(json_response['error']).to eq('branch is missing') - end - end - - context 'unauthorized user' do - it 'does not cherry pick the commit' do - post v3_api("/projects/#{project.id}/repository/commits/#{master_pickable_commit.id}/cherry_pick"), branch: 'master' - - expect(response).to have_gitlab_http_status(401) - end - end - end - - describe 'Post comment to commit' do - context 'authorized user' do - it 'returns comment' do - post v3_api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}/comments", user), note: 'My comment' - expect(response).to have_gitlab_http_status(201) - expect(json_response['note']).to eq('My comment') - expect(json_response['path']).to be_nil - expect(json_response['line']).to be_nil - expect(json_response['line_type']).to be_nil - end - - it 'returns the inline comment' do - post v3_api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}/comments", user), note: 'My comment', path: project.repository.commit.raw_diffs.first.new_path, line: 1, line_type: 'new' - - expect(response).to have_gitlab_http_status(201) - expect(json_response['note']).to eq('My comment') - expect(json_response['path']).to eq(project.repository.commit.raw_diffs.first.new_path) - expect(json_response['line']).to eq(1) - expect(json_response['line_type']).to eq('new') - end - - it 'returns 400 if note is missing' do - post v3_api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}/comments", user) - expect(response).to have_gitlab_http_status(400) - end - - it 'returns 404 if note is attached to non existent commit' do - post v3_api("/projects/#{project.id}/repository/commits/1234ab/comments", user), note: 'My comment' - expect(response).to have_gitlab_http_status(404) - end - end - - context 'unauthorized user' do - it 'does not return the diff of the selected commit' do - post v3_api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}/comments") - expect(response).to have_gitlab_http_status(401) - end - end - end -end diff --git a/spec/requests/api/v3/deploy_keys_spec.rb b/spec/requests/api/v3/deploy_keys_spec.rb deleted file mode 100644 index 501af587ad4..00000000000 --- a/spec/requests/api/v3/deploy_keys_spec.rb +++ /dev/null @@ -1,179 +0,0 @@ -require 'spec_helper' - -describe API::V3::DeployKeys do - let(:user) { create(:user) } - let(:admin) { create(:admin) } - let(:project) { create(:project, creator_id: user.id) } - let(:project2) { create(:project, creator_id: user.id) } - let(:deploy_key) { create(:deploy_key, public: true) } - - let!(:deploy_keys_project) do - create(:deploy_keys_project, project: project, deploy_key: deploy_key) - end - - describe 'GET /deploy_keys' do - context 'when unauthenticated' do - it 'should return authentication error' do - get v3_api('/deploy_keys') - - expect(response.status).to eq(401) - end - end - - context 'when authenticated as non-admin user' do - it 'should return a 403 error' do - get v3_api('/deploy_keys', user) - - expect(response.status).to eq(403) - end - end - - context 'when authenticated as admin' do - it 'should return all deploy keys' do - get v3_api('/deploy_keys', admin) - - expect(response.status).to eq(200) - expect(json_response).to be_an Array - expect(json_response.first['id']).to eq(deploy_keys_project.deploy_key.id) - end - end - end - - %w(deploy_keys keys).each do |path| - describe "GET /projects/:id/#{path}" do - before { deploy_key } - - it 'should return array of ssh keys' do - get v3_api("/projects/#{project.id}/#{path}", admin) - - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.first['title']).to eq(deploy_key.title) - end - end - - describe "GET /projects/:id/#{path}/:key_id" do - it 'should return a single key' do - get v3_api("/projects/#{project.id}/#{path}/#{deploy_key.id}", admin) - - expect(response).to have_gitlab_http_status(200) - expect(json_response['title']).to eq(deploy_key.title) - end - - it 'should return 404 Not Found with invalid ID' do - get v3_api("/projects/#{project.id}/#{path}/404", admin) - - expect(response).to have_gitlab_http_status(404) - end - end - - describe "POST /projects/:id/deploy_keys" do - it 'should not create an invalid ssh key' do - post v3_api("/projects/#{project.id}/#{path}", admin), { title: 'invalid key' } - - expect(response).to have_gitlab_http_status(400) - expect(json_response['error']).to eq('key is missing') - end - - it 'should not create a key without title' do - post v3_api("/projects/#{project.id}/#{path}", admin), key: 'some key' - - expect(response).to have_gitlab_http_status(400) - expect(json_response['error']).to eq('title is missing') - end - - it 'should create new ssh key' do - key_attrs = attributes_for :another_key - - expect do - post v3_api("/projects/#{project.id}/#{path}", admin), key_attrs - end.to change { project.deploy_keys.count }.by(1) - end - - it 'returns an existing ssh key when attempting to add a duplicate' do - expect do - post v3_api("/projects/#{project.id}/#{path}", admin), { key: deploy_key.key, title: deploy_key.title } - end.not_to change { project.deploy_keys.count } - - expect(response).to have_gitlab_http_status(201) - end - - it 'joins an existing ssh key to a new project' do - expect do - post v3_api("/projects/#{project2.id}/#{path}", admin), { key: deploy_key.key, title: deploy_key.title } - end.to change { project2.deploy_keys.count }.by(1) - - expect(response).to have_gitlab_http_status(201) - end - - it 'accepts can_push parameter' do - key_attrs = attributes_for(:another_key).merge(can_push: true) - - post v3_api("/projects/#{project.id}/#{path}", admin), key_attrs - - expect(response).to have_gitlab_http_status(201) - expect(json_response['can_push']).to eq(true) - end - end - - describe "DELETE /projects/:id/#{path}/:key_id" do - before { deploy_key } - - it 'should delete existing key' do - expect do - delete v3_api("/projects/#{project.id}/#{path}/#{deploy_key.id}", admin) - end.to change { project.deploy_keys.count }.by(-1) - end - - it 'should return 404 Not Found with invalid ID' do - delete v3_api("/projects/#{project.id}/#{path}/404", admin) - - expect(response).to have_gitlab_http_status(404) - end - end - - describe "POST /projects/:id/#{path}/:key_id/enable" do - let(:project2) { create(:project) } - - context 'when the user can admin the project' do - it 'enables the key' do - expect do - post v3_api("/projects/#{project2.id}/#{path}/#{deploy_key.id}/enable", admin) - end.to change { project2.deploy_keys.count }.from(0).to(1) - - expect(response).to have_gitlab_http_status(201) - expect(json_response['id']).to eq(deploy_key.id) - end - end - - context 'when authenticated as non-admin user' do - it 'should return a 404 error' do - post v3_api("/projects/#{project2.id}/#{path}/#{deploy_key.id}/enable", user) - - expect(response).to have_gitlab_http_status(404) - end - end - end - - describe "DELETE /projects/:id/deploy_keys/:key_id/disable" do - context 'when the user can admin the project' do - it 'disables the key' do - expect do - delete v3_api("/projects/#{project.id}/#{path}/#{deploy_key.id}/disable", admin) - end.to change { project.deploy_keys.count }.from(1).to(0) - - expect(response).to have_gitlab_http_status(200) - expect(json_response['id']).to eq(deploy_key.id) - end - end - - context 'when authenticated as non-admin user' do - it 'should return a 404 error' do - delete v3_api("/projects/#{project.id}/#{path}/#{deploy_key.id}/disable", user) - - expect(response).to have_gitlab_http_status(404) - end - end - end - end -end diff --git a/spec/requests/api/v3/deployments_spec.rb b/spec/requests/api/v3/deployments_spec.rb deleted file mode 100644 index ac86fbea498..00000000000 --- a/spec/requests/api/v3/deployments_spec.rb +++ /dev/null @@ -1,69 +0,0 @@ -require 'spec_helper' - -describe API::V3::Deployments do - let(:user) { create(:user) } - let(:non_member) { create(:user) } - let(:project) { deployment.environment.project } - let!(:deployment) { create(:deployment) } - - before do - project.add_master(user) - end - - shared_examples 'a paginated resources' do - before do - # Fires the request - request - end - - it 'has pagination headers' do - expect(response).to include_pagination_headers - end - end - - describe 'GET /projects/:id/deployments' do - context 'as member of the project' do - it_behaves_like 'a paginated resources' do - let(:request) { get v3_api("/projects/#{project.id}/deployments", user) } - end - - it 'returns projects deployments' do - get v3_api("/projects/#{project.id}/deployments", user) - - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.size).to eq(1) - expect(json_response.first['iid']).to eq(deployment.iid) - expect(json_response.first['sha']).to match /\A\h{40}\z/ - end - end - - context 'as non member' do - it 'returns a 404 status code' do - get v3_api("/projects/#{project.id}/deployments", non_member) - - expect(response).to have_gitlab_http_status(404) - end - end - end - - describe 'GET /projects/:id/deployments/:deployment_id' do - context 'as a member of the project' do - it 'returns the projects deployment' do - get v3_api("/projects/#{project.id}/deployments/#{deployment.id}", user) - - expect(response).to have_gitlab_http_status(200) - expect(json_response['sha']).to match /\A\h{40}\z/ - expect(json_response['id']).to eq(deployment.id) - end - end - - context 'as non member' do - it 'returns a 404 status code' do - get v3_api("/projects/#{project.id}/deployments/#{deployment.id}", non_member) - - expect(response).to have_gitlab_http_status(404) - end - end - end -end diff --git a/spec/requests/api/v3/environments_spec.rb b/spec/requests/api/v3/environments_spec.rb deleted file mode 100644 index 68be5256b64..00000000000 --- a/spec/requests/api/v3/environments_spec.rb +++ /dev/null @@ -1,163 +0,0 @@ -require 'spec_helper' - -describe API::V3::Environments do - let(:user) { create(:user) } - let(:non_member) { create(:user) } - let(:project) { create(:project, :private, namespace: user.namespace) } - let!(:environment) { create(:environment, project: project) } - - before do - project.add_master(user) - end - - shared_examples 'a paginated resources' do - before do - # Fires the request - request - end - - it 'has pagination headers' do - expect(response.headers).to include('X-Total') - expect(response.headers).to include('X-Total-Pages') - expect(response.headers).to include('X-Per-Page') - expect(response.headers).to include('X-Page') - expect(response.headers).to include('X-Next-Page') - expect(response.headers).to include('X-Prev-Page') - expect(response.headers).to include('Link') - end - end - - describe 'GET /projects/:id/environments' do - context 'as member of the project' do - it_behaves_like 'a paginated resources' do - let(:request) { get v3_api("/projects/#{project.id}/environments", user) } - end - - it 'returns project environments' do - get v3_api("/projects/#{project.id}/environments", user) - - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.size).to eq(1) - expect(json_response.first['name']).to eq(environment.name) - expect(json_response.first['external_url']).to eq(environment.external_url) - expect(json_response.first['project']['id']).to eq(project.id) - expect(json_response.first['project']['visibility_level']).to be_present - end - end - - context 'as non member' do - it 'returns a 404 status code' do - get v3_api("/projects/#{project.id}/environments", non_member) - - expect(response).to have_gitlab_http_status(404) - end - end - end - - describe 'POST /projects/:id/environments' do - context 'as a member' do - it 'creates a environment with valid params' do - post v3_api("/projects/#{project.id}/environments", user), name: "mepmep" - - expect(response).to have_gitlab_http_status(201) - expect(json_response['name']).to eq('mepmep') - expect(json_response['slug']).to eq('mepmep') - expect(json_response['external']).to be nil - end - - it 'requires name to be passed' do - post v3_api("/projects/#{project.id}/environments", user), external_url: 'test.gitlab.com' - - expect(response).to have_gitlab_http_status(400) - end - - it 'returns a 400 if environment already exists' do - post v3_api("/projects/#{project.id}/environments", user), name: environment.name - - expect(response).to have_gitlab_http_status(400) - end - - it 'returns a 400 if slug is specified' do - post v3_api("/projects/#{project.id}/environments", user), name: "foo", slug: "foo" - - expect(response).to have_gitlab_http_status(400) - expect(json_response["error"]).to eq("slug is automatically generated and cannot be changed") - end - end - - context 'a non member' do - it 'rejects the request' do - post v3_api("/projects/#{project.id}/environments", non_member), name: 'gitlab.com' - - expect(response).to have_gitlab_http_status(404) - end - - it 'returns a 400 when the required params are missing' do - post v3_api("/projects/12345/environments", non_member), external_url: 'http://env.git.com' - end - end - end - - describe 'PUT /projects/:id/environments/:environment_id' do - it 'returns a 200 if name and external_url are changed' do - url = 'https://mepmep.whatever.ninja' - put v3_api("/projects/#{project.id}/environments/#{environment.id}", user), - name: 'Mepmep', external_url: url - - expect(response).to have_gitlab_http_status(200) - expect(json_response['name']).to eq('Mepmep') - expect(json_response['external_url']).to eq(url) - end - - it "won't allow slug to be changed" do - slug = environment.slug - api_url = v3_api("/projects/#{project.id}/environments/#{environment.id}", user) - put api_url, slug: slug + "-foo" - - expect(response).to have_gitlab_http_status(400) - expect(json_response["error"]).to eq("slug is automatically generated and cannot be changed") - end - - it "won't update the external_url if only the name is passed" do - url = environment.external_url - put v3_api("/projects/#{project.id}/environments/#{environment.id}", user), - name: 'Mepmep' - - expect(response).to have_gitlab_http_status(200) - expect(json_response['name']).to eq('Mepmep') - expect(json_response['external_url']).to eq(url) - end - - it 'returns a 404 if the environment does not exist' do - put v3_api("/projects/#{project.id}/environments/12345", user) - - expect(response).to have_gitlab_http_status(404) - end - end - - describe 'DELETE /projects/:id/environments/:environment_id' do - context 'as a master' do - it 'returns a 200 for an existing environment' do - delete v3_api("/projects/#{project.id}/environments/#{environment.id}", user) - - expect(response).to have_gitlab_http_status(200) - end - - it 'returns a 404 for non existing id' do - delete v3_api("/projects/#{project.id}/environments/12345", user) - - expect(response).to have_gitlab_http_status(404) - expect(json_response['message']).to eq('404 Not found') - end - end - - context 'a non member' do - it 'rejects the request' do - delete v3_api("/projects/#{project.id}/environments/#{environment.id}", non_member) - - expect(response).to have_gitlab_http_status(404) - end - end - end -end diff --git a/spec/requests/api/v3/files_spec.rb b/spec/requests/api/v3/files_spec.rb deleted file mode 100644 index 26a3d8870a0..00000000000 --- a/spec/requests/api/v3/files_spec.rb +++ /dev/null @@ -1,283 +0,0 @@ -require 'spec_helper' - -describe API::V3::Files do - # I have to remove periods from the end of the name - # This happened when the user's name had a suffix (i.e. "Sr.") - # This seems to be what git does under the hood. For example, this commit: - # - # $ git commit --author='Foo Sr. <foo@example.com>' -m 'Where's my trailing period?' - # - # results in this: - # - # $ git show --pretty - # ... - # Author: Foo Sr <foo@example.com> - # ... - - let(:user) { create(:user) } - let!(:project) { create(:project, :repository, namespace: user.namespace ) } - let(:guest) { create(:user) { |u| project.add_guest(u) } } - let(:file_path) { 'files/ruby/popen.rb' } - let(:params) do - { - file_path: file_path, - ref: 'master' - } - end - let(:author_email) { 'user@example.org' } - let(:author_name) { 'John Doe' } - - before { project.add_developer(user) } - - describe "GET /projects/:id/repository/files" do - let(:route) { "/projects/#{project.id}/repository/files" } - - shared_examples_for 'repository files' do - it "returns file info" do - get v3_api(route, current_user), params - - expect(response).to have_gitlab_http_status(200) - expect(json_response['file_path']).to eq(file_path) - expect(json_response['file_name']).to eq('popen.rb') - expect(json_response['last_commit_id']).to eq('570e7b2abdd848b95f2f578043fc23bd6f6fd24d') - expect(Base64.decode64(json_response['content']).lines.first).to eq("require 'fileutils'\n") - end - - context 'when no params are given' do - it_behaves_like '400 response' do - let(:request) { get v3_api(route, current_user) } - end - end - - context 'when file_path does not exist' do - let(:params) do - { - file_path: 'app/models/application.rb', - ref: 'master' - } - end - - it_behaves_like '404 response' do - let(:request) { get v3_api(route, current_user), params } - let(:message) { '404 File Not Found' } - end - end - - context 'when repository is disabled' do - include_context 'disabled repository' - - it_behaves_like '403 response' do - let(:request) { get v3_api(route, current_user), params } - end - end - end - - context 'when unauthenticated', 'and project is public' do - it_behaves_like 'repository files' do - let(:project) { create(:project, :public, :repository) } - let(:current_user) { nil } - end - end - - context 'when unauthenticated', 'and project is private' do - it_behaves_like '404 response' do - let(:request) { get v3_api(route), params } - let(:message) { '404 Project Not Found' } - end - end - - context 'when authenticated', 'as a developer' do - it_behaves_like 'repository files' do - let(:current_user) { user } - end - end - - context 'when authenticated', 'as a guest' do - it_behaves_like '403 response' do - let(:request) { get v3_api(route, guest), params } - end - end - end - - describe "POST /projects/:id/repository/files" do - let(:valid_params) do - { - file_path: 'newfile.rb', - branch_name: 'master', - content: 'puts 8', - commit_message: 'Added newfile' - } - end - - it "creates a new file in project repo" do - post v3_api("/projects/#{project.id}/repository/files", user), valid_params - - expect(response).to have_gitlab_http_status(201) - expect(json_response['file_path']).to eq('newfile.rb') - last_commit = project.repository.commit.raw - expect(last_commit.author_email).to eq(user.email) - expect(last_commit.author_name).to eq(user.name) - end - - it "returns a 400 bad request if no params given" do - post v3_api("/projects/#{project.id}/repository/files", user) - - expect(response).to have_gitlab_http_status(400) - end - - it "returns a 400 if editor fails to create file" do - allow_any_instance_of(Repository).to receive(:create_file) - .and_raise(Gitlab::Git::CommitError, 'Cannot create file') - - post v3_api("/projects/#{project.id}/repository/files", user), valid_params - - expect(response).to have_gitlab_http_status(400) - end - - context "when specifying an author" do - it "creates a new file with the specified author" do - valid_params.merge!(author_email: author_email, author_name: author_name) - - post v3_api("/projects/#{project.id}/repository/files", user), valid_params - - expect(response).to have_gitlab_http_status(201) - last_commit = project.repository.commit.raw - expect(last_commit.author_email).to eq(author_email) - expect(last_commit.author_name).to eq(author_name) - end - end - - context 'when the repo is empty' do - let!(:project) { create(:project_empty_repo, namespace: user.namespace ) } - - it "creates a new file in project repo" do - post v3_api("/projects/#{project.id}/repository/files", user), valid_params - - expect(response).to have_gitlab_http_status(201) - expect(json_response['file_path']).to eq('newfile.rb') - last_commit = project.repository.commit.raw - expect(last_commit.author_email).to eq(user.email) - expect(last_commit.author_name).to eq(user.name) - end - end - end - - describe "PUT /projects/:id/repository/files" do - let(:valid_params) do - { - file_path: file_path, - branch_name: 'master', - content: 'puts 8', - commit_message: 'Changed file' - } - end - - it "updates existing file in project repo" do - put v3_api("/projects/#{project.id}/repository/files", user), valid_params - - expect(response).to have_gitlab_http_status(200) - expect(json_response['file_path']).to eq(file_path) - last_commit = project.repository.commit.raw - expect(last_commit.author_email).to eq(user.email) - expect(last_commit.author_name).to eq(user.name) - end - - it "returns a 400 bad request if no params given" do - put v3_api("/projects/#{project.id}/repository/files", user) - - expect(response).to have_gitlab_http_status(400) - end - - context "when specifying an author" do - it "updates a file with the specified author" do - valid_params.merge!(author_email: author_email, author_name: author_name, content: "New content") - - put v3_api("/projects/#{project.id}/repository/files", user), valid_params - - expect(response).to have_gitlab_http_status(200) - last_commit = project.repository.commit.raw - expect(last_commit.author_email).to eq(author_email) - expect(last_commit.author_name).to eq(author_name) - end - end - end - - describe "DELETE /projects/:id/repository/files" do - let(:valid_params) do - { - file_path: file_path, - branch_name: 'master', - commit_message: 'Changed file' - } - end - - it "deletes existing file in project repo" do - delete v3_api("/projects/#{project.id}/repository/files", user), valid_params - - expect(response).to have_gitlab_http_status(200) - expect(json_response['file_path']).to eq(file_path) - last_commit = project.repository.commit.raw - expect(last_commit.author_email).to eq(user.email) - expect(last_commit.author_name).to eq(user.name) - end - - it "returns a 400 bad request if no params given" do - delete v3_api("/projects/#{project.id}/repository/files", user) - - expect(response).to have_gitlab_http_status(400) - end - - it "returns a 400 if fails to delete file" do - allow_any_instance_of(Repository).to receive(:delete_file).and_raise(Gitlab::Git::CommitError, 'Cannot delete file') - - delete v3_api("/projects/#{project.id}/repository/files", user), valid_params - - expect(response).to have_gitlab_http_status(400) - end - - context "when specifying an author" do - it "removes a file with the specified author" do - valid_params.merge!(author_email: author_email, author_name: author_name) - - delete v3_api("/projects/#{project.id}/repository/files", user), valid_params - - expect(response).to have_gitlab_http_status(200) - last_commit = project.repository.commit.raw - expect(last_commit.author_email).to eq(author_email) - expect(last_commit.author_name).to eq(author_name) - end - end - end - - describe "POST /projects/:id/repository/files with binary file" do - let(:file_path) { 'test.bin' } - let(:put_params) do - { - file_path: file_path, - branch_name: 'master', - content: 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEUAAACnej3aAAAAAXRSTlMAQObYZgAAAApJREFUCNdjYAAAAAIAAeIhvDMAAAAASUVORK5CYII=', - commit_message: 'Binary file with a \n should not be touched', - encoding: 'base64' - } - end - let(:get_params) do - { - file_path: file_path, - ref: 'master' - } - end - - before do - post v3_api("/projects/#{project.id}/repository/files", user), put_params - end - - it "remains unchanged" do - get v3_api("/projects/#{project.id}/repository/files", user), get_params - - expect(response).to have_gitlab_http_status(200) - expect(json_response['file_path']).to eq(file_path) - expect(json_response['file_name']).to eq(file_path) - expect(json_response['content']).to eq(put_params[:content]) - end - end -end diff --git a/spec/requests/api/v3/groups_spec.rb b/spec/requests/api/v3/groups_spec.rb deleted file mode 100644 index 34d4b8e9565..00000000000 --- a/spec/requests/api/v3/groups_spec.rb +++ /dev/null @@ -1,566 +0,0 @@ -require 'spec_helper' - -describe API::V3::Groups do - include UploadHelpers - - let(:user1) { create(:user, can_create_group: false) } - let(:user2) { create(:user) } - let(:user3) { create(:user) } - let(:admin) { create(:admin) } - let!(:group1) { create(:group, avatar: File.open(uploaded_image_temp_path)) } - let!(:group2) { create(:group, :private) } - let!(:project1) { create(:project, namespace: group1) } - let!(:project2) { create(:project, namespace: group2) } - let!(:project3) { create(:project, namespace: group1, path: 'test', visibility_level: Gitlab::VisibilityLevel::PRIVATE) } - - before do - group1.add_owner(user1) - group2.add_owner(user2) - end - - describe "GET /groups" do - context "when unauthenticated" do - it "returns authentication error" do - get v3_api("/groups") - - expect(response).to have_gitlab_http_status(401) - end - end - - context "when authenticated as user" do - it "normal user: returns an array of groups of user1" do - get v3_api("/groups", user1) - - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.length).to eq(1) - expect(json_response) - .to satisfy_one { |group| group['name'] == group1.name } - end - - it "does not include statistics" do - get v3_api("/groups", user1), statistics: true - - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.first).not_to include 'statistics' - end - end - - context "when authenticated as admin" do - it "admin: returns an array of all groups" do - get v3_api("/groups", admin) - - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.length).to eq(2) - end - - it "does not include statistics by default" do - get v3_api("/groups", admin) - - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.first).not_to include('statistics') - end - - it "includes statistics if requested" do - attributes = { - storage_size: 702, - repository_size: 123, - lfs_objects_size: 234, - build_artifacts_size: 345 - }.stringify_keys - - project1.statistics.update!(attributes) - - get v3_api("/groups", admin), statistics: true - - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response) - .to satisfy_one { |group| group['statistics'] == attributes } - end - end - - context "when using skip_groups in request" do - it "returns all groups excluding skipped groups" do - get v3_api("/groups", admin), skip_groups: [group2.id] - - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.length).to eq(1) - end - end - - context "when using all_available in request" do - let(:response_groups) { json_response.map { |group| group['name'] } } - - it "returns all groups you have access to" do - public_group = create :group, :public - - get v3_api("/groups", user1), all_available: true - - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(response_groups).to contain_exactly(public_group.name, group1.name) - end - end - - context "when using sorting" do - let(:group3) { create(:group, name: "a#{group1.name}", path: "z#{group1.path}") } - let(:response_groups) { json_response.map { |group| group['name'] } } - - before do - group3.add_owner(user1) - end - - it "sorts by name ascending by default" do - get v3_api("/groups", user1) - - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(response_groups).to eq([group3.name, group1.name]) - end - - it "sorts in descending order when passed" do - get v3_api("/groups", user1), sort: "desc" - - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(response_groups).to eq([group1.name, group3.name]) - end - - it "sorts by the order_by param" do - get v3_api("/groups", user1), order_by: "path" - - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(response_groups).to eq([group1.name, group3.name]) - end - end - end - - describe 'GET /groups/owned' do - context 'when unauthenticated' do - it 'returns authentication error' do - get v3_api('/groups/owned') - - expect(response).to have_gitlab_http_status(401) - end - end - - context 'when authenticated as group owner' do - it 'returns an array of groups the user owns' do - get v3_api('/groups/owned', user2) - - expect(response).to have_gitlab_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array - expect(json_response.first['name']).to eq(group2.name) - end - end - end - - describe "GET /groups/:id" do - context "when authenticated as user" do - it "returns one of user1's groups" do - project = create(:project, namespace: group2, path: 'Foo') - create(:project_group_link, project: project, group: group1) - - get v3_api("/groups/#{group1.id}", user1) - - expect(response).to have_gitlab_http_status(200) - expect(json_response['id']).to eq(group1.id) - expect(json_response['name']).to eq(group1.name) - expect(json_response['path']).to eq(group1.path) - expect(json_response['description']).to eq(group1.description) - expect(json_response['visibility_level']).to eq(group1.visibility_level) - expect(json_response['avatar_url']).to eq(group1.avatar_url(only_path: false)) - expect(json_response['web_url']).to eq(group1.web_url) - expect(json_response['request_access_enabled']).to eq(group1.request_access_enabled) - expect(json_response['full_name']).to eq(group1.full_name) - expect(json_response['full_path']).to eq(group1.full_path) - expect(json_response['parent_id']).to eq(group1.parent_id) - expect(json_response['projects']).to be_an Array - expect(json_response['projects'].length).to eq(2) - expect(json_response['shared_projects']).to be_an Array - expect(json_response['shared_projects'].length).to eq(1) - expect(json_response['shared_projects'][0]['id']).to eq(project.id) - end - - it "does not return a non existing group" do - get v3_api("/groups/1328", user1) - - expect(response).to have_gitlab_http_status(404) - end - - it "does not return a group not attached to user1" do - get v3_api("/groups/#{group2.id}", user1) - - expect(response).to have_gitlab_http_status(404) - end - end - - context "when authenticated as admin" do - it "returns any existing group" do - get v3_api("/groups/#{group2.id}", admin) - - expect(response).to have_gitlab_http_status(200) - expect(json_response['name']).to eq(group2.name) - end - - it "does not return a non existing group" do - get v3_api("/groups/1328", admin) - - expect(response).to have_gitlab_http_status(404) - end - end - - context 'when using group path in URL' do - it 'returns any existing group' do - get v3_api("/groups/#{group1.path}", admin) - - expect(response).to have_gitlab_http_status(200) - expect(json_response['name']).to eq(group1.name) - end - - it 'does not return a non existing group' do - get v3_api('/groups/unknown', admin) - - expect(response).to have_gitlab_http_status(404) - end - - it 'does not return a group not attached to user1' do - get v3_api("/groups/#{group2.path}", user1) - - expect(response).to have_gitlab_http_status(404) - end - end - end - - describe 'PUT /groups/:id' do - let(:new_group_name) { 'New Group'} - - context 'when authenticated as the group owner' do - it 'updates the group' do - put v3_api("/groups/#{group1.id}", user1), name: new_group_name, request_access_enabled: true - - expect(response).to have_gitlab_http_status(200) - expect(json_response['name']).to eq(new_group_name) - expect(json_response['request_access_enabled']).to eq(true) - end - - it 'returns 404 for a non existing group' do - put v3_api('/groups/1328', user1), name: new_group_name - - expect(response).to have_gitlab_http_status(404) - end - end - - context 'when authenticated as the admin' do - it 'updates the group' do - put v3_api("/groups/#{group1.id}", admin), name: new_group_name - - expect(response).to have_gitlab_http_status(200) - expect(json_response['name']).to eq(new_group_name) - end - end - - context 'when authenticated as an user that can see the group' do - it 'does not updates the group' do - put v3_api("/groups/#{group1.id}", user2), name: new_group_name - - expect(response).to have_gitlab_http_status(403) - end - end - - context 'when authenticated as an user that cannot see the group' do - it 'returns 404 when trying to update the group' do - put v3_api("/groups/#{group2.id}", user1), name: new_group_name - - expect(response).to have_gitlab_http_status(404) - end - end - end - - describe "GET /groups/:id/projects" do - context "when authenticated as user" do - it "returns the group's projects" do - get v3_api("/groups/#{group1.id}/projects", user1) - - expect(response).to have_gitlab_http_status(200) - expect(json_response.length).to eq(2) - project_names = json_response.map { |proj| proj['name'] } - expect(project_names).to match_array([project1.name, project3.name]) - expect(json_response.first['visibility_level']).to be_present - end - - it "returns the group's projects with simple representation" do - get v3_api("/groups/#{group1.id}/projects", user1), simple: true - - expect(response).to have_gitlab_http_status(200) - expect(json_response.length).to eq(2) - project_names = json_response.map { |proj| proj['name'] } - expect(project_names).to match_array([project1.name, project3.name]) - expect(json_response.first['visibility_level']).not_to be_present - end - - it 'filters the groups projects' do - public_project = create(:project, :public, path: 'test1', group: group1) - - get v3_api("/groups/#{group1.id}/projects", user1), visibility: 'public' - - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an(Array) - expect(json_response.length).to eq(1) - expect(json_response.first['name']).to eq(public_project.name) - end - - it "does not return a non existing group" do - get v3_api("/groups/1328/projects", user1) - - expect(response).to have_gitlab_http_status(404) - end - - it "does not return a group not attached to user1" do - get v3_api("/groups/#{group2.id}/projects", user1) - - expect(response).to have_gitlab_http_status(404) - end - - it "only returns projects to which user has access" do - project3.add_developer(user3) - - get v3_api("/groups/#{group1.id}/projects", user3) - - expect(response).to have_gitlab_http_status(200) - expect(json_response.length).to eq(1) - expect(json_response.first['name']).to eq(project3.name) - end - - it 'only returns the projects owned by user' do - project2.group.add_owner(user3) - - get v3_api("/groups/#{project2.group.id}/projects", user3), owned: true - - expect(response).to have_gitlab_http_status(200) - expect(json_response.length).to eq(1) - expect(json_response.first['name']).to eq(project2.name) - end - - it 'only returns the projects starred by user' do - user1.starred_projects = [project1] - - get v3_api("/groups/#{group1.id}/projects", user1), starred: true - - expect(response).to have_gitlab_http_status(200) - expect(json_response.length).to eq(1) - expect(json_response.first['name']).to eq(project1.name) - end - end - - context "when authenticated as admin" do - it "returns any existing group" do - get v3_api("/groups/#{group2.id}/projects", admin) - - expect(response).to have_gitlab_http_status(200) - expect(json_response.length).to eq(1) - expect(json_response.first['name']).to eq(project2.name) - end - - it "does not return a non existing group" do - get v3_api("/groups/1328/projects", admin) - - expect(response).to have_gitlab_http_status(404) - end - end - - context 'when using group path in URL' do - it 'returns any existing group' do - get v3_api("/groups/#{group1.path}/projects", admin) - - expect(response).to have_gitlab_http_status(200) - project_names = json_response.map { |proj| proj['name'] } - expect(project_names).to match_array([project1.name, project3.name]) - end - - it 'does not return a non existing group' do - get v3_api('/groups/unknown/projects', admin) - - expect(response).to have_gitlab_http_status(404) - end - - it 'does not return a group not attached to user1' do - get v3_api("/groups/#{group2.path}/projects", user1) - - expect(response).to have_gitlab_http_status(404) - end - end - end - - describe "POST /groups" do - context "when authenticated as user without group permissions" do - it "does not create group" do - post v3_api("/groups", user1), attributes_for(:group) - - expect(response).to have_gitlab_http_status(403) - end - end - - context "when authenticated as user with group permissions" do - it "creates group" do - group = attributes_for(:group, { request_access_enabled: false }) - - post v3_api("/groups", user3), group - - expect(response).to have_gitlab_http_status(201) - - expect(json_response["name"]).to eq(group[:name]) - expect(json_response["path"]).to eq(group[:path]) - expect(json_response["request_access_enabled"]).to eq(group[:request_access_enabled]) - end - - it "creates a nested group", :nested_groups do - parent = create(:group) - parent.add_owner(user3) - group = attributes_for(:group, { parent_id: parent.id }) - - post v3_api("/groups", user3), group - - expect(response).to have_gitlab_http_status(201) - - expect(json_response["full_path"]).to eq("#{parent.path}/#{group[:path]}") - expect(json_response["parent_id"]).to eq(parent.id) - end - - it "does not create group, duplicate" do - post v3_api("/groups", user3), { name: 'Duplicate Test', path: group2.path } - - expect(response).to have_gitlab_http_status(400) - expect(response.message).to eq("Bad Request") - end - - it "returns 400 bad request error if name not given" do - post v3_api("/groups", user3), { path: group2.path } - - expect(response).to have_gitlab_http_status(400) - end - - it "returns 400 bad request error if path not given" do - post v3_api("/groups", user3), { name: 'test' } - - expect(response).to have_gitlab_http_status(400) - end - end - end - - describe "DELETE /groups/:id" do - context "when authenticated as user" do - it "removes group" do - Sidekiq::Testing.fake! do - expect { delete v3_api("/groups/#{group1.id}", user1) }.to change(GroupDestroyWorker.jobs, :size).by(1) - end - - expect(response).to have_gitlab_http_status(202) - end - - it "does not remove a group if not an owner" do - user4 = create(:user) - group1.add_master(user4) - - delete v3_api("/groups/#{group1.id}", user3) - - expect(response).to have_gitlab_http_status(403) - end - - it "does not remove a non existing group" do - delete v3_api("/groups/1328", user1) - - expect(response).to have_gitlab_http_status(404) - end - - it "does not remove a group not attached to user1" do - delete v3_api("/groups/#{group2.id}", user1) - - expect(response).to have_gitlab_http_status(404) - end - end - - context "when authenticated as admin" do - it "removes any existing group" do - delete v3_api("/groups/#{group2.id}", admin) - - expect(response).to have_gitlab_http_status(202) - end - - it "does not remove a non existing group" do - delete v3_api("/groups/1328", admin) - - expect(response).to have_gitlab_http_status(404) - end - end - end - - describe "POST /groups/:id/projects/:project_id" do - let(:project) { create(:project) } - let(:project_path) { CGI.escape(project.full_path) } - - before do - allow_any_instance_of(Projects::TransferService) - .to receive(:execute).and_return(true) - end - - context "when authenticated as user" do - it "does not transfer project to group" do - post v3_api("/groups/#{group1.id}/projects/#{project.id}", user2) - - expect(response).to have_gitlab_http_status(403) - end - end - - context "when authenticated as admin" do - it "transfers project to group" do - post v3_api("/groups/#{group1.id}/projects/#{project.id}", admin) - - expect(response).to have_gitlab_http_status(201) - end - - context 'when using project path in URL' do - context 'with a valid project path' do - it "transfers project to group" do - post v3_api("/groups/#{group1.id}/projects/#{project_path}", admin) - - expect(response).to have_gitlab_http_status(201) - end - end - - context 'with a non-existent project path' do - it "does not transfer project to group" do - post v3_api("/groups/#{group1.id}/projects/nogroup%2Fnoproject", admin) - - expect(response).to have_gitlab_http_status(404) - end - end - end - - context 'when using a group path in URL' do - context 'with a valid group path' do - it "transfers project to group" do - post v3_api("/groups/#{group1.path}/projects/#{project_path}", admin) - - expect(response).to have_gitlab_http_status(201) - end - end - - context 'with a non-existent group path' do - it "does not transfer project to group" do - post v3_api("/groups/noexist/projects/#{project_path}", admin) - - expect(response).to have_gitlab_http_status(404) - end - end - end - end - end -end diff --git a/spec/requests/api/v3/issues_spec.rb b/spec/requests/api/v3/issues_spec.rb deleted file mode 100644 index 11b5469be7b..00000000000 --- a/spec/requests/api/v3/issues_spec.rb +++ /dev/null @@ -1,1298 +0,0 @@ -require 'spec_helper' - -describe API::V3::Issues do - set(:user) { create(:user) } - set(:user2) { create(:user) } - set(:non_member) { create(:user) } - set(:guest) { create(:user) } - set(:author) { create(:author) } - set(:assignee) { create(:assignee) } - set(:admin) { create(:user, :admin) } - let!(:project) { create(:project, :public, creator_id: user.id, namespace: user.namespace ) } - let!(:closed_issue) do - create :closed_issue, - author: user, - assignees: [user], - project: project, - state: :closed, - milestone: milestone, - created_at: generate(:past_time), - updated_at: 3.hours.ago - end - let!(:confidential_issue) do - create :issue, - :confidential, - project: project, - author: author, - assignees: [assignee], - created_at: generate(:past_time), - updated_at: 2.hours.ago - end - let!(:issue) do - create :issue, - author: user, - assignees: [user], - project: project, - milestone: milestone, - created_at: generate(:past_time), - updated_at: 1.hour.ago - end - let!(:label) do - create(:label, title: 'label', color: '#FFAABB', project: project) - end - let!(:label_link) { create(:label_link, label: label, target: issue) } - let!(:milestone) { create(:milestone, title: '1.0.0', project: project) } - 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) } - - let(:no_milestone_title) { URI.escape(Milestone::None.title) } - - before do - project.add_reporter(user) - project.add_guest(guest) - end - - describe "GET /issues" do - context "when unauthenticated" do - it "returns authentication error" do - get v3_api("/issues") - - expect(response).to have_gitlab_http_status(401) - end - end - - context "when authenticated" do - it "returns an array of issues" do - get v3_api("/issues", user) - - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.first['title']).to eq(issue.title) - expect(json_response.last).to have_key('web_url') - end - - it 'returns an array of closed issues' do - get v3_api('/issues?state=closed', user) - - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.length).to eq(1) - expect(json_response.first['id']).to eq(closed_issue.id) - end - - it 'returns an array of opened issues' do - get v3_api('/issues?state=opened', user) - - expect(response).to have_gitlab_http_status(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 array of all issues' do - get v3_api('/issues?state=all', user) - - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.length).to eq(2) - expect(json_response.first['id']).to eq(issue.id) - expect(json_response.second['id']).to eq(closed_issue.id) - end - - it 'returns an array of labeled issues' do - get v3_api("/issues?labels=#{label.title}", user) - - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.length).to eq(1) - expect(json_response.first['labels']).to eq([label.title]) - end - - it 'returns an array of labeled issues when at least one label matches' do - get v3_api("/issues?labels=#{label.title},foo,bar", user) - - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.length).to eq(1) - expect(json_response.first['labels']).to eq([label.title]) - end - - it 'returns an empty array if no issue matches labels' do - get v3_api('/issues?labels=foo,bar', user) - - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.length).to eq(0) - end - - it 'returns an array of labeled issues matching given state' do - get v3_api("/issues?labels=#{label.title}&state=opened", user) - - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.length).to eq(1) - expect(json_response.first['labels']).to eq([label.title]) - expect(json_response.first['state']).to eq('opened') - end - - it 'returns an empty array if no issue matches labels and state filters' do - get v3_api("/issues?labels=#{label.title}&state=closed", user) - - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.length).to eq(0) - end - - it 'returns an empty array if no issue matches milestone' do - get v3_api("/issues?milestone=#{empty_milestone.title}", user) - - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.length).to eq(0) - end - - it 'returns an empty array if milestone does not exist' do - get v3_api("/issues?milestone=foo", user) - - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.length).to eq(0) - end - - it 'returns an array of issues in given milestone' do - get v3_api("/issues?milestone=#{milestone.title}", user) - - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.length).to eq(2) - expect(json_response.first['id']).to eq(issue.id) - expect(json_response.second['id']).to eq(closed_issue.id) - end - - it 'returns an array of issues matching state in milestone' do - get v3_api("/issues?milestone=#{milestone.title}", user), - '&state=closed' - - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.length).to eq(1) - expect(json_response.first['id']).to eq(closed_issue.id) - end - - it 'returns an array of issues with no milestone' do - get v3_api("/issues?milestone=#{no_milestone_title}", author) - - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.length).to eq(1) - expect(json_response.first['id']).to eq(confidential_issue.id) - end - - it 'sorts by created_at descending by default' do - get v3_api('/issues', user) - - response_dates = json_response.map { |issue| issue['created_at'] } - - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(response_dates).to eq(response_dates.sort.reverse) - end - - it 'sorts ascending when requested' do - get v3_api('/issues?sort=asc', user) - - response_dates = json_response.map { |issue| issue['created_at'] } - - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(response_dates).to eq(response_dates.sort) - end - - it 'sorts by updated_at descending when requested' do - get v3_api('/issues?order_by=updated_at', user) - - response_dates = json_response.map { |issue| issue['updated_at'] } - - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(response_dates).to eq(response_dates.sort.reverse) - end - - it 'sorts by updated_at ascending when requested' do - get v3_api('/issues?order_by=updated_at&sort=asc', user) - - response_dates = json_response.map { |issue| issue['updated_at'] } - - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(response_dates).to eq(response_dates.sort) - end - - it 'matches V3 response schema' do - get v3_api('/issues', user) - - expect(response).to have_gitlab_http_status(200) - expect(response).to match_response_schema('public_api/v3/issues') - end - end - end - - describe "GET /groups/:id/issues" do - let!(:group) { create(:group) } - let!(:group_project) { create(:project, :public, creator_id: user.id, namespace: group) } - let!(:group_closed_issue) do - create :closed_issue, - author: user, - assignees: [user], - project: group_project, - state: :closed, - milestone: group_milestone, - updated_at: 3.hours.ago - end - let!(:group_confidential_issue) do - create :issue, - :confidential, - project: group_project, - author: author, - assignees: [assignee], - updated_at: 2.hours.ago - end - let!(:group_issue) do - create :issue, - author: user, - assignees: [user], - project: group_project, - milestone: group_milestone, - updated_at: 1.hour.ago - end - let!(:group_label) do - create(:label, title: 'group_lbl', color: '#FFAABB', project: group_project) - end - let!(:group_label_link) { create(:label_link, label: group_label, target: group_issue) } - let!(:group_milestone) { create(:milestone, title: '3.0.0', project: group_project) } - let!(:group_empty_milestone) do - create(:milestone, title: '4.0.0', project: group_project) - end - let!(:group_note) { create(:note_on_issue, author: user, project: group_project, noteable: group_issue) } - - before do - group_project.add_reporter(user) - end - let(:base_url) { "/groups/#{group.id}/issues" } - - it 'returns all group issues (including opened and closed)' do - get v3_api(base_url, admin) - - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.length).to eq(3) - end - - it 'returns group issues without confidential issues for non project members' do - get v3_api("#{base_url}?state=opened", non_member) - - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.length).to eq(1) - expect(json_response.first['title']).to eq(group_issue.title) - end - - it 'returns group confidential issues for author' do - get v3_api("#{base_url}?state=opened", author) - - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.length).to eq(2) - end - - it 'returns group confidential issues for assignee' do - get v3_api("#{base_url}?state=opened", assignee) - - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.length).to eq(2) - end - - it 'returns group issues with confidential issues for project members' do - get v3_api("#{base_url}?state=opened", user) - - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.length).to eq(2) - end - - it 'returns group confidential issues for admin' do - get v3_api("#{base_url}?state=opened", admin) - - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.length).to eq(2) - end - - it 'returns an array of labeled group issues' do - get v3_api("#{base_url}?labels=#{group_label.title}", user) - - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.length).to eq(1) - expect(json_response.first['labels']).to eq([group_label.title]) - end - - it 'returns an array of labeled group issues where all labels match' do - get v3_api("#{base_url}?labels=#{group_label.title},foo,bar", user) - - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.length).to eq(0) - end - - it 'returns an empty array if no group issue matches labels' do - get v3_api("#{base_url}?labels=foo,bar", user) - - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.length).to eq(0) - end - - it 'returns an empty array if no issue matches milestone' do - get v3_api("#{base_url}?milestone=#{group_empty_milestone.title}", user) - - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.length).to eq(0) - end - - it 'returns an empty array if milestone does not exist' do - get v3_api("#{base_url}?milestone=foo", user) - - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.length).to eq(0) - end - - it 'returns an array of issues in given milestone' do - get v3_api("#{base_url}?state=opened&milestone=#{group_milestone.title}", user) - - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.length).to eq(1) - expect(json_response.first['id']).to eq(group_issue.id) - end - - it 'returns an array of issues matching state in milestone' do - get v3_api("#{base_url}?milestone=#{group_milestone.title}", user), - '&state=closed' - - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.length).to eq(1) - expect(json_response.first['id']).to eq(group_closed_issue.id) - end - - it 'returns an array of issues with no milestone' do - get v3_api("#{base_url}?milestone=#{no_milestone_title}", user) - - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.length).to eq(1) - expect(json_response.first['id']).to eq(group_confidential_issue.id) - end - - it 'sorts by created_at descending by default' do - get v3_api(base_url, user) - - response_dates = json_response.map { |issue| issue['created_at'] } - - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(response_dates).to eq(response_dates.sort.reverse) - end - - it 'sorts ascending when requested' do - get v3_api("#{base_url}?sort=asc", user) - - response_dates = json_response.map { |issue| issue['created_at'] } - - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(response_dates).to eq(response_dates.sort) - end - - it 'sorts by updated_at descending when requested' do - get v3_api("#{base_url}?order_by=updated_at", user) - - response_dates = json_response.map { |issue| issue['updated_at'] } - - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(response_dates).to eq(response_dates.sort.reverse) - end - - it 'sorts by updated_at ascending when requested' do - get v3_api("#{base_url}?order_by=updated_at&sort=asc", user) - - response_dates = json_response.map { |issue| issue['updated_at'] } - - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(response_dates).to eq(response_dates.sort) - end - end - - describe "GET /projects/:id/issues" do - let(:base_url) { "/projects/#{project.id}" } - - it 'returns 404 when project does not exist' do - get v3_api('/projects/1000/issues', non_member) - - expect(response).to have_gitlab_http_status(404) - end - - it "returns 404 on private projects for other users" do - private_project = create(:project, :private) - create(:issue, project: private_project) - - get v3_api("/projects/#{private_project.id}/issues", non_member) - - expect(response).to have_gitlab_http_status(404) - end - - it 'returns no issues when user has access to project but not issues' do - restricted_project = create(:project, :public, issues_access_level: ProjectFeature::PRIVATE) - create(:issue, project: restricted_project) - - get v3_api("/projects/#{restricted_project.id}/issues", non_member) - - expect(json_response).to eq([]) - end - - it 'returns project issues without confidential issues for non project members' do - get v3_api("#{base_url}/issues", non_member) - - expect(response).to have_gitlab_http_status(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 'returns project issues without confidential issues for project members with guest role' do - get v3_api("#{base_url}/issues", guest) - - expect(response).to have_gitlab_http_status(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 'returns project confidential issues for author' do - get v3_api("#{base_url}/issues", author) - - expect(response).to have_gitlab_http_status(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 'returns project confidential issues for assignee' do - get v3_api("#{base_url}/issues", assignee) - - expect(response).to have_gitlab_http_status(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 'returns project issues with confidential issues for project members' do - get v3_api("#{base_url}/issues", user) - - expect(response).to have_gitlab_http_status(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 'returns project confidential issues for admin' do - get v3_api("#{base_url}/issues", admin) - - expect(response).to have_gitlab_http_status(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 'returns an array of labeled project issues' do - get v3_api("#{base_url}/issues?labels=#{label.title}", user) - - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.length).to eq(1) - expect(json_response.first['labels']).to eq([label.title]) - end - - it 'returns an array of labeled project issues where all labels match' do - get v3_api("#{base_url}/issues?labels=#{label.title},foo,bar", user) - - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.length).to eq(1) - expect(json_response.first['labels']).to eq([label.title]) - end - - it 'returns an empty array if no project issue matches labels' do - get v3_api("#{base_url}/issues?labels=foo,bar", user) - - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.length).to eq(0) - end - - it 'returns an empty array if no issue matches milestone' do - get v3_api("#{base_url}/issues?milestone=#{empty_milestone.title}", user) - - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.length).to eq(0) - end - - it 'returns an empty array if milestone does not exist' do - get v3_api("#{base_url}/issues?milestone=foo", user) - - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.length).to eq(0) - end - - it 'returns an array of issues in given milestone' do - get v3_api("#{base_url}/issues?milestone=#{milestone.title}", user) - - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.length).to eq(2) - expect(json_response.first['id']).to eq(issue.id) - expect(json_response.second['id']).to eq(closed_issue.id) - end - - it 'returns an array of issues matching state in milestone' do - get v3_api("#{base_url}/issues?milestone=#{milestone.title}", user), - '&state=closed' - - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.length).to eq(1) - expect(json_response.first['id']).to eq(closed_issue.id) - end - - it 'returns an array of issues with no milestone' do - get v3_api("#{base_url}/issues?milestone=#{no_milestone_title}", user) - - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.length).to eq(1) - expect(json_response.first['id']).to eq(confidential_issue.id) - end - - it 'sorts by created_at descending by default' do - get v3_api("#{base_url}/issues", user) - - response_dates = json_response.map { |issue| issue['created_at'] } - - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(response_dates).to eq(response_dates.sort.reverse) - end - - it 'sorts ascending when requested' do - get v3_api("#{base_url}/issues?sort=asc", user) - - response_dates = json_response.map { |issue| issue['created_at'] } - - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(response_dates).to eq(response_dates.sort) - end - - it 'sorts by updated_at descending when requested' do - get v3_api("#{base_url}/issues?order_by=updated_at", user) - - response_dates = json_response.map { |issue| issue['updated_at'] } - - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(response_dates).to eq(response_dates.sort.reverse) - end - - it 'sorts by updated_at ascending when requested' do - get v3_api("#{base_url}/issues?order_by=updated_at&sort=asc", user) - - response_dates = json_response.map { |issue| issue['updated_at'] } - - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(response_dates).to eq(response_dates.sort) - end - end - - describe "GET /projects/:id/issues/:issue_id" do - it 'exposes known attributes' do - get v3_api("/projects/#{project.id}/issues/#{issue.id}", user) - - expect(response).to have_gitlab_http_status(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 - expect(json_response['confidential']).to be_falsy - end - - it "returns a project issue by id" do - get v3_api("/projects/#{project.id}/issues/#{issue.id}", user) - - expect(response).to have_gitlab_http_status(200) - expect(json_response['title']).to eq(issue.title) - expect(json_response['iid']).to eq(issue.iid) - end - - it 'returns a project issue by iid' do - get v3_api("/projects/#{project.id}/issues?iid=#{issue.iid}", user) - - expect(response.status).to eq 200 - 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 - expect(json_response.first['iid']).to eq issue.iid - end - - it 'returns an empty array for an unknown project issue iid' do - get v3_api("/projects/#{project.id}/issues?iid=#{issue.iid + 10}", user) - - expect(response.status).to eq 200 - expect(json_response.length).to eq 0 - end - - it "returns 404 if issue id not found" do - get v3_api("/projects/#{project.id}/issues/54321", user) - - expect(response).to have_gitlab_http_status(404) - end - - context 'confidential issues' do - it "returns 404 for non project members" do - get v3_api("/projects/#{project.id}/issues/#{confidential_issue.id}", non_member) - - expect(response).to have_gitlab_http_status(404) - end - - it "returns 404 for project members with guest role" do - get v3_api("/projects/#{project.id}/issues/#{confidential_issue.id}", guest) - - expect(response).to have_gitlab_http_status(404) - end - - it "returns confidential issue for project members" do - get v3_api("/projects/#{project.id}/issues/#{confidential_issue.id}", user) - - expect(response).to have_gitlab_http_status(200) - expect(json_response['title']).to eq(confidential_issue.title) - expect(json_response['iid']).to eq(confidential_issue.iid) - end - - it "returns confidential issue for author" do - get v3_api("/projects/#{project.id}/issues/#{confidential_issue.id}", author) - - expect(response).to have_gitlab_http_status(200) - expect(json_response['title']).to eq(confidential_issue.title) - expect(json_response['iid']).to eq(confidential_issue.iid) - end - - it "returns confidential issue for assignee" do - get v3_api("/projects/#{project.id}/issues/#{confidential_issue.id}", assignee) - - expect(response).to have_gitlab_http_status(200) - expect(json_response['title']).to eq(confidential_issue.title) - expect(json_response['iid']).to eq(confidential_issue.iid) - end - - it "returns confidential issue for admin" do - get v3_api("/projects/#{project.id}/issues/#{confidential_issue.id}", admin) - - expect(response).to have_gitlab_http_status(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 - it 'creates a new project issue' do - post v3_api("/projects/#{project.id}/issues", user), - title: 'new issue', labels: 'label, label2', assignee_id: assignee.id - - expect(response).to have_gitlab_http_status(201) - expect(json_response['title']).to eq('new issue') - expect(json_response['description']).to be_nil - expect(json_response['labels']).to eq(%w(label label2)) - expect(json_response['confidential']).to be_falsy - expect(json_response['assignee']['name']).to eq(assignee.name) - end - - it 'creates a new confidential project issue' do - post v3_api("/projects/#{project.id}/issues", user), - title: 'new issue', confidential: true - - expect(response).to have_gitlab_http_status(201) - expect(json_response['title']).to eq('new issue') - expect(json_response['confidential']).to be_truthy - end - - it 'creates a new confidential project issue with a different param' do - post v3_api("/projects/#{project.id}/issues", user), - title: 'new issue', confidential: 'y' - - expect(response).to have_gitlab_http_status(201) - expect(json_response['title']).to eq('new issue') - expect(json_response['confidential']).to be_truthy - end - - it 'creates a public issue when confidential param is false' do - post v3_api("/projects/#{project.id}/issues", user), - title: 'new issue', confidential: false - - expect(response).to have_gitlab_http_status(201) - expect(json_response['title']).to eq('new issue') - expect(json_response['confidential']).to be_falsy - end - - it 'creates a public issue when confidential param is invalid' do - post v3_api("/projects/#{project.id}/issues", user), - title: 'new issue', confidential: 'foo' - - expect(response).to have_gitlab_http_status(400) - expect(json_response['error']).to eq('confidential is invalid') - end - - it "returns a 400 bad request if title not given" do - post v3_api("/projects/#{project.id}/issues", user), labels: 'label, label2' - - expect(response).to have_gitlab_http_status(400) - end - - it 'allows special label names' do - post v3_api("/projects/#{project.id}/issues", user), - title: 'new issue', - labels: 'label, label?, label&foo, ?, &' - - expect(response.status).to eq(201) - expect(json_response['labels']).to include 'label' - expect(json_response['labels']).to include 'label?' - expect(json_response['labels']).to include 'label&foo' - expect(json_response['labels']).to include '?' - expect(json_response['labels']).to include '&' - end - - it 'returns 400 if title is too long' do - post v3_api("/projects/#{project.id}/issues", user), - title: 'g' * 256 - - expect(response).to have_gitlab_http_status(400) - expect(json_response['message']['title']).to eq([ - 'is too long (maximum is 255 characters)' - ]) - end - - context 'resolving issues in a merge request' do - set(:diff_note_on_merge_request) { create(:diff_note_on_merge_request) } - let(:discussion) { diff_note_on_merge_request.to_discussion } - let(:merge_request) { discussion.noteable } - let(:project) { merge_request.source_project } - before do - project.add_master(user) - post v3_api("/projects/#{project.id}/issues", user), - title: 'New Issue', - merge_request_for_resolving_discussions: merge_request.iid - end - - it 'creates a new project issue' do - expect(response).to have_gitlab_http_status(:created) - end - - it 'resolves the discussions in a merge request' do - discussion.first_note.reload - - expect(discussion.resolved?).to be(true) - end - - it 'assigns a description to the issue mentioning the merge request' do - expect(json_response['description']).to include(merge_request.to_reference) - end - end - - context 'with due date' do - it 'creates a new project issue' do - due_date = 2.weeks.from_now.strftime('%Y-%m-%d') - - post v3_api("/projects/#{project.id}/issues", user), - title: 'new issue', due_date: due_date - - expect(response).to have_gitlab_http_status(201) - expect(json_response['title']).to eq('new issue') - expect(json_response['description']).to be_nil - expect(json_response['due_date']).to eq(due_date) - end - end - - context 'when an admin or owner makes the request' do - it 'accepts the creation date to be set' do - creation_time = 2.weeks.ago - post v3_api("/projects/#{project.id}/issues", user), - title: 'new issue', labels: 'label, label2', created_at: creation_time - - expect(response).to have_gitlab_http_status(201) - expect(Time.parse(json_response['created_at'])).to be_like_time(creation_time) - end - end - - context 'the user can only read the issue' do - it 'cannot create new labels' do - expect do - post v3_api("/projects/#{project.id}/issues", non_member), title: 'new issue', labels: 'label, label2' - end.not_to change { project.labels.count } - end - end - end - - describe 'POST /projects/:id/issues with spam filtering' do - before do - allow_any_instance_of(SpamService).to receive(:check_for_spam?).and_return(true) - allow_any_instance_of(AkismetService).to receive_messages(spam?: true) - end - - let(:params) do - { - title: 'new issue', - description: 'content here', - labels: 'label, label2' - } - end - - it "does not create a new project issue" do - expect { post v3_api("/projects/#{project.id}/issues", user), params }.not_to change(Issue, :count) - - expect(response).to have_gitlab_http_status(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') - end - end - - describe "PUT /projects/:id/issues/:issue_id to update only title" do - it "updates a project issue" do - put v3_api("/projects/#{project.id}/issues/#{issue.id}", user), - title: 'updated title' - - expect(response).to have_gitlab_http_status(200) - expect(json_response['title']).to eq('updated title') - end - - it "returns 404 error if issue id not found" do - put v3_api("/projects/#{project.id}/issues/44444", user), - title: 'updated title' - - expect(response).to have_gitlab_http_status(404) - end - - it 'allows special label names' do - put v3_api("/projects/#{project.id}/issues/#{issue.id}", user), - title: 'updated title', - labels: 'label, label?, label&foo, ?, &' - - expect(response.status).to eq(200) - expect(json_response['labels']).to include 'label' - expect(json_response['labels']).to include 'label?' - expect(json_response['labels']).to include 'label&foo' - expect(json_response['labels']).to include '?' - expect(json_response['labels']).to include '&' - end - - context 'confidential issues' do - it "returns 403 for non project members" do - put v3_api("/projects/#{project.id}/issues/#{confidential_issue.id}", non_member), - title: 'updated title' - - expect(response).to have_gitlab_http_status(403) - end - - it "returns 403 for project members with guest role" do - put v3_api("/projects/#{project.id}/issues/#{confidential_issue.id}", guest), - title: 'updated title' - - expect(response).to have_gitlab_http_status(403) - end - - it "updates a confidential issue for project members" do - put v3_api("/projects/#{project.id}/issues/#{confidential_issue.id}", user), - title: 'updated title' - - expect(response).to have_gitlab_http_status(200) - expect(json_response['title']).to eq('updated title') - end - - it "updates a confidential issue for author" do - put v3_api("/projects/#{project.id}/issues/#{confidential_issue.id}", author), - title: 'updated title' - - expect(response).to have_gitlab_http_status(200) - expect(json_response['title']).to eq('updated title') - end - - it "updates a confidential issue for admin" do - put v3_api("/projects/#{project.id}/issues/#{confidential_issue.id}", admin), - title: 'updated title' - - expect(response).to have_gitlab_http_status(200) - expect(json_response['title']).to eq('updated title') - end - - it 'sets an issue to confidential' do - put v3_api("/projects/#{project.id}/issues/#{issue.id}", user), - confidential: true - - expect(response).to have_gitlab_http_status(200) - expect(json_response['confidential']).to be_truthy - end - - it 'makes a confidential issue public' do - put v3_api("/projects/#{project.id}/issues/#{confidential_issue.id}", user), - confidential: false - - expect(response).to have_gitlab_http_status(200) - expect(json_response['confidential']).to be_falsy - end - - it 'does not update a confidential issue with wrong confidential flag' do - put v3_api("/projects/#{project.id}/issues/#{confidential_issue.id}", user), - confidential: 'foo' - - expect(response).to have_gitlab_http_status(400) - expect(json_response['error']).to eq('confidential is invalid') - end - end - end - - describe 'PUT /projects/:id/issues/:issue_id with spam filtering' do - let(:params) do - { - title: 'updated title', - description: 'content here', - labels: 'label, label2' - } - end - - it "does not create a new project issue" do - allow_any_instance_of(SpamService).to receive_messages(check_for_spam?: true) - allow_any_instance_of(AkismetService).to receive_messages(spam?: true) - - put v3_api("/projects/#{project.id}/issues/#{issue.id}", user), params - - expect(response).to have_gitlab_http_status(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('updated title') - 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') - end - end - - describe 'PUT /projects/:id/issues/:issue_id to update labels' do - let!(:label) { create(:label, title: 'dummy', project: project) } - let!(:label_link) { create(:label_link, label: label, target: issue) } - - it 'does not update labels if not present' do - put v3_api("/projects/#{project.id}/issues/#{issue.id}", user), - title: 'updated title' - - expect(response).to have_gitlab_http_status(200) - expect(json_response['labels']).to eq([label.title]) - end - - it 'removes all labels' do - put v3_api("/projects/#{project.id}/issues/#{issue.id}", user), labels: '' - - expect(response).to have_gitlab_http_status(200) - expect(json_response['labels']).to eq([]) - end - - it 'updates labels' do - put v3_api("/projects/#{project.id}/issues/#{issue.id}", user), - labels: 'foo,bar' - - expect(response).to have_gitlab_http_status(200) - expect(json_response['labels']).to include 'foo' - expect(json_response['labels']).to include 'bar' - end - - it 'allows special label names' do - put v3_api("/projects/#{project.id}/issues/#{issue.id}", user), - labels: 'label:foo, label-bar,label_bar,label/bar,label?bar,label&bar,?,&' - - expect(response.status).to eq(200) - expect(json_response['labels']).to include 'label:foo' - expect(json_response['labels']).to include 'label-bar' - expect(json_response['labels']).to include 'label_bar' - expect(json_response['labels']).to include 'label/bar' - expect(json_response['labels']).to include 'label?bar' - expect(json_response['labels']).to include 'label&bar' - expect(json_response['labels']).to include '?' - expect(json_response['labels']).to include '&' - end - - it 'returns 400 if title is too long' do - put v3_api("/projects/#{project.id}/issues/#{issue.id}", user), - title: 'g' * 256 - - expect(response).to have_gitlab_http_status(400) - expect(json_response['message']['title']).to eq([ - 'is too long (maximum is 255 characters)' - ]) - end - end - - describe "PUT /projects/:id/issues/:issue_id to update state and label" do - it "updates a project issue" do - put v3_api("/projects/#{project.id}/issues/#{issue.id}", user), - labels: 'label2', state_event: "close" - - expect(response).to have_gitlab_http_status(200) - expect(json_response['labels']).to include 'label2' - expect(json_response['state']).to eq "closed" - end - - it 'reopens a project isssue' do - put v3_api("/projects/#{project.id}/issues/#{closed_issue.id}", user), state_event: 'reopen' - - expect(response).to have_gitlab_http_status(200) - expect(json_response['state']).to eq 'opened' - end - - context 'when an admin or owner makes the request' do - it 'accepts the update date to be set' do - update_time = 2.weeks.ago - put v3_api("/projects/#{project.id}/issues/#{issue.id}", user), - labels: 'label3', state_event: 'close', updated_at: update_time - - expect(response).to have_gitlab_http_status(200) - expect(json_response['labels']).to include 'label3' - expect(Time.parse(json_response['updated_at'])).to be_like_time(update_time) - end - end - end - - describe 'PUT /projects/:id/issues/:issue_id to update due date' do - it 'creates a new project issue' do - due_date = 2.weeks.from_now.strftime('%Y-%m-%d') - - put v3_api("/projects/#{project.id}/issues/#{issue.id}", user), due_date: due_date - - expect(response).to have_gitlab_http_status(200) - expect(json_response['due_date']).to eq(due_date) - end - end - - describe 'PUT /projects/:id/issues/:issue_id to update assignee' do - it 'updates an issue with no assignee' do - put v3_api("/projects/#{project.id}/issues/#{issue.id}", user), assignee_id: 0 - - expect(response).to have_gitlab_http_status(200) - expect(json_response['assignee']).to eq(nil) - end - - it 'updates an issue with assignee' do - put v3_api("/projects/#{project.id}/issues/#{issue.id}", user), assignee_id: user2.id - - expect(response).to have_gitlab_http_status(200) - expect(json_response['assignee']['name']).to eq(user2.name) - end - end - - describe "DELETE /projects/:id/issues/:issue_id" do - it "rejects a non member from deleting an issue" do - delete v3_api("/projects/#{project.id}/issues/#{issue.id}", non_member) - - expect(response).to have_gitlab_http_status(403) - end - - it "rejects a developer from deleting an issue" do - delete v3_api("/projects/#{project.id}/issues/#{issue.id}", author) - - expect(response).to have_gitlab_http_status(403) - end - - context "when the user is project owner" do - set(:owner) { create(:user) } - let(:project) { create(:project, namespace: owner.namespace) } - - it "deletes the issue if an admin requests it" do - delete v3_api("/projects/#{project.id}/issues/#{issue.id}", owner) - - expect(response).to have_gitlab_http_status(200) - expect(json_response['state']).to eq 'opened' - end - end - - context 'when issue does not exist' do - it 'returns 404 when trying to move an issue' do - delete v3_api("/projects/#{project.id}/issues/123", user) - - expect(response).to have_gitlab_http_status(404) - end - end - end - - describe '/projects/:id/issues/:issue_id/move' do - let!(:target_project) { create(:project, creator_id: user.id, namespace: user.namespace ) } - let!(:target_project2) { create(:project, creator_id: non_member.id, namespace: non_member.namespace ) } - - it 'moves an issue' do - post v3_api("/projects/#{project.id}/issues/#{issue.id}/move", user), - to_project_id: target_project.id - - expect(response).to have_gitlab_http_status(201) - expect(json_response['project_id']).to eq(target_project.id) - end - - context 'when source and target projects are the same' do - it 'returns 400 when trying to move an issue' do - post v3_api("/projects/#{project.id}/issues/#{issue.id}/move", user), - to_project_id: project.id - - expect(response).to have_gitlab_http_status(400) - expect(json_response['message']).to eq('Cannot move issue to project it originates from!') - end - end - - context 'when the user does not have the permission to move issues' do - it 'returns 400 when trying to move an issue' do - post v3_api("/projects/#{project.id}/issues/#{issue.id}/move", user), - to_project_id: target_project2.id - - expect(response).to have_gitlab_http_status(400) - expect(json_response['message']).to eq('Cannot move issue due to insufficient permissions!') - end - end - - it 'moves the issue to another namespace if I am admin' do - post v3_api("/projects/#{project.id}/issues/#{issue.id}/move", admin), - to_project_id: target_project2.id - - expect(response).to have_gitlab_http_status(201) - expect(json_response['project_id']).to eq(target_project2.id) - end - - context 'when issue does not exist' do - it 'returns 404 when trying to move an issue' do - post v3_api("/projects/#{project.id}/issues/123/move", user), - to_project_id: target_project.id - - expect(response).to have_gitlab_http_status(404) - expect(json_response['message']).to eq('404 Issue Not Found') - end - end - - context 'when source project does not exist' do - it 'returns 404 when trying to move an issue' do - post v3_api("/projects/0/issues/#{issue.id}/move", user), - to_project_id: target_project.id - - expect(response).to have_gitlab_http_status(404) - expect(json_response['message']).to eq('404 Project Not Found') - end - end - - context 'when target project does not exist' do - it 'returns 404 when trying to move an issue' do - post v3_api("/projects/#{project.id}/issues/#{issue.id}/move", user), - to_project_id: 0 - - expect(response).to have_gitlab_http_status(404) - end - end - end - - describe 'POST :id/issues/:issue_id/subscription' do - it 'subscribes to an issue' do - post v3_api("/projects/#{project.id}/issues/#{issue.id}/subscription", user2) - - expect(response).to have_gitlab_http_status(201) - expect(json_response['subscribed']).to eq(true) - end - - it 'returns 304 if already subscribed' do - post v3_api("/projects/#{project.id}/issues/#{issue.id}/subscription", user) - - expect(response).to have_gitlab_http_status(304) - end - - it 'returns 404 if the issue is not found' do - post v3_api("/projects/#{project.id}/issues/123/subscription", user) - - expect(response).to have_gitlab_http_status(404) - end - - it 'returns 404 if the issue is confidential' do - post v3_api("/projects/#{project.id}/issues/#{confidential_issue.id}/subscription", non_member) - - expect(response).to have_gitlab_http_status(404) - end - end - - describe 'DELETE :id/issues/:issue_id/subscription' do - it 'unsubscribes from an issue' do - delete v3_api("/projects/#{project.id}/issues/#{issue.id}/subscription", user) - - expect(response).to have_gitlab_http_status(200) - expect(json_response['subscribed']).to eq(false) - end - - it 'returns 304 if not subscribed' do - delete v3_api("/projects/#{project.id}/issues/#{issue.id}/subscription", user2) - - expect(response).to have_gitlab_http_status(304) - end - - it 'returns 404 if the issue is not found' do - delete v3_api("/projects/#{project.id}/issues/123/subscription", user) - - expect(response).to have_gitlab_http_status(404) - end - - it 'returns 404 if the issue is confidential' do - delete v3_api("/projects/#{project.id}/issues/#{confidential_issue.id}/subscription", non_member) - - expect(response).to have_gitlab_http_status(404) - end - end - - describe 'time tracking endpoints' do - let(:issuable) { issue } - - include_examples 'V3 time tracking endpoints', 'issue' - end -end diff --git a/spec/requests/api/v3/labels_spec.rb b/spec/requests/api/v3/labels_spec.rb deleted file mode 100644 index cdab4d2bd73..00000000000 --- a/spec/requests/api/v3/labels_spec.rb +++ /dev/null @@ -1,169 +0,0 @@ -require 'spec_helper' - -describe API::V3::Labels do - let(:user) { create(:user) } - let(:project) { create(:project, creator_id: user.id, namespace: user.namespace) } - let!(:label1) { create(:label, title: 'label1', project: project) } - let!(:priority_label) { create(:label, title: 'bug', project: project, priority: 3) } - - before do - project.add_master(user) - end - - describe 'GET /projects/:id/labels' do - it 'returns all available labels to the project' do - group = create(:group) - group_label = create(:group_label, title: 'feature', group: group) - project.update(group: group) - create(:labeled_issue, project: project, labels: [group_label], author: user) - create(:labeled_issue, project: project, labels: [label1], author: user, state: :closed) - create(:labeled_merge_request, labels: [priority_label], author: user, source_project: project ) - - expected_keys = %w( - id name color description - open_issues_count closed_issues_count open_merge_requests_count - subscribed priority - ) - - get v3_api("/projects/#{project.id}/labels", user) - - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.size).to eq(3) - expect(json_response.first.keys).to match_array expected_keys - expect(json_response.map { |l| l['name'] }).to match_array([group_label.name, priority_label.name, label1.name]) - - label1_response = json_response.find { |l| l['name'] == label1.title } - group_label_response = json_response.find { |l| l['name'] == group_label.title } - priority_label_response = json_response.find { |l| l['name'] == priority_label.title } - - expect(label1_response['open_issues_count']).to eq(0) - expect(label1_response['closed_issues_count']).to eq(1) - expect(label1_response['open_merge_requests_count']).to eq(0) - expect(label1_response['name']).to eq(label1.name) - expect(label1_response['color']).to be_present - expect(label1_response['description']).to be_nil - expect(label1_response['priority']).to be_nil - expect(label1_response['subscribed']).to be_falsey - - expect(group_label_response['open_issues_count']).to eq(1) - expect(group_label_response['closed_issues_count']).to eq(0) - expect(group_label_response['open_merge_requests_count']).to eq(0) - expect(group_label_response['name']).to eq(group_label.name) - expect(group_label_response['color']).to be_present - expect(group_label_response['description']).to be_nil - expect(group_label_response['priority']).to be_nil - expect(group_label_response['subscribed']).to be_falsey - - expect(priority_label_response['open_issues_count']).to eq(0) - expect(priority_label_response['closed_issues_count']).to eq(0) - expect(priority_label_response['open_merge_requests_count']).to eq(1) - expect(priority_label_response['name']).to eq(priority_label.name) - expect(priority_label_response['color']).to be_present - expect(priority_label_response['description']).to be_nil - expect(priority_label_response['priority']).to eq(3) - expect(priority_label_response['subscribed']).to be_falsey - end - end - - describe "POST /projects/:id/labels/:label_id/subscription" do - context "when label_id is a label title" do - it "subscribes to the label" do - post v3_api("/projects/#{project.id}/labels/#{label1.title}/subscription", user) - - expect(response).to have_gitlab_http_status(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 "subscribes to the label" do - post v3_api("/projects/#{project.id}/labels/#{label1.id}/subscription", user) - - expect(response).to have_gitlab_http_status(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, project) } - - it "returns 304" do - post v3_api("/projects/#{project.id}/labels/#{label1.id}/subscription", user) - - expect(response).to have_gitlab_http_status(304) - end - end - - context "when label ID is not found" do - it "returns 404 error" do - post v3_api("/projects/#{project.id}/labels/1234/subscription", user) - - expect(response).to have_gitlab_http_status(404) - end - end - end - - describe "DELETE /projects/:id/labels/:label_id/subscription" do - before { label1.subscribe(user, project) } - - context "when label_id is a label title" do - it "unsubscribes from the label" do - delete v3_api("/projects/#{project.id}/labels/#{label1.title}/subscription", user) - - expect(response).to have_gitlab_http_status(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 "unsubscribes from the label" do - delete v3_api("/projects/#{project.id}/labels/#{label1.id}/subscription", user) - - expect(response).to have_gitlab_http_status(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, project) } - - it "returns 304" do - delete v3_api("/projects/#{project.id}/labels/#{label1.id}/subscription", user) - - expect(response).to have_gitlab_http_status(304) - end - end - - context "when label ID is not found" do - it "returns 404 error" do - delete v3_api("/projects/#{project.id}/labels/1234/subscription", user) - - expect(response).to have_gitlab_http_status(404) - end - end - end - - describe 'DELETE /projects/:id/labels' do - it 'returns 200 for existing label' do - delete v3_api("/projects/#{project.id}/labels", user), name: 'label1' - - expect(response).to have_gitlab_http_status(200) - end - - it 'returns 404 for non existing label' do - delete v3_api("/projects/#{project.id}/labels", user), name: 'label2' - expect(response).to have_gitlab_http_status(404) - expect(json_response['message']).to eq('404 Label Not Found') - end - - it 'returns 400 for wrong parameters' do - delete v3_api("/projects/#{project.id}/labels", user) - expect(response).to have_gitlab_http_status(400) - end - end -end diff --git a/spec/requests/api/v3/members_spec.rb b/spec/requests/api/v3/members_spec.rb deleted file mode 100644 index de4339ecb8b..00000000000 --- a/spec/requests/api/v3/members_spec.rb +++ /dev/null @@ -1,350 +0,0 @@ -require 'spec_helper' - -describe API::V3::Members do - let(:master) { create(:user, username: 'master_user') } - let(:developer) { create(:user) } - let(:access_requester) { create(:user) } - let(:stranger) { create(:user) } - - let(:project) do - create(:project, :public, :access_requestable, creator_id: master.id, namespace: master.namespace) do |project| - project.add_developer(developer) - project.add_master(master) - project.request_access(access_requester) - end - end - - let!(:group) do - create(:group, :public, :access_requestable) do |group| - group.add_developer(developer) - group.add_owner(master) - group.request_access(access_requester) - end - end - - shared_examples 'GET /:sources/:id/members' do |source_type| - context "with :sources == #{source_type.pluralize}" do - it_behaves_like 'a 404 response when source is private' do - let(:route) { get v3_api("/#{source_type.pluralize}/#{source.id}/members", stranger) } - end - - %i[master developer access_requester stranger].each do |type| - context "when authenticated as a #{type}" do - it 'returns 200' do - user = public_send(type) - get v3_api("/#{source_type.pluralize}/#{source.id}/members", user) - - expect(response).to have_gitlab_http_status(200) - expect(json_response.size).to eq(2) - expect(json_response.map { |u| u['id'] }).to match_array [master.id, developer.id] - end - end - end - - it 'does not return invitees' do - create(:"#{source_type}_member", invite_token: '123', invite_email: 'test@abc.com', source: source, user: nil) - - get v3_api("/#{source_type.pluralize}/#{source.id}/members", developer) - - expect(response).to have_gitlab_http_status(200) - expect(json_response.size).to eq(2) - expect(json_response.map { |u| u['id'] }).to match_array [master.id, developer.id] - end - - it 'finds members with query string' do - get v3_api("/#{source_type.pluralize}/#{source.id}/members", developer), query: master.username - - expect(response).to have_gitlab_http_status(200) - expect(json_response.count).to eq(1) - expect(json_response.first['username']).to eq(master.username) - end - - it 'finds all members with no query specified' do - get v3_api("/#{source_type.pluralize}/#{source.id}/members", developer), query: '' - - expect(response).to have_gitlab_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array - expect(json_response.count).to eq(2) - expect(json_response.map { |u| u['id'] }).to match_array [master.id, developer.id] - end - end - end - - shared_examples 'GET /:sources/:id/members/:user_id' do |source_type| - context "with :sources == #{source_type.pluralize}" do - it_behaves_like 'a 404 response when source is private' do - let(:route) { get v3_api("/#{source_type.pluralize}/#{source.id}/members/#{developer.id}", stranger) } - end - - context 'when authenticated as a non-member' do - %i[access_requester stranger].each do |type| - context "as a #{type}" do - it 'returns 200' do - user = public_send(type) - get v3_api("/#{source_type.pluralize}/#{source.id}/members/#{developer.id}", user) - - expect(response).to have_gitlab_http_status(200) - # User attributes - expect(json_response['id']).to eq(developer.id) - expect(json_response['name']).to eq(developer.name) - expect(json_response['username']).to eq(developer.username) - expect(json_response['state']).to eq(developer.state) - expect(json_response['avatar_url']).to eq(developer.avatar_url) - expect(json_response['web_url']).to eq(Gitlab::Routing.url_helpers.user_url(developer)) - - # Member attributes - expect(json_response['access_level']).to eq(Member::DEVELOPER) - end - end - end - end - end - end - - shared_examples 'POST /:sources/:id/members' do |source_type| - context "with :sources == #{source_type.pluralize}" do - it_behaves_like 'a 404 response when source is private' do - let(:route) do - post v3_api("/#{source_type.pluralize}/#{source.id}/members", stranger), - user_id: access_requester.id, access_level: Member::MASTER - end - end - - context 'when authenticated as a non-member or member with insufficient rights' do - %i[access_requester stranger developer].each do |type| - context "as a #{type}" do - it 'returns 403' do - user = public_send(type) - post v3_api("/#{source_type.pluralize}/#{source.id}/members", user), - user_id: access_requester.id, access_level: Member::MASTER - - expect(response).to have_gitlab_http_status(403) - end - end - end - end - - context 'when authenticated as a master/owner' do - context 'and new member is already a requester' do - it 'transforms the requester into a proper member' do - expect do - post v3_api("/#{source_type.pluralize}/#{source.id}/members", master), - user_id: access_requester.id, access_level: Member::MASTER - - expect(response).to have_gitlab_http_status(201) - end.to change { source.members.count }.by(1) - expect(source.requesters.count).to eq(0) - expect(json_response['id']).to eq(access_requester.id) - expect(json_response['access_level']).to eq(Member::MASTER) - end - end - - it 'creates a new member' do - expect do - post v3_api("/#{source_type.pluralize}/#{source.id}/members", master), - user_id: stranger.id, access_level: Member::DEVELOPER, expires_at: '2016-08-05' - - expect(response).to have_gitlab_http_status(201) - end.to change { source.members.count }.by(1) - expect(json_response['id']).to eq(stranger.id) - expect(json_response['access_level']).to eq(Member::DEVELOPER) - expect(json_response['expires_at']).to eq('2016-08-05') - end - end - - it "returns #{source_type == 'project' ? 201 : 409} if member already exists" do - post v3_api("/#{source_type.pluralize}/#{source.id}/members", master), - user_id: master.id, access_level: Member::MASTER - - expect(response).to have_gitlab_http_status(source_type == 'project' ? 201 : 409) - end - - it 'returns 400 when user_id is not given' do - post v3_api("/#{source_type.pluralize}/#{source.id}/members", master), - access_level: Member::MASTER - - expect(response).to have_gitlab_http_status(400) - end - - it 'returns 400 when access_level is not given' do - post v3_api("/#{source_type.pluralize}/#{source.id}/members", master), - user_id: stranger.id - - expect(response).to have_gitlab_http_status(400) - end - - it 'returns 422 when access_level is not valid' do - post v3_api("/#{source_type.pluralize}/#{source.id}/members", master), - user_id: stranger.id, access_level: 1234 - - expect(response).to have_gitlab_http_status(422) - end - end - end - - shared_examples 'PUT /:sources/:id/members/:user_id' do |source_type| - context "with :sources == #{source_type.pluralize}" do - it_behaves_like 'a 404 response when source is private' do - let(:route) do - put v3_api("/#{source_type.pluralize}/#{source.id}/members/#{developer.id}", stranger), - access_level: Member::MASTER - end - end - - context 'when authenticated as a non-member or member with insufficient rights' do - %i[access_requester stranger developer].each do |type| - context "as a #{type}" do - it 'returns 403' do - user = public_send(type) - put v3_api("/#{source_type.pluralize}/#{source.id}/members/#{developer.id}", user), - access_level: Member::MASTER - - expect(response).to have_gitlab_http_status(403) - end - end - end - end - - context 'when authenticated as a master/owner' do - it 'updates the member' do - put v3_api("/#{source_type.pluralize}/#{source.id}/members/#{developer.id}", master), - access_level: Member::MASTER, expires_at: '2016-08-05' - - expect(response).to have_gitlab_http_status(200) - expect(json_response['id']).to eq(developer.id) - expect(json_response['access_level']).to eq(Member::MASTER) - expect(json_response['expires_at']).to eq('2016-08-05') - end - end - - it 'returns 409 if member does not exist' do - put v3_api("/#{source_type.pluralize}/#{source.id}/members/123", master), - access_level: Member::MASTER - - expect(response).to have_gitlab_http_status(404) - end - - it 'returns 400 when access_level is not given' do - put v3_api("/#{source_type.pluralize}/#{source.id}/members/#{developer.id}", master) - - expect(response).to have_gitlab_http_status(400) - end - - it 'returns 422 when access level is not valid' do - put v3_api("/#{source_type.pluralize}/#{source.id}/members/#{developer.id}", master), - access_level: 1234 - - expect(response).to have_gitlab_http_status(422) - end - end - end - - shared_examples 'DELETE /:sources/:id/members/:user_id' do |source_type| - context "with :sources == #{source_type.pluralize}" do - it_behaves_like 'a 404 response when source is private' do - let(:route) { delete v3_api("/#{source_type.pluralize}/#{source.id}/members/#{developer.id}", stranger) } - end - - context 'when authenticated as a non-member or member with insufficient rights' do - %i[access_requester stranger].each do |type| - context "as a #{type}" do - it 'returns 403' do - user = public_send(type) - delete v3_api("/#{source_type.pluralize}/#{source.id}/members/#{developer.id}", user) - - expect(response).to have_gitlab_http_status(403) - end - end - end - end - - context 'when authenticated as a member and deleting themself' do - it 'deletes the member' do - expect do - delete v3_api("/#{source_type.pluralize}/#{source.id}/members/#{developer.id}", developer) - - expect(response).to have_gitlab_http_status(200) - end.to change { source.members.count }.by(-1) - end - end - - context 'when authenticated as a master/owner' do - context 'and member is a requester' do - it "returns #{source_type == 'project' ? 200 : 404}" do - expect do - delete v3_api("/#{source_type.pluralize}/#{source.id}/members/#{access_requester.id}", master) - - expect(response).to have_gitlab_http_status(source_type == 'project' ? 200 : 404) - end.not_to change { source.requesters.count } - end - end - - it 'deletes the member' do - expect do - delete v3_api("/#{source_type.pluralize}/#{source.id}/members/#{developer.id}", master) - - expect(response).to have_gitlab_http_status(200) - end.to change { source.members.count }.by(-1) - end - end - - it "returns #{source_type == 'project' ? 200 : 404} if member does not exist" do - delete v3_api("/#{source_type.pluralize}/#{source.id}/members/123", master) - - expect(response).to have_gitlab_http_status(source_type == 'project' ? 200 : 404) - end - end - end - - it_behaves_like 'GET /:sources/:id/members', 'project' do - let(:source) { project } - end - - it_behaves_like 'GET /:sources/:id/members', 'group' do - let(:source) { group } - end - - it_behaves_like 'GET /:sources/:id/members/:user_id', 'project' do - let(:source) { project } - end - - it_behaves_like 'GET /:sources/:id/members/:user_id', 'group' do - let(:source) { group } - end - - it_behaves_like 'POST /:sources/:id/members', 'project' do - let(:source) { project } - end - - it_behaves_like 'POST /:sources/:id/members', 'group' do - let(:source) { group } - end - - it_behaves_like 'PUT /:sources/:id/members/:user_id', 'project' do - let(:source) { project } - end - - it_behaves_like 'PUT /:sources/:id/members/:user_id', 'group' do - let(:source) { group } - end - - it_behaves_like 'DELETE /:sources/:id/members/:user_id', 'project' do - let(:source) { project } - end - - it_behaves_like 'DELETE /:sources/:id/members/:user_id', 'group' do - let(:source) { group } - end - - context 'Adding owner to project' do - it 'returns 403' do - expect do - post v3_api("/projects/#{project.id}/members", master), - user_id: stranger.id, access_level: Member::OWNER - - expect(response).to have_gitlab_http_status(422) - end.to change { project.members.count }.by(0) - end - end -end diff --git a/spec/requests/api/v3/merge_request_diffs_spec.rb b/spec/requests/api/v3/merge_request_diffs_spec.rb deleted file mode 100644 index 547c066fadc..00000000000 --- a/spec/requests/api/v3/merge_request_diffs_spec.rb +++ /dev/null @@ -1,48 +0,0 @@ -require "spec_helper" - -describe API::V3::MergeRequestDiffs, 'MergeRequestDiffs' do - let!(:user) { create(:user) } - let!(:merge_request) { create(:merge_request, importing: true) } - let!(:project) { merge_request.target_project } - - before do - merge_request.merge_request_diffs.create(head_commit_sha: '6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9') - merge_request.merge_request_diffs.create(head_commit_sha: '5937ac0a7beb003549fc5fd26fc247adbce4a52e') - project.add_master(user) - end - - describe 'GET /projects/:id/merge_requests/:merge_request_id/versions' do - it 'returns 200 for a valid merge request' do - get v3_api("/projects/#{project.id}/merge_requests/#{merge_request.id}/versions", user) - merge_request_diff = merge_request.merge_request_diffs.last - - expect(response.status).to eq 200 - expect(json_response.size).to eq(merge_request.merge_request_diffs.size) - expect(json_response.first['id']).to eq(merge_request_diff.id) - expect(json_response.first['head_commit_sha']).to eq(merge_request_diff.head_commit_sha) - end - - it 'returns a 404 when merge_request_id not found' do - get v3_api("/projects/#{project.id}/merge_requests/999/versions", user) - expect(response).to have_gitlab_http_status(404) - end - end - - describe 'GET /projects/:id/merge_requests/:merge_request_id/versions/:version_id' do - it 'returns a 200 for a valid merge request' do - merge_request_diff = merge_request.merge_request_diffs.first - get v3_api("/projects/#{project.id}/merge_requests/#{merge_request.id}/versions/#{merge_request_diff.id}", user) - - expect(response.status).to eq 200 - expect(json_response['id']).to eq(merge_request_diff.id) - expect(json_response['head_commit_sha']).to eq(merge_request_diff.head_commit_sha) - expect(json_response['diffs'].size).to eq(merge_request_diff.diffs.size) - end - - it 'returns a 404 when merge_request_id not found' do - get v3_api("/projects/#{project.id}/merge_requests/#{merge_request.id}/versions/999", user) - - expect(response).to have_gitlab_http_status(404) - end - end -end diff --git a/spec/requests/api/v3/merge_requests_spec.rb b/spec/requests/api/v3/merge_requests_spec.rb deleted file mode 100644 index 79a16fbd1b0..00000000000 --- a/spec/requests/api/v3/merge_requests_spec.rb +++ /dev/null @@ -1,767 +0,0 @@ -require "spec_helper" - -describe API::MergeRequests do - include ProjectForksHelper - - let(:base_time) { Time.now } - let(:user) { create(:user) } - let(:admin) { create(:user, :admin) } - let(:non_member) { create(:user) } - let!(:project) { create(:project, :public, :repository, creator: user, namespace: user.namespace) } - let!(:merge_request) { create(:merge_request, :simple, author: user, assignee: user, source_project: project, title: "Test", created_at: base_time) } - let!(:merge_request_closed) { create(:merge_request, state: "closed", author: user, assignee: user, source_project: project, title: "Closed test", created_at: base_time + 1.second) } - let!(:merge_request_merged) { create(:merge_request, state: "merged", author: user, assignee: user, source_project: project, title: "Merged test", created_at: base_time + 2.seconds, merge_commit_sha: '9999999999999999999999999999999999999999') } - let(:milestone) { create(:milestone, title: '1.0.0', project: project) } - - before do - project.add_reporter(user) - end - - describe "GET /projects/:id/merge_requests" do - context "when unauthenticated" do - it "returns authentication error" do - get v3_api("/projects/#{project.id}/merge_requests") - expect(response).to have_gitlab_http_status(401) - end - end - - context "when authenticated" do - it "returns an array of all merge_requests" do - get v3_api("/projects/#{project.id}/merge_requests", user) - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.length).to eq(3) - expect(json_response.last['title']).to eq(merge_request.title) - expect(json_response.last).to have_key('web_url') - expect(json_response.last['sha']).to eq(merge_request.diff_head_sha) - expect(json_response.last['merge_commit_sha']).to be_nil - expect(json_response.last['merge_commit_sha']).to eq(merge_request.merge_commit_sha) - expect(json_response.first['title']).to eq(merge_request_merged.title) - expect(json_response.first['sha']).to eq(merge_request_merged.diff_head_sha) - expect(json_response.first['merge_commit_sha']).not_to be_nil - expect(json_response.first['merge_commit_sha']).to eq(merge_request_merged.merge_commit_sha) - expect(json_response.first['squash']).to eq(merge_request_merged.squash) - end - - it "returns an array of all merge_requests" do - get v3_api("/projects/#{project.id}/merge_requests?state", user) - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.length).to eq(3) - expect(json_response.last['title']).to eq(merge_request.title) - end - - it "returns an array of open merge_requests" do - get v3_api("/projects/#{project.id}/merge_requests?state=opened", user) - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.length).to eq(1) - expect(json_response.last['title']).to eq(merge_request.title) - end - - it "returns an array of closed merge_requests" do - get v3_api("/projects/#{project.id}/merge_requests?state=closed", user) - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.length).to eq(1) - expect(json_response.first['title']).to eq(merge_request_closed.title) - end - - it "returns an array of merged merge_requests" do - get v3_api("/projects/#{project.id}/merge_requests?state=merged", user) - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.length).to eq(1) - expect(json_response.first['title']).to eq(merge_request_merged.title) - end - - it 'matches V3 response schema' do - get v3_api("/projects/#{project.id}/merge_requests", user) - - expect(response).to have_gitlab_http_status(200) - expect(response).to match_response_schema('public_api/v3/merge_requests') - end - - context "with ordering" do - before do - @mr_later = mr_with_later_created_and_updated_at_time - @mr_earlier = mr_with_earlier_created_and_updated_at_time - end - - it "returns an array of merge_requests in ascending order" do - get v3_api("/projects/#{project.id}/merge_requests?sort=asc", user) - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.length).to eq(3) - response_dates = json_response.map { |merge_request| merge_request['created_at'] } - expect(response_dates).to eq(response_dates.sort) - end - - it "returns an array of merge_requests in descending order" do - get v3_api("/projects/#{project.id}/merge_requests?sort=desc", user) - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.length).to eq(3) - response_dates = json_response.map { |merge_request| merge_request['created_at'] } - expect(response_dates).to eq(response_dates.sort.reverse) - end - - it "returns an array of merge_requests ordered by updated_at" do - get v3_api("/projects/#{project.id}/merge_requests?order_by=updated_at", user) - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.length).to eq(3) - response_dates = json_response.map { |merge_request| merge_request['updated_at'] } - expect(response_dates).to eq(response_dates.sort.reverse) - end - - it "returns an array of merge_requests ordered by created_at" do - get v3_api("/projects/#{project.id}/merge_requests?order_by=created_at&sort=asc", user) - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.length).to eq(3) - response_dates = json_response.map { |merge_request| merge_request['created_at'] } - expect(response_dates).to eq(response_dates.sort) - end - end - end - end - - describe "GET /projects/:id/merge_requests/:merge_request_id" do - it 'exposes known attributes' do - get v3_api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user) - - expect(response).to have_gitlab_http_status(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') - expect(json_response['should_close_merge_request']).to be_falsy - expect(json_response['force_close_merge_request']).to be_falsy - end - - it "returns merge_request" do - get v3_api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user) - expect(response).to have_gitlab_http_status(200) - expect(json_response['title']).to eq(merge_request.title) - expect(json_response['iid']).to eq(merge_request.iid) - expect(json_response['work_in_progress']).to eq(false) - expect(json_response['merge_status']).to eq('can_be_merged') - expect(json_response['should_close_merge_request']).to be_falsy - expect(json_response['force_close_merge_request']).to be_falsy - end - - it 'returns merge_request by iid' do - url = "/projects/#{project.id}/merge_requests?iid=#{merge_request.iid}" - get v3_api(url, user) - expect(response.status).to eq 200 - expect(json_response.first['title']).to eq merge_request.title - expect(json_response.first['id']).to eq merge_request.id - end - - it 'returns merge_request by iid array' do - get v3_api("/projects/#{project.id}/merge_requests", user), iid: [merge_request.iid, merge_request_closed.iid] - - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.length).to eq(2) - expect(json_response.first['title']).to eq merge_request_closed.title - expect(json_response.first['id']).to eq merge_request_closed.id - end - - it "returns a 404 error if merge_request_id not found" do - get v3_api("/projects/#{project.id}/merge_requests/999", user) - expect(response).to have_gitlab_http_status(404) - end - - context 'Work in Progress' do - let!(:merge_request_wip) { create(:merge_request, author: user, assignee: user, source_project: project, target_project: project, title: "WIP: Test", created_at: base_time + 1.second) } - - it "returns merge_request" do - get v3_api("/projects/#{project.id}/merge_requests/#{merge_request_wip.id}", user) - expect(response).to have_gitlab_http_status(200) - expect(json_response['work_in_progress']).to eq(true) - end - end - end - - describe 'GET /projects/:id/merge_requests/:merge_request_id/commits' do - it 'returns a 200 when merge request is valid' do - get v3_api("/projects/#{project.id}/merge_requests/#{merge_request.id}/commits", user) - commit = merge_request.commits.first - - expect(response.status).to eq 200 - expect(json_response.size).to eq(merge_request.commits.size) - expect(json_response.first['id']).to eq(commit.id) - expect(json_response.first['title']).to eq(commit.title) - end - - it 'returns a 404 when merge_request_id not found' do - get v3_api("/projects/#{project.id}/merge_requests/999/commits", user) - expect(response).to have_gitlab_http_status(404) - end - end - - describe 'GET /projects/:id/merge_requests/:merge_request_id/changes' do - it 'returns the change information of the merge_request' do - get v3_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 v3_api("/projects/#{project.id}/merge_requests/999/changes", user) - expect(response).to have_gitlab_http_status(404) - end - end - - describe "POST /projects/:id/merge_requests" do - context 'between branches projects' do - it "returns merge_request" do - post v3_api("/projects/#{project.id}/merge_requests", user), - title: 'Test merge_request', - source_branch: 'feature_conflict', - target_branch: 'master', - author: user, - labels: 'label, label2', - milestone_id: milestone.id, - remove_source_branch: true, - squash: true - - expect(response).to have_gitlab_http_status(201) - expect(json_response['title']).to eq('Test merge_request') - expect(json_response['labels']).to eq(%w(label label2)) - expect(json_response['milestone']['id']).to eq(milestone.id) - expect(json_response['force_remove_source_branch']).to be_truthy - expect(json_response['squash']).to be_truthy - end - - it "returns 422 when source_branch equals target_branch" do - post v3_api("/projects/#{project.id}/merge_requests", user), - title: "Test merge_request", source_branch: "master", target_branch: "master", author: user - expect(response).to have_gitlab_http_status(422) - end - - it "returns 400 when source_branch is missing" do - post v3_api("/projects/#{project.id}/merge_requests", user), - title: "Test merge_request", target_branch: "master", author: user - expect(response).to have_gitlab_http_status(400) - end - - it "returns 400 when target_branch is missing" do - post v3_api("/projects/#{project.id}/merge_requests", user), - title: "Test merge_request", source_branch: "markdown", author: user - expect(response).to have_gitlab_http_status(400) - end - - it "returns 400 when title is missing" do - post v3_api("/projects/#{project.id}/merge_requests", user), - target_branch: 'master', source_branch: 'markdown' - expect(response).to have_gitlab_http_status(400) - end - - it 'allows special label names' do - post v3_api("/projects/#{project.id}/merge_requests", user), - title: 'Test merge_request', - source_branch: 'markdown', - target_branch: 'master', - author: user, - labels: 'label, label?, label&foo, ?, &' - expect(response.status).to eq(201) - expect(json_response['labels']).to include 'label' - expect(json_response['labels']).to include 'label?' - expect(json_response['labels']).to include 'label&foo' - expect(json_response['labels']).to include '?' - expect(json_response['labels']).to include '&' - end - - context 'with existing MR' do - before do - post v3_api("/projects/#{project.id}/merge_requests", user), - title: 'Test merge_request', - source_branch: 'feature_conflict', - target_branch: 'master', - author: user - @mr = MergeRequest.all.last - end - - it 'returns 409 when MR already exists for source/target' do - expect do - post v3_api("/projects/#{project.id}/merge_requests", user), - title: 'New test merge_request', - source_branch: 'feature_conflict', - target_branch: 'master', - author: user - end.to change { MergeRequest.count }.by(0) - expect(response).to have_gitlab_http_status(409) - end - end - end - - context 'forked projects' do - let!(:user2) { create(:user) } - let!(:forked_project) { fork_project(project, user2, repository: true) } - let!(:unrelated_project) { create(:project, namespace: create(:user).namespace, creator_id: user2.id) } - - before do - forked_project.add_reporter(user2) - end - - it "returns merge_request" do - post v3_api("/projects/#{forked_project.id}/merge_requests", user2), - title: 'Test merge_request', source_branch: "feature_conflict", target_branch: "master", - author: user2, target_project_id: project.id, description: 'Test description for Test merge_request' - expect(response).to have_gitlab_http_status(201) - expect(json_response['title']).to eq('Test merge_request') - expect(json_response['description']).to eq('Test description for Test merge_request') - end - - it "does not return 422 when source_branch equals target_branch" do - expect(project.id).not_to eq(forked_project.id) - expect(forked_project.forked?).to be_truthy - expect(forked_project.forked_from_project).to eq(project) - post v3_api("/projects/#{forked_project.id}/merge_requests", user2), - title: 'Test merge_request', source_branch: "master", target_branch: "master", author: user2, target_project_id: project.id - expect(response).to have_gitlab_http_status(201) - expect(json_response['title']).to eq('Test merge_request') - end - - it "returns 403 when target project has disabled merge requests" do - project.project_feature.update(merge_requests_access_level: 0) - - post v3_api("/projects/#{forked_project.id}/merge_requests", user2), - title: 'Test', - target_branch: "master", - source_branch: 'markdown', - author: user2, - target_project_id: project.id - - expect(response).to have_gitlab_http_status(403) - end - - it "returns 400 when source_branch is missing" do - post v3_api("/projects/#{forked_project.id}/merge_requests", user2), - title: 'Test merge_request', target_branch: "master", author: user2, target_project_id: project.id - expect(response).to have_gitlab_http_status(400) - end - - it "returns 400 when target_branch is missing" do - post v3_api("/projects/#{forked_project.id}/merge_requests", user2), - title: 'Test merge_request', target_branch: "master", author: user2, target_project_id: project.id - expect(response).to have_gitlab_http_status(400) - end - - it "returns 400 when title is missing" do - post v3_api("/projects/#{forked_project.id}/merge_requests", user2), - target_branch: 'master', source_branch: 'markdown', author: user2, target_project_id: project.id - expect(response).to have_gitlab_http_status(400) - end - - context 'when target_branch and target_project_id is specified' do - let(:params) do - { title: 'Test merge_request', - target_branch: 'master', - source_branch: 'markdown', - author: user2, - target_project_id: unrelated_project.id } - end - - it 'returns 422 if targeting a different fork' do - unrelated_project.add_developer(user2) - - post v3_api("/projects/#{forked_project.id}/merge_requests", user2), params - - expect(response).to have_gitlab_http_status(422) - end - - it 'returns 403 if targeting a different fork which user can not access' do - post v3_api("/projects/#{forked_project.id}/merge_requests", user2), params - - expect(response).to have_gitlab_http_status(403) - end - end - - it "returns 201 when target_branch is specified and for the same project" do - post v3_api("/projects/#{forked_project.id}/merge_requests", user2), - title: 'Test merge_request', target_branch: 'master', source_branch: 'markdown', author: user2, target_project_id: forked_project.id - expect(response).to have_gitlab_http_status(201) - end - end - end - - describe "DELETE /projects/:id/merge_requests/:merge_request_id" do - context "when the user is developer" do - let(:developer) { create(:user) } - - before do - project.add_developer(developer) - end - - it "denies the deletion of the merge request" do - delete v3_api("/projects/#{project.id}/merge_requests/#{merge_request.id}", developer) - expect(response).to have_gitlab_http_status(403) - end - end - - context "when the user is project owner" do - it "destroys the merge request owners can destroy" do - delete v3_api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user) - - expect(response).to have_gitlab_http_status(200) - end - end - end - - describe "PUT /projects/:id/merge_requests/:merge_request_id/merge" do - let(:pipeline) { create(:ci_pipeline_without_jobs) } - - it "returns merge_request in case of success" do - put v3_api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge", user) - - expect(response).to have_gitlab_http_status(200) - end - - it "returns 406 if branch can't be merged" do - allow_any_instance_of(MergeRequest) - .to receive(:can_be_merged?).and_return(false) - - put v3_api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge", user) - - expect(response).to have_gitlab_http_status(406) - expect(json_response['message']).to eq('Branch cannot be merged') - end - - it "returns 405 if merge_request is not open" do - merge_request.close - put v3_api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge", user) - expect(response).to have_gitlab_http_status(405) - expect(json_response['message']).to eq('405 Method Not Allowed') - end - - it "returns 405 if merge_request is a work in progress" do - merge_request.update_attribute(:title, "WIP: #{merge_request.title}") - put v3_api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge", user) - expect(response).to have_gitlab_http_status(405) - 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 v3_api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge", user) - - expect(response).to have_gitlab_http_status(405) - expect(json_response['message']).to eq('405 Method Not Allowed') - end - - it "returns 401 if user has no permissions to merge" do - user2 = create(:user) - project.add_reporter(user2) - put v3_api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge", user2) - expect(response).to have_gitlab_http_status(401) - expect(json_response['message']).to eq('401 Unauthorized') - end - - it "returns 409 if the SHA parameter doesn't match" do - put v3_api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge", user), sha: merge_request.diff_head_sha.reverse - - expect(response).to have_gitlab_http_status(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 v3_api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge", user), sha: merge_request.diff_head_sha - - expect(response).to have_gitlab_http_status(200) - end - - it "updates the MR's squash attribute" do - expect do - put v3_api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge", user), squash: true - end.to change { merge_request.reload.squash } - - expect(response).to have_gitlab_http_status(200) - end - - it "enables merge when pipeline succeeds if the pipeline is active" do - allow_any_instance_of(MergeRequest).to receive(:head_pipeline).and_return(pipeline) - allow(pipeline).to receive(:active?).and_return(true) - - put v3_api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge", user), merge_when_build_succeeds: true - - expect(response).to have_gitlab_http_status(200) - expect(json_response['title']).to eq('Test') - expect(json_response['merge_when_build_succeeds']).to eq(true) - end - end - - describe "PUT /projects/:id/merge_requests/:merge_request_id" do - context "to close a MR" do - it "returns merge_request" do - put v3_api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user), state_event: "close" - - expect(response).to have_gitlab_http_status(200) - expect(json_response['state']).to eq('closed') - end - end - - it "updates title and returns merge_request" do - put v3_api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user), title: "New title" - expect(response).to have_gitlab_http_status(200) - expect(json_response['title']).to eq('New title') - end - - it "updates description and returns merge_request" do - put v3_api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user), description: "New description" - expect(response).to have_gitlab_http_status(200) - expect(json_response['description']).to eq('New description') - end - - it "updates milestone_id and returns merge_request" do - put v3_api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user), milestone_id: milestone.id - expect(response).to have_gitlab_http_status(200) - expect(json_response['milestone']['id']).to eq(milestone.id) - end - - it "updates squash and returns merge_request" do - put v3_api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user), squash: true - - expect(response).to have_gitlab_http_status(200) - expect(json_response['squash']).to be_truthy - end - - it "returns merge_request with renamed target_branch" do - put v3_api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user), target_branch: "wiki" - expect(response).to have_gitlab_http_status(200) - expect(json_response['target_branch']).to eq('wiki') - end - - it "returns merge_request that removes the source branch" do - put v3_api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user), remove_source_branch: true - - expect(response).to have_gitlab_http_status(200) - expect(json_response['force_remove_source_branch']).to be_truthy - end - - it 'allows special label names' do - put v3_api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user), - title: 'new issue', - labels: 'label, label?, label&foo, ?, &' - - expect(response.status).to eq(200) - expect(json_response['labels']).to include 'label' - expect(json_response['labels']).to include 'label?' - expect(json_response['labels']).to include 'label&foo' - expect(json_response['labels']).to include '?' - expect(json_response['labels']).to include '&' - end - - it 'does not update state when title is empty' do - put v3_api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user), state_event: 'close', title: nil - - merge_request.reload - expect(response).to have_gitlab_http_status(400) - expect(merge_request.state).to eq('opened') - end - - it 'does not update state when target_branch is empty' do - put v3_api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user), state_event: 'close', target_branch: nil - - merge_request.reload - expect(response).to have_gitlab_http_status(400) - expect(merge_request.state).to eq('opened') - end - end - - describe "POST /projects/:id/merge_requests/:merge_request_id/comments" do - it "returns comment" do - original_count = merge_request.notes.size - - post v3_api("/projects/#{project.id}/merge_requests/#{merge_request.id}/comments", user), note: "My comment" - - expect(response).to have_gitlab_http_status(201) - expect(json_response['note']).to eq('My comment') - expect(json_response['author']['name']).to eq(user.name) - expect(json_response['author']['username']).to eq(user.username) - expect(merge_request.reload.notes.size).to eq(original_count + 1) - end - - it "returns 400 if note is missing" do - post v3_api("/projects/#{project.id}/merge_requests/#{merge_request.id}/comments", user) - expect(response).to have_gitlab_http_status(400) - end - - it "returns 404 if note is attached to non existent merge request" do - post v3_api("/projects/#{project.id}/merge_requests/404/comments", user), - note: 'My comment' - expect(response).to have_gitlab_http_status(404) - end - end - - describe "GET :id/merge_requests/:merge_request_id/comments" do - 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") } - - it "returns merge_request comments ordered by created_at" do - get v3_api("/projects/#{project.id}/merge_requests/#{merge_request.id}/comments", user) - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.length).to eq(2) - expect(json_response.first['note']).to eq("a comment on a MR") - expect(json_response.first['author']['id']).to eq(user.id) - expect(json_response.last['note']).to eq("another comment on a MR") - end - - it "returns a 404 error if merge_request_id not found" do - get v3_api("/projects/#{project.id}/merge_requests/999/comments", user) - expect(response).to have_gitlab_http_status(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 v3_api("/projects/#{project.id}/merge_requests/#{mr.id}/closes_issues", user) - expect(response).to have_gitlab_http_status(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 v3_api("/projects/#{project.id}/merge_requests/#{merge_request.id}/closes_issues", user) - expect(response).to have_gitlab_http_status(200) - 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, :repository, 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 v3_api("/projects/#{jira_project.id}/merge_requests/#{merge_request.id}/closes_issues", user) - - expect(response).to have_gitlab_http_status(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 - - it 'returns 403 if the user has no access to the merge request' do - project = create(:project, :private, :repository) - merge_request = create(:merge_request, :simple, source_project: project) - guest = create(:user) - project.add_guest(guest) - - get v3_api("/projects/#{project.id}/merge_requests/#{merge_request.id}/closes_issues", guest) - - expect(response).to have_gitlab_http_status(403) - end - end - - describe 'POST :id/merge_requests/:merge_request_id/subscription' do - it 'subscribes to a merge request' do - post v3_api("/projects/#{project.id}/merge_requests/#{merge_request.id}/subscription", admin) - - expect(response).to have_gitlab_http_status(201) - expect(json_response['subscribed']).to eq(true) - end - - it 'returns 304 if already subscribed' do - post v3_api("/projects/#{project.id}/merge_requests/#{merge_request.id}/subscription", user) - - expect(response).to have_gitlab_http_status(304) - end - - it 'returns 404 if the merge request is not found' do - post v3_api("/projects/#{project.id}/merge_requests/123/subscription", user) - - expect(response).to have_gitlab_http_status(404) - end - - it 'returns 403 if user has no access to read code' do - guest = create(:user) - project.add_guest(guest) - - post v3_api("/projects/#{project.id}/merge_requests/#{merge_request.id}/subscription", guest) - - expect(response).to have_gitlab_http_status(403) - end - end - - describe 'DELETE :id/merge_requests/:merge_request_id/subscription' do - it 'unsubscribes from a merge request' do - delete v3_api("/projects/#{project.id}/merge_requests/#{merge_request.id}/subscription", user) - - expect(response).to have_gitlab_http_status(200) - expect(json_response['subscribed']).to eq(false) - end - - it 'returns 304 if not subscribed' do - delete v3_api("/projects/#{project.id}/merge_requests/#{merge_request.id}/subscription", admin) - - expect(response).to have_gitlab_http_status(304) - end - - it 'returns 404 if the merge request is not found' do - post v3_api("/projects/#{project.id}/merge_requests/123/subscription", user) - - expect(response).to have_gitlab_http_status(404) - end - - it 'returns 403 if user has no access to read code' do - guest = create(:user) - project.add_guest(guest) - - delete v3_api("/projects/#{project.id}/merge_requests/#{merge_request.id}/subscription", guest) - - expect(response).to have_gitlab_http_status(403) - end - end - - describe 'Time tracking' do - let(:issuable) { merge_request } - - include_examples 'V3 time tracking endpoints', 'merge_request' - end - - def mr_with_later_created_and_updated_at_time - merge_request - merge_request.created_at += 1.hour - merge_request.updated_at += 30.minutes - merge_request.save - merge_request - end - - def mr_with_earlier_created_and_updated_at_time - merge_request_closed - merge_request_closed.created_at -= 1.hour - merge_request_closed.updated_at -= 30.minutes - merge_request_closed.save - merge_request_closed - end -end diff --git a/spec/requests/api/v3/milestones_spec.rb b/spec/requests/api/v3/milestones_spec.rb deleted file mode 100644 index 6021600e09c..00000000000 --- a/spec/requests/api/v3/milestones_spec.rb +++ /dev/null @@ -1,238 +0,0 @@ -require 'spec_helper' - -describe API::V3::Milestones do - let(:user) { create(:user) } - let!(:project) { create(:project, namespace: user.namespace ) } - let!(:closed_milestone) { create(:closed_milestone, project: project) } - let!(:milestone) { create(:milestone, project: project) } - - before { project.add_developer(user) } - - describe 'GET /projects/:id/milestones' do - it 'returns project milestones' do - get v3_api("/projects/#{project.id}/milestones", user) - - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.first['title']).to eq(milestone.title) - end - - it 'returns a 401 error if user not authenticated' do - get v3_api("/projects/#{project.id}/milestones") - - expect(response).to have_gitlab_http_status(401) - end - - it 'returns an array of active milestones' do - get v3_api("/projects/#{project.id}/milestones?state=active", user) - - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.length).to eq(1) - expect(json_response.first['id']).to eq(milestone.id) - end - - it 'returns an array of closed milestones' do - get v3_api("/projects/#{project.id}/milestones?state=closed", user) - - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.length).to eq(1) - expect(json_response.first['id']).to eq(closed_milestone.id) - end - end - - describe 'GET /projects/:id/milestones/:milestone_id' do - it 'returns a project milestone by id' do - get v3_api("/projects/#{project.id}/milestones/#{milestone.id}", user) - - expect(response).to have_gitlab_http_status(200) - expect(json_response['title']).to eq(milestone.title) - expect(json_response['iid']).to eq(milestone.iid) - end - - it 'returns a project milestone by iid' do - get v3_api("/projects/#{project.id}/milestones?iid=#{closed_milestone.iid}", user) - - expect(response.status).to eq 200 - expect(json_response.size).to eq(1) - expect(json_response.first['title']).to eq closed_milestone.title - expect(json_response.first['id']).to eq closed_milestone.id - end - - it 'returns a project milestone by iid array' do - get v3_api("/projects/#{project.id}/milestones", user), iid: [milestone.iid, closed_milestone.iid] - - expect(response).to have_gitlab_http_status(200) - expect(json_response.size).to eq(2) - expect(json_response.first['title']).to eq milestone.title - expect(json_response.first['id']).to eq milestone.id - end - - it 'returns 401 error if user not authenticated' do - get v3_api("/projects/#{project.id}/milestones/#{milestone.id}") - - expect(response).to have_gitlab_http_status(401) - end - - it 'returns a 404 error if milestone id not found' do - get v3_api("/projects/#{project.id}/milestones/1234", user) - - expect(response).to have_gitlab_http_status(404) - end - end - - describe 'POST /projects/:id/milestones' do - it 'creates a new project milestone' do - post v3_api("/projects/#{project.id}/milestones", user), title: 'new milestone' - - expect(response).to have_gitlab_http_status(201) - expect(json_response['title']).to eq('new milestone') - expect(json_response['description']).to be_nil - end - - it 'creates a new project milestone with description and dates' do - post v3_api("/projects/#{project.id}/milestones", user), - title: 'new milestone', description: 'release', due_date: '2013-03-02', start_date: '2013-02-02' - - expect(response).to have_gitlab_http_status(201) - expect(json_response['description']).to eq('release') - expect(json_response['due_date']).to eq('2013-03-02') - expect(json_response['start_date']).to eq('2013-02-02') - end - - it 'returns a 400 error if title is missing' do - post v3_api("/projects/#{project.id}/milestones", user) - - expect(response).to have_gitlab_http_status(400) - end - - it 'returns a 400 error if params are invalid (duplicate title)' do - post v3_api("/projects/#{project.id}/milestones", user), - title: milestone.title, description: 'release', due_date: '2013-03-02' - - expect(response).to have_gitlab_http_status(400) - end - - it 'creates a new project with reserved html characters' do - post v3_api("/projects/#{project.id}/milestones", user), title: 'foo & bar 1.1 -> 2.2' - - expect(response).to have_gitlab_http_status(201) - expect(json_response['title']).to eq('foo & bar 1.1 -> 2.2') - expect(json_response['description']).to be_nil - end - end - - describe 'PUT /projects/:id/milestones/:milestone_id' do - it 'updates a project milestone' do - put v3_api("/projects/#{project.id}/milestones/#{milestone.id}", user), - title: 'updated title' - - expect(response).to have_gitlab_http_status(200) - expect(json_response['title']).to eq('updated title') - end - - it 'removes a due date if nil is passed' do - milestone.update!(due_date: "2016-08-05") - - put v3_api("/projects/#{project.id}/milestones/#{milestone.id}", user), due_date: nil - - expect(response).to have_gitlab_http_status(200) - expect(json_response['due_date']).to be_nil - end - - it 'returns a 404 error if milestone id not found' do - put v3_api("/projects/#{project.id}/milestones/1234", user), - title: 'updated title' - - expect(response).to have_gitlab_http_status(404) - end - end - - describe 'PUT /projects/:id/milestones/:milestone_id to close milestone' do - it 'updates a project milestone' do - put v3_api("/projects/#{project.id}/milestones/#{milestone.id}", user), - state_event: 'close' - expect(response).to have_gitlab_http_status(200) - - expect(json_response['state']).to eq('closed') - end - end - - describe 'PUT /projects/:id/milestones/:milestone_id to test observer on close' do - it 'creates an activity event when an milestone is closed' do - expect(Event).to receive(:create!) - - put v3_api("/projects/#{project.id}/milestones/#{milestone.id}", user), - state_event: 'close' - end - end - - describe 'GET /projects/:id/milestones/:milestone_id/issues' do - before do - milestone.issues << create(:issue, project: project) - end - it 'returns project issues for a particular milestone' do - get v3_api("/projects/#{project.id}/milestones/#{milestone.id}/issues", user) - - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.first['milestone']['title']).to eq(milestone.title) - end - - it 'matches V3 response schema for a list of issues' do - get v3_api("/projects/#{project.id}/milestones/#{milestone.id}/issues", user) - - expect(response).to have_gitlab_http_status(200) - expect(response).to match_response_schema('public_api/v3/issues') - end - - it 'returns a 401 error if user not authenticated' do - get v3_api("/projects/#{project.id}/milestones/#{milestone.id}/issues") - - expect(response).to have_gitlab_http_status(401) - end - - describe 'confidential issues' do - let(:public_project) { create(:project, :public) } - let(:milestone) { create(:milestone, project: public_project) } - let(:issue) { create(:issue, project: public_project) } - let(:confidential_issue) { create(:issue, confidential: true, project: public_project) } - - before do - public_project.add_developer(user) - milestone.issues << issue << confidential_issue - end - - it 'returns confidential issues to team members' do - get v3_api("/projects/#{public_project.id}/milestones/#{milestone.id}/issues", user) - - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.size).to eq(2) - expect(json_response.map { |issue| issue['id'] }).to include(issue.id, confidential_issue.id) - end - - it 'does not return confidential issues to team members with guest role' do - member = create(:user) - project.add_guest(member) - - get v3_api("/projects/#{public_project.id}/milestones/#{milestone.id}/issues", member) - - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.size).to eq(1) - expect(json_response.map { |issue| issue['id'] }).to include(issue.id) - end - - it 'does not return confidential issues to regular users' do - get v3_api("/projects/#{public_project.id}/milestones/#{milestone.id}/issues", create(:user)) - - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.size).to eq(1) - expect(json_response.map { |issue| issue['id'] }).to include(issue.id) - end - end - end -end diff --git a/spec/requests/api/v3/notes_spec.rb b/spec/requests/api/v3/notes_spec.rb deleted file mode 100644 index 5532795ab02..00000000000 --- a/spec/requests/api/v3/notes_spec.rb +++ /dev/null @@ -1,431 +0,0 @@ -require 'spec_helper' - -describe API::V3::Notes do - let(:user) { create(:user) } - 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) } - 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.add_master(private_user) } - 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.add_reporter(user) } - - describe "GET /projects/:id/noteable/:noteable_id/notes" do - context "when noteable is an Issue" do - it "returns an array of issue notes" do - get v3_api("/projects/#{project.id}/issues/#{issue.id}/notes", user) - - expect(response).to have_gitlab_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array - expect(json_response.first['body']).to eq(issue_note.note) - expect(json_response.first['upvote']).to be_falsey - expect(json_response.first['downvote']).to be_falsey - end - - it "returns a 404 error when issue id not found" do - get v3_api("/projects/#{project.id}/issues/12345/notes", user) - - expect(response).to have_gitlab_http_status(404) - end - - context "and current user cannot view the notes" do - it "returns an empty array" do - get v3_api("/projects/#{ext_proj.id}/issues/#{ext_issue.id}/notes", user) - - expect(response).to have_gitlab_http_status(200) - expect(response).to include_pagination_headers - 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 v3_api("/projects/#{ext_proj.id}/issues/#{ext_issue.id}/notes", user) - - expect(response).to have_gitlab_http_status(404) - end - end - - context "and current user can view the note" do - it "returns an empty array" do - get v3_api("/projects/#{ext_proj.id}/issues/#{ext_issue.id}/notes", private_user) - - expect(response).to have_gitlab_http_status(200) - expect(response).to include_pagination_headers - 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 - it "returns an array of snippet notes" do - get v3_api("/projects/#{project.id}/snippets/#{snippet.id}/notes", user) - - expect(response).to have_gitlab_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array - expect(json_response.first['body']).to eq(snippet_note.note) - end - - it "returns a 404 error when snippet id not found" do - get v3_api("/projects/#{project.id}/snippets/42/notes", user) - - expect(response).to have_gitlab_http_status(404) - end - - it "returns 404 when not authorized" do - get v3_api("/projects/#{project.id}/snippets/#{snippet.id}/notes", private_user) - - expect(response).to have_gitlab_http_status(404) - end - end - - context "when noteable is a Merge Request" do - it "returns an array of merge_requests notes" do - get v3_api("/projects/#{project.id}/merge_requests/#{merge_request.id}/notes", user) - - expect(response).to have_gitlab_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array - expect(json_response.first['body']).to eq(merge_request_note.note) - end - - it "returns a 404 error if merge request id not found" do - get v3_api("/projects/#{project.id}/merge_requests/4444/notes", user) - - expect(response).to have_gitlab_http_status(404) - end - - it "returns 404 when not authorized" do - get v3_api("/projects/#{project.id}/merge_requests/4444/notes", private_user) - - expect(response).to have_gitlab_http_status(404) - end - end - end - - describe "GET /projects/:id/noteable/:noteable_id/notes/:note_id" do - context "when noteable is an Issue" do - it "returns an issue note by id" do - get v3_api("/projects/#{project.id}/issues/#{issue.id}/notes/#{issue_note.id}", user) - - expect(response).to have_gitlab_http_status(200) - expect(json_response['body']).to eq(issue_note.note) - end - - it "returns a 404 error if issue note not found" do - get v3_api("/projects/#{project.id}/issues/#{issue.id}/notes/12345", user) - - expect(response).to have_gitlab_http_status(404) - end - - context "and current user cannot view the note" do - it "returns a 404 error" do - get v3_api("/projects/#{ext_proj.id}/issues/#{ext_issue.id}/notes/#{cross_reference_note.id}", user) - - expect(response).to have_gitlab_http_status(404) - end - - context "when issue is confidential" do - before { issue.update_attributes(confidential: true) } - - it "returns 404" do - get v3_api("/projects/#{project.id}/issues/#{issue.id}/notes/#{issue_note.id}", private_user) - - expect(response).to have_gitlab_http_status(404) - end - end - - context "and current user can view the note" do - it "returns an issue note by id" do - get v3_api("/projects/#{ext_proj.id}/issues/#{ext_issue.id}/notes/#{cross_reference_note.id}", private_user) - - expect(response).to have_gitlab_http_status(200) - expect(json_response['body']).to eq(cross_reference_note.note) - end - end - end - end - - context "when noteable is a Snippet" do - it "returns a snippet note by id" do - get v3_api("/projects/#{project.id}/snippets/#{snippet.id}/notes/#{snippet_note.id}", user) - - expect(response).to have_gitlab_http_status(200) - expect(json_response['body']).to eq(snippet_note.note) - end - - it "returns a 404 error if snippet note not found" do - get v3_api("/projects/#{project.id}/snippets/#{snippet.id}/notes/12345", user) - - expect(response).to have_gitlab_http_status(404) - end - end - end - - describe "POST /projects/:id/noteable/:noteable_id/notes" do - context "when noteable is an Issue" do - it "creates a new issue note" do - post v3_api("/projects/#{project.id}/issues/#{issue.id}/notes", user), body: 'hi!' - - expect(response).to have_gitlab_http_status(201) - expect(json_response['body']).to eq('hi!') - expect(json_response['author']['username']).to eq(user.username) - end - - it "returns a 400 bad request error if body not given" do - post v3_api("/projects/#{project.id}/issues/#{issue.id}/notes", user) - - expect(response).to have_gitlab_http_status(400) - end - - it "returns a 401 unauthorized error if user not authenticated" do - post v3_api("/projects/#{project.id}/issues/#{issue.id}/notes"), body: 'hi!' - - expect(response).to have_gitlab_http_status(401) - end - - context 'when an admin or owner makes the request' do - it 'accepts the creation date to be set' do - creation_time = 2.weeks.ago - post v3_api("/projects/#{project.id}/issues/#{issue.id}/notes", user), - body: 'hi!', created_at: creation_time - - expect(response).to have_gitlab_http_status(201) - expect(json_response['body']).to eq('hi!') - expect(json_response['author']['username']).to eq(user.username) - expect(Time.parse(json_response['created_at'])).to be_like_time(creation_time) - end - end - - context 'when the user is posting an award emoji on an issue created by someone else' do - let(:issue2) { create(:issue, project: project) } - - it 'creates a new issue note' do - post v3_api("/projects/#{project.id}/issues/#{issue2.id}/notes", user), body: ':+1:' - - expect(response).to have_gitlab_http_status(201) - expect(json_response['body']).to eq(':+1:') - end - end - - context 'when the user is posting an award emoji on his/her own issue' do - it 'creates a new issue note' do - post v3_api("/projects/#{project.id}/issues/#{issue.id}/notes", user), body: ':+1:' - - expect(response).to have_gitlab_http_status(201) - expect(json_response['body']).to eq(':+1:') - end - end - end - - context "when noteable is a Snippet" do - it "creates a new snippet note" do - post v3_api("/projects/#{project.id}/snippets/#{snippet.id}/notes", user), body: 'hi!' - - expect(response).to have_gitlab_http_status(201) - expect(json_response['body']).to eq('hi!') - expect(json_response['author']['username']).to eq(user.username) - end - - it "returns a 400 bad request error if body not given" do - post v3_api("/projects/#{project.id}/snippets/#{snippet.id}/notes", user) - - expect(response).to have_gitlab_http_status(400) - end - - it "returns a 401 unauthorized error if user not authenticated" do - post v3_api("/projects/#{project.id}/snippets/#{snippet.id}/notes"), body: 'hi!' - - expect(response).to have_gitlab_http_status(401) - end - end - - context 'when user does not have access to read the noteable' do - it 'responds with 404' do - project = create(:project, :private) { |p| p.add_guest(user) } - issue = create(:issue, :confidential, project: project) - - post v3_api("/projects/#{project.id}/issues/#{issue.id}/notes", user), - body: 'Foo' - - expect(response).to have_gitlab_http_status(404) - end - end - - context 'when user does not have access to create noteable' do - let(:private_issue) { create(:issue, project: create(:project, :private)) } - - ## - # We are posting to project user has access to, but we use issue id - # from a different project, see #15577 - # - before do - post v3_api("/projects/#{project.id}/issues/#{private_issue.id}/notes", user), - body: 'Hi!' - end - - it 'responds with resource not found error' do - expect(response.status).to eq 404 - end - - it 'does not create new note' do - expect(private_issue.notes.reload).to be_empty - end - end - end - - describe "POST /projects/:id/noteable/:noteable_id/notes to test observer on create" do - it "creates an activity event when an issue note is created" do - expect(Event).to receive(:create!) - - post v3_api("/projects/#{project.id}/issues/#{issue.id}/notes", user), body: 'hi!' - end - end - - describe 'PUT /projects/:id/noteable/:noteable_id/notes/:note_id' do - context 'when noteable is an Issue' do - it 'returns modified note' do - put v3_api("/projects/#{project.id}/issues/#{issue.id}/"\ - "notes/#{issue_note.id}", user), body: 'Hello!' - - expect(response).to have_gitlab_http_status(200) - expect(json_response['body']).to eq('Hello!') - end - - it 'returns a 404 error when note id not found' do - put v3_api("/projects/#{project.id}/issues/#{issue.id}/notes/12345", user), - body: 'Hello!' - - expect(response).to have_gitlab_http_status(404) - end - - it 'returns a 400 bad request error if body not given' do - put v3_api("/projects/#{project.id}/issues/#{issue.id}/"\ - "notes/#{issue_note.id}", user) - - expect(response).to have_gitlab_http_status(400) - end - end - - context 'when noteable is a Snippet' do - it 'returns modified note' do - put v3_api("/projects/#{project.id}/snippets/#{snippet.id}/"\ - "notes/#{snippet_note.id}", user), body: 'Hello!' - - expect(response).to have_gitlab_http_status(200) - expect(json_response['body']).to eq('Hello!') - end - - it 'returns a 404 error when note id not found' do - put v3_api("/projects/#{project.id}/snippets/#{snippet.id}/"\ - "notes/12345", user), body: "Hello!" - - expect(response).to have_gitlab_http_status(404) - end - end - - context 'when noteable is a Merge Request' do - it 'returns modified note' do - put v3_api("/projects/#{project.id}/merge_requests/#{merge_request.id}/"\ - "notes/#{merge_request_note.id}", user), body: 'Hello!' - - expect(response).to have_gitlab_http_status(200) - expect(json_response['body']).to eq('Hello!') - end - - it 'returns a 404 error when note id not found' do - put v3_api("/projects/#{project.id}/merge_requests/#{merge_request.id}/"\ - "notes/12345", user), body: "Hello!" - - expect(response).to have_gitlab_http_status(404) - end - end - end - - describe 'DELETE /projects/:id/noteable/:noteable_id/notes/:note_id' do - context 'when noteable is an Issue' do - it 'deletes a note' do - delete v3_api("/projects/#{project.id}/issues/#{issue.id}/"\ - "notes/#{issue_note.id}", user) - - expect(response).to have_gitlab_http_status(200) - # Check if note is really deleted - delete v3_api("/projects/#{project.id}/issues/#{issue.id}/"\ - "notes/#{issue_note.id}", user) - expect(response).to have_gitlab_http_status(404) - end - - it 'returns a 404 error when note id not found' do - delete v3_api("/projects/#{project.id}/issues/#{issue.id}/notes/12345", user) - - expect(response).to have_gitlab_http_status(404) - end - end - - context 'when noteable is a Snippet' do - it 'deletes a note' do - delete v3_api("/projects/#{project.id}/snippets/#{snippet.id}/"\ - "notes/#{snippet_note.id}", user) - - expect(response).to have_gitlab_http_status(200) - # Check if note is really deleted - delete v3_api("/projects/#{project.id}/snippets/#{snippet.id}/"\ - "notes/#{snippet_note.id}", user) - expect(response).to have_gitlab_http_status(404) - end - - it 'returns a 404 error when note id not found' do - delete v3_api("/projects/#{project.id}/snippets/#{snippet.id}/"\ - "notes/12345", user) - - expect(response).to have_gitlab_http_status(404) - end - end - - context 'when noteable is a Merge Request' do - it 'deletes a note' do - delete v3_api("/projects/#{project.id}/merge_requests/"\ - "#{merge_request.id}/notes/#{merge_request_note.id}", user) - - expect(response).to have_gitlab_http_status(200) - # Check if note is really deleted - delete v3_api("/projects/#{project.id}/merge_requests/"\ - "#{merge_request.id}/notes/#{merge_request_note.id}", user) - expect(response).to have_gitlab_http_status(404) - end - - it 'returns a 404 error when note id not found' do - delete v3_api("/projects/#{project.id}/merge_requests/"\ - "#{merge_request.id}/notes/12345", user) - - expect(response).to have_gitlab_http_status(404) - end - end - end -end diff --git a/spec/requests/api/v3/pipelines_spec.rb b/spec/requests/api/v3/pipelines_spec.rb deleted file mode 100644 index ea943f22c41..00000000000 --- a/spec/requests/api/v3/pipelines_spec.rb +++ /dev/null @@ -1,201 +0,0 @@ -require 'spec_helper' - -describe API::V3::Pipelines do - let(:user) { create(:user) } - let(:non_member) { create(:user) } - let(:project) { create(:project, :repository, creator: user) } - - let!(:pipeline) do - create(:ci_empty_pipeline, project: project, sha: project.commit.id, - ref: project.default_branch) - end - - before { project.add_master(user) } - - shared_examples 'a paginated resources' do - before do - # Fires the request - request - end - - it 'has pagination headers' do - expect(response).to include_pagination_headers - end - end - - describe 'GET /projects/:id/pipelines ' do - it_behaves_like 'a paginated resources' do - let(:request) { get v3_api("/projects/#{project.id}/pipelines", user) } - end - - context 'authorized user' do - it 'returns project pipelines' do - get v3_api("/projects/#{project.id}/pipelines", user) - - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.first['sha']).to match(/\A\h{40}\z/) - expect(json_response.first['id']).to eq pipeline.id - expect(json_response.first.keys).to contain_exactly(*%w[id sha ref status before_sha tag yaml_errors user created_at updated_at started_at finished_at committed_at duration coverage]) - end - end - - context 'unauthorized user' do - it 'does not return project pipelines' do - get v3_api("/projects/#{project.id}/pipelines", non_member) - - expect(response).to have_gitlab_http_status(404) - expect(json_response['message']).to eq '404 Project Not Found' - expect(json_response).not_to be_an Array - end - end - end - - describe 'POST /projects/:id/pipeline ' do - context 'authorized user' do - context 'with gitlab-ci.yml' do - before { stub_ci_pipeline_to_return_yaml_file } - - it 'creates and returns a new pipeline' do - expect do - post v3_api("/projects/#{project.id}/pipeline", user), ref: project.default_branch - end.to change { Ci::Pipeline.count }.by(1) - - expect(response).to have_gitlab_http_status(201) - expect(json_response).to be_a Hash - expect(json_response['sha']).to eq project.commit.id - end - - it 'fails when using an invalid ref' do - post v3_api("/projects/#{project.id}/pipeline", user), ref: 'invalid_ref' - - expect(response).to have_gitlab_http_status(400) - expect(json_response['message']['base'].first).to eq 'Reference not found' - expect(json_response).not_to be_an Array - end - end - - context 'without gitlab-ci.yml' do - it 'fails to create pipeline' do - post v3_api("/projects/#{project.id}/pipeline", user), ref: project.default_branch - - expect(response).to have_gitlab_http_status(400) - expect(json_response['message']['base'].first).to eq 'Missing .gitlab-ci.yml file' - expect(json_response).not_to be_an Array - end - end - end - - context 'unauthorized user' do - it 'does not create pipeline' do - post v3_api("/projects/#{project.id}/pipeline", non_member), ref: project.default_branch - - expect(response).to have_gitlab_http_status(404) - expect(json_response['message']).to eq '404 Project Not Found' - expect(json_response).not_to be_an Array - end - end - end - - describe 'GET /projects/:id/pipelines/:pipeline_id' do - context 'authorized user' do - it 'returns project pipelines' do - get v3_api("/projects/#{project.id}/pipelines/#{pipeline.id}", user) - - expect(response).to have_gitlab_http_status(200) - expect(json_response['sha']).to match /\A\h{40}\z/ - end - - it 'returns 404 when it does not exist' do - get v3_api("/projects/#{project.id}/pipelines/123456", user) - - expect(response).to have_gitlab_http_status(404) - expect(json_response['message']).to eq '404 Not found' - expect(json_response['id']).to be nil - end - - context 'with coverage' do - before do - create(:ci_build, coverage: 30, pipeline: pipeline) - end - - it 'exposes the coverage' do - get v3_api("/projects/#{project.id}/pipelines/#{pipeline.id}", user) - - expect(json_response["coverage"].to_i).to eq(30) - end - end - end - - context 'unauthorized user' do - it 'should not return a project pipeline' do - get v3_api("/projects/#{project.id}/pipelines/#{pipeline.id}", non_member) - - expect(response).to have_gitlab_http_status(404) - expect(json_response['message']).to eq '404 Project Not Found' - expect(json_response['id']).to be nil - end - end - end - - describe 'POST /projects/:id/pipelines/:pipeline_id/retry' do - context 'authorized user' do - let!(:pipeline) do - create(:ci_pipeline, project: project, sha: project.commit.id, - ref: project.default_branch) - end - - let!(:build) { create(:ci_build, :failed, pipeline: pipeline) } - - it 'retries failed builds' do - expect do - post v3_api("/projects/#{project.id}/pipelines/#{pipeline.id}/retry", user) - end.to change { pipeline.builds.count }.from(1).to(2) - - expect(response).to have_gitlab_http_status(201) - expect(build.reload.retried?).to be true - end - end - - context 'unauthorized user' do - it 'should not return a project pipeline' do - post v3_api("/projects/#{project.id}/pipelines/#{pipeline.id}/retry", non_member) - - expect(response).to have_gitlab_http_status(404) - expect(json_response['message']).to eq '404 Project Not Found' - expect(json_response['id']).to be nil - end - end - end - - describe 'POST /projects/:id/pipelines/:pipeline_id/cancel' do - let!(:pipeline) do - create(:ci_empty_pipeline, project: project, sha: project.commit.id, - ref: project.default_branch) - end - - let!(:build) { create(:ci_build, :running, pipeline: pipeline) } - - context 'authorized user' do - it 'retries failed builds' do - post v3_api("/projects/#{project.id}/pipelines/#{pipeline.id}/cancel", user) - - expect(response).to have_gitlab_http_status(200) - expect(json_response['status']).to eq('canceled') - end - end - - context 'user without proper access rights' do - let!(:reporter) { create(:user) } - - before { project.add_reporter(reporter) } - - it 'rejects the action' do - post v3_api("/projects/#{project.id}/pipelines/#{pipeline.id}/cancel", reporter) - - expect(response).to have_gitlab_http_status(403) - expect(pipeline.reload.status).to eq('pending') - end - end - end -end diff --git a/spec/requests/api/v3/project_hooks_spec.rb b/spec/requests/api/v3/project_hooks_spec.rb deleted file mode 100644 index 8f6a2330d25..00000000000 --- a/spec/requests/api/v3/project_hooks_spec.rb +++ /dev/null @@ -1,219 +0,0 @@ -require 'spec_helper' - -describe API::ProjectHooks, 'ProjectHooks' do - let(:user) { create(:user) } - let(:user3) { create(:user) } - let!(:project) { create(:project, creator_id: user.id, namespace: user.namespace) } - let!(:hook) do - create(:project_hook, - :all_events_enabled, - project: project, - url: 'http://example.com', - enable_ssl_verification: true) - end - - before do - project.add_master(user) - project.add_developer(user3) - end - - describe "GET /projects/:id/hooks" do - context "authorized user" do - it "returns project hooks" do - get v3_api("/projects/#{project.id}/hooks", user) - - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.count).to eq(1) - expect(json_response.first['url']).to eq("http://example.com") - expect(json_response.first['issues_events']).to eq(true) - expect(json_response.first['confidential_issues_events']).to eq(true) - expect(json_response.first['push_events']).to eq(true) - expect(json_response.first['merge_requests_events']).to eq(true) - expect(json_response.first['tag_push_events']).to eq(true) - expect(json_response.first['note_events']).to eq(true) - expect(json_response.first['build_events']).to eq(true) - expect(json_response.first['pipeline_events']).to eq(true) - expect(json_response.first['wiki_page_events']).to eq(true) - expect(json_response.first['enable_ssl_verification']).to eq(true) - end - end - - context "unauthorized user" do - it "does not access project hooks" do - get v3_api("/projects/#{project.id}/hooks", user3) - - expect(response).to have_gitlab_http_status(403) - end - end - end - - describe "GET /projects/:id/hooks/:hook_id" do - context "authorized user" do - it "returns a project hook" do - get v3_api("/projects/#{project.id}/hooks/#{hook.id}", user) - expect(response).to have_gitlab_http_status(200) - expect(json_response['url']).to eq(hook.url) - expect(json_response['issues_events']).to eq(hook.issues_events) - expect(json_response['confidential_issues_events']).to eq(hook.confidential_issues_events) - expect(json_response['push_events']).to eq(hook.push_events) - expect(json_response['merge_requests_events']).to eq(hook.merge_requests_events) - expect(json_response['tag_push_events']).to eq(hook.tag_push_events) - expect(json_response['note_events']).to eq(hook.note_events) - expect(json_response['build_events']).to eq(hook.job_events) - expect(json_response['pipeline_events']).to eq(hook.pipeline_events) - expect(json_response['wiki_page_events']).to eq(hook.wiki_page_events) - expect(json_response['enable_ssl_verification']).to eq(hook.enable_ssl_verification) - end - - it "returns a 404 error if hook id is not available" do - get v3_api("/projects/#{project.id}/hooks/1234", user) - expect(response).to have_gitlab_http_status(404) - end - end - - context "unauthorized user" do - it "does not access an existing hook" do - get v3_api("/projects/#{project.id}/hooks/#{hook.id}", user3) - expect(response).to have_gitlab_http_status(403) - end - end - - it "returns a 404 error if hook id is not available" do - get v3_api("/projects/#{project.id}/hooks/1234", user) - expect(response).to have_gitlab_http_status(404) - end - end - - describe "POST /projects/:id/hooks" do - it "adds hook to project" do - expect do - post v3_api("/projects/#{project.id}/hooks", user), - url: "http://example.com", issues_events: true, confidential_issues_events: true, wiki_page_events: true, build_events: true - end.to change {project.hooks.count}.by(1) - - expect(response).to have_gitlab_http_status(201) - expect(json_response['url']).to eq('http://example.com') - expect(json_response['issues_events']).to eq(true) - expect(json_response['confidential_issues_events']).to eq(true) - expect(json_response['push_events']).to eq(true) - expect(json_response['merge_requests_events']).to eq(false) - expect(json_response['tag_push_events']).to eq(false) - expect(json_response['note_events']).to eq(false) - expect(json_response['build_events']).to eq(true) - expect(json_response['pipeline_events']).to eq(false) - expect(json_response['wiki_page_events']).to eq(true) - expect(json_response['enable_ssl_verification']).to eq(true) - expect(json_response).not_to include('token') - end - - it "adds the token without including it in the response" do - token = "secret token" - - expect do - post v3_api("/projects/#{project.id}/hooks", user), url: "http://example.com", token: token - end.to change {project.hooks.count}.by(1) - - expect(response).to have_gitlab_http_status(201) - expect(json_response["url"]).to eq("http://example.com") - expect(json_response).not_to include("token") - - hook = project.hooks.find(json_response["id"]) - - expect(hook.url).to eq("http://example.com") - expect(hook.token).to eq(token) - end - - it "returns a 400 error if url not given" do - post v3_api("/projects/#{project.id}/hooks", user) - expect(response).to have_gitlab_http_status(400) - end - - it "returns a 422 error if url not valid" do - post v3_api("/projects/#{project.id}/hooks", user), "url" => "ftp://example.com" - expect(response).to have_gitlab_http_status(422) - end - end - - describe "PUT /projects/:id/hooks/:hook_id" do - it "updates an existing project hook" do - put v3_api("/projects/#{project.id}/hooks/#{hook.id}", user), - url: 'http://example.org', push_events: false, build_events: true - expect(response).to have_gitlab_http_status(200) - expect(json_response['url']).to eq('http://example.org') - expect(json_response['issues_events']).to eq(hook.issues_events) - expect(json_response['confidential_issues_events']).to eq(hook.confidential_issues_events) - expect(json_response['push_events']).to eq(false) - expect(json_response['merge_requests_events']).to eq(hook.merge_requests_events) - expect(json_response['tag_push_events']).to eq(hook.tag_push_events) - expect(json_response['note_events']).to eq(hook.note_events) - expect(json_response['build_events']).to eq(hook.job_events) - expect(json_response['pipeline_events']).to eq(hook.pipeline_events) - expect(json_response['wiki_page_events']).to eq(hook.wiki_page_events) - expect(json_response['enable_ssl_verification']).to eq(hook.enable_ssl_verification) - end - - it "adds the token without including it in the response" do - token = "secret token" - - put v3_api("/projects/#{project.id}/hooks/#{hook.id}", user), url: "http://example.org", token: token - - expect(response).to have_gitlab_http_status(200) - expect(json_response["url"]).to eq("http://example.org") - expect(json_response).not_to include("token") - - expect(hook.reload.url).to eq("http://example.org") - expect(hook.reload.token).to eq(token) - end - - it "returns 404 error if hook id not found" do - put v3_api("/projects/#{project.id}/hooks/1234", user), url: 'http://example.org' - expect(response).to have_gitlab_http_status(404) - end - - it "returns 400 error if url is not given" do - put v3_api("/projects/#{project.id}/hooks/#{hook.id}", user) - expect(response).to have_gitlab_http_status(400) - end - - it "returns a 422 error if url is not valid" do - put v3_api("/projects/#{project.id}/hooks/#{hook.id}", user), url: 'ftp://example.com' - expect(response).to have_gitlab_http_status(422) - end - end - - describe "DELETE /projects/:id/hooks/:hook_id" do - it "deletes hook from project" do - expect do - delete v3_api("/projects/#{project.id}/hooks/#{hook.id}", user) - end.to change {project.hooks.count}.by(-1) - expect(response).to have_gitlab_http_status(200) - end - - it "returns success when deleting hook" do - delete v3_api("/projects/#{project.id}/hooks/#{hook.id}", user) - expect(response).to have_gitlab_http_status(200) - end - - it "returns a 404 error when deleting non existent hook" do - delete v3_api("/projects/#{project.id}/hooks/42", user) - expect(response).to have_gitlab_http_status(404) - end - - it "returns a 404 error if hook id not given" do - delete v3_api("/projects/#{project.id}/hooks", user) - - expect(response).to have_gitlab_http_status(404) - end - - it "returns a 404 if a user attempts to delete project hooks he/she does not own" do - test_user = create(:user) - other_project = create(:project) - other_project.add_master(test_user) - - delete v3_api("/projects/#{other_project.id}/hooks/#{hook.id}", test_user) - expect(response).to have_gitlab_http_status(404) - expect(WebHook.exists?(hook.id)).to be_truthy - end - end -end diff --git a/spec/requests/api/v3/project_snippets_spec.rb b/spec/requests/api/v3/project_snippets_spec.rb deleted file mode 100644 index 2ed31b99516..00000000000 --- a/spec/requests/api/v3/project_snippets_spec.rb +++ /dev/null @@ -1,226 +0,0 @@ -require 'rails_helper' - -describe API::ProjectSnippets do - let(:project) { create(:project, :public) } - let(:user) { create(:user) } - let(:admin) { create(:admin) } - - describe 'GET /projects/:project_id/snippets/:id' do - # TODO (rspeicher): Deprecated; remove in 9.0 - it 'always exposes expires_at as nil' do - snippet = create(:project_snippet, author: admin) - - get v3_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 - - describe 'GET /projects/:project_id/snippets/' do - let(:user) { create(:user) } - - it 'returns all snippets available to team member' do - project.add_developer(user) - public_snippet = create(:project_snippet, :public, project: project) - internal_snippet = create(:project_snippet, :internal, project: project) - private_snippet = create(:project_snippet, :private, project: project) - - get v3_api("/projects/#{project.id}/snippets/", user) - - expect(response).to have_gitlab_http_status(200) - expect(json_response.size).to eq(3) - expect(json_response.map { |snippet| snippet['id']} ).to include(public_snippet.id, internal_snippet.id, private_snippet.id) - expect(json_response.last).to have_key('web_url') - end - - it 'hides private snippets from regular user' do - create(:project_snippet, :private, project: project) - - get v3_api("/projects/#{project.id}/snippets/", user) - expect(response).to have_gitlab_http_status(200) - expect(json_response.size).to eq(0) - end - end - - describe 'POST /projects/:project_id/snippets/' do - let(:params) do - { - title: 'Test Title', - file_name: 'test.rb', - code: 'puts "hello world"', - visibility_level: Snippet::PUBLIC - } - end - - it 'creates a new snippet' do - post v3_api("/projects/#{project.id}/snippets/", admin), params - - expect(response).to have_gitlab_http_status(201) - snippet = ProjectSnippet.find(json_response['id']) - expect(snippet.content).to eq(params[:code]) - expect(snippet.title).to eq(params[:title]) - expect(snippet.file_name).to eq(params[:file_name]) - expect(snippet.visibility_level).to eq(params[:visibility_level]) - end - - it 'returns 400 for missing parameters' do - params.delete(:title) - - post v3_api("/projects/#{project.id}/snippets/", admin), params - - expect(response).to have_gitlab_http_status(400) - end - - context 'when the snippet is spam' do - def create_snippet(project, snippet_params = {}) - project.add_developer(user) - - post v3_api("/projects/#{project.id}/snippets", user), params.merge(snippet_params) - end - - before do - allow_any_instance_of(AkismetService).to receive(:spam?).and_return(true) - end - - context 'when the snippet is private' do - it 'creates the snippet' do - expect { create_snippet(project, visibility_level: Snippet::PRIVATE) } - .to change { Snippet.count }.by(1) - end - end - - context 'when the snippet is public' do - it 'rejects the shippet' do - expect { create_snippet(project, visibility_level: Snippet::PUBLIC) } - .not_to change { Snippet.count } - - expect(response).to have_gitlab_http_status(400) - expect(json_response['message']).to eq({ "error" => "Spam detected" }) - end - - it 'creates a spam log' do - expect { create_snippet(project, visibility_level: Snippet::PUBLIC) } - .to change { SpamLog.count }.by(1) - end - end - end - end - - describe 'PUT /projects/:project_id/snippets/:id/' do - let(:visibility_level) { Snippet::PUBLIC } - let(:snippet) { create(:project_snippet, author: admin, visibility_level: visibility_level) } - - it 'updates snippet' do - new_content = 'New content' - - put v3_api("/projects/#{snippet.project.id}/snippets/#{snippet.id}/", admin), code: new_content - - expect(response).to have_gitlab_http_status(200) - snippet.reload - expect(snippet.content).to eq(new_content) - end - - it 'returns 404 for invalid snippet id' do - put v3_api("/projects/#{snippet.project.id}/snippets/1234", admin), title: 'foo' - - expect(response).to have_gitlab_http_status(404) - expect(json_response['message']).to eq('404 Snippet Not Found') - end - - it 'returns 400 for missing parameters' do - put v3_api("/projects/#{project.id}/snippets/1234", admin) - - expect(response).to have_gitlab_http_status(400) - end - - context 'when the snippet is spam' do - def update_snippet(snippet_params = {}) - put v3_api("/projects/#{snippet.project.id}/snippets/#{snippet.id}", admin), snippet_params - end - - before do - allow_any_instance_of(AkismetService).to receive(:spam?).and_return(true) - end - - context 'when the snippet is private' do - let(:visibility_level) { Snippet::PRIVATE } - - it 'creates the snippet' do - expect { update_snippet(title: 'Foo') } - .to change { snippet.reload.title }.to('Foo') - end - end - - context 'when the snippet is public' do - let(:visibility_level) { Snippet::PUBLIC } - - it 'rejects the snippet' do - expect { update_snippet(title: 'Foo') } - .not_to change { snippet.reload.title } - end - - it 'creates a spam log' do - expect { update_snippet(title: 'Foo') } - .to change { SpamLog.count }.by(1) - end - end - - context 'when the private snippet is made public' do - let(:visibility_level) { Snippet::PRIVATE } - - it 'rejects the snippet' do - expect { update_snippet(title: 'Foo', visibility_level: Snippet::PUBLIC) } - .not_to change { snippet.reload.title } - - expect(response).to have_gitlab_http_status(400) - expect(json_response['message']).to eq({ "error" => "Spam detected" }) - end - - it 'creates a spam log' do - expect { update_snippet(title: 'Foo', visibility_level: Snippet::PUBLIC) } - .to change { SpamLog.count }.by(1) - end - end - end - end - - describe 'DELETE /projects/:project_id/snippets/:id/' do - let(:snippet) { create(:project_snippet, author: admin) } - - it 'deletes snippet' do - admin = create(:admin) - snippet = create(:project_snippet, author: admin) - - delete v3_api("/projects/#{snippet.project.id}/snippets/#{snippet.id}/", admin) - - expect(response).to have_gitlab_http_status(200) - end - - it 'returns 404 for invalid snippet id' do - delete v3_api("/projects/#{snippet.project.id}/snippets/1234", admin) - - expect(response).to have_gitlab_http_status(404) - expect(json_response['message']).to eq('404 Snippet Not Found') - end - end - - describe 'GET /projects/:project_id/snippets/:id/raw' do - let(:snippet) { create(:project_snippet, author: admin) } - - it 'returns raw text' do - get v3_api("/projects/#{snippet.project.id}/snippets/#{snippet.id}/raw", admin) - - expect(response).to have_gitlab_http_status(200) - expect(response.content_type).to eq 'text/plain' - expect(response.body).to eq(snippet.content) - end - - it 'returns 404 for invalid snippet id' do - delete v3_api("/projects/#{snippet.project.id}/snippets/1234", admin) - - expect(response).to have_gitlab_http_status(404) - expect(json_response['message']).to eq('404 Snippet Not Found') - end - end -end diff --git a/spec/requests/api/v3/projects_spec.rb b/spec/requests/api/v3/projects_spec.rb deleted file mode 100644 index 158ddf171bc..00000000000 --- a/spec/requests/api/v3/projects_spec.rb +++ /dev/null @@ -1,1495 +0,0 @@ -require 'spec_helper' - -describe API::V3::Projects do - let(:user) { create(:user) } - let(:user2) { create(:user) } - let(:user3) { create(:user) } - let(:admin) { create(:admin) } - let(:project) { create(:project, creator_id: user.id, namespace: user.namespace) } - let(:project2) { create(:project, 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, :developer, user: user3, project: project) } - let(:user4) { create(:user) } - let(:project3) do - create(:project, - :private, - :repository, - 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) - end - let(:project_member2) do - create(:project_member, - user: user4, - project: project3, - access_level: ProjectMember::MASTER) - end - let(:project4) do - create(:project, - name: 'third_project', - path: 'third_project', - creator_id: user4.id, - namespace: user4.namespace) - end - - describe 'GET /projects' do - before { project } - - context 'when unauthenticated' do - it 'returns authentication error' do - get v3_api('/projects') - expect(response).to have_gitlab_http_status(401) - end - end - - context 'when authenticated as regular user' do - it 'returns an array of projects' do - get v3_api('/projects', user) - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.first['name']).to eq(project.name) - expect(json_response.first['owner']['username']).to eq(user.username) - end - - it 'includes the project labels as the tag_list' do - get v3_api('/projects', user) - expect(response.status).to eq 200 - expect(json_response).to be_an Array - expect(json_response.first.keys).to include('tag_list') - end - - it 'includes open_issues_count' do - get v3_api('/projects', user) - expect(response.status).to eq 200 - expect(json_response).to be_an Array - expect(json_response.first.keys).to include('open_issues_count') - end - - it 'does not include open_issues_count' do - project.project_feature.update_attribute(:issues_access_level, ProjectFeature::DISABLED) - - get v3_api('/projects', user) - expect(response.status).to eq 200 - expect(json_response).to be_an Array - expect(json_response.first.keys).not_to include('open_issues_count') - end - - context 'GET /projects?simple=true' do - it 'returns a simplified version of all the projects' do - expected_keys = %w( - id description default_branch tag_list - ssh_url_to_repo http_url_to_repo web_url readme_url - name name_with_namespace - path path_with_namespace - star_count forks_count - created_at last_activity_at - avatar_url - ) - - get v3_api('/projects?simple=true', user) - - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.first.keys).to match_array expected_keys - end - end - - context 'and using search' do - it 'returns searched project' do - get v3_api('/projects', user), { search: project.name } - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.length).to eq(1) - end - end - - context 'and using the visibility filter' do - it 'filters based on private visibility param' do - get v3_api('/projects', user), { visibility: 'private' } - expect(response).to have_gitlab_http_status(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 'filters based on internal visibility param' do - get v3_api('/projects', user), { visibility: 'internal' } - expect(response).to have_gitlab_http_status(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 'filters based on public visibility param' do - get v3_api('/projects', user), { visibility: 'public' } - expect(response).to have_gitlab_http_status(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 archived' do - let!(:archived_project) { create(:project, creator_id: user.id, namespace: user.namespace, archived: true) } - - it 'returns archived project' do - get v3_api('/projects?archived=true', user) - - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.length).to eq(1) - expect(json_response.first['id']).to eq(archived_project.id) - end - - it 'returns non-archived project' do - get v3_api('/projects?archived=false', user) - - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.length).to eq(1) - expect(json_response.first['id']).to eq(project.id) - end - - it 'returns all project' do - get v3_api('/projects', user) - - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.length).to eq(2) - end - end - - context 'and using sorting' do - before do - project2 - project3 - end - - it 'returns the correct order when sorted by id' do - get v3_api('/projects', user), { order_by: 'id', sort: 'desc' } - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.first['id']).to eq(project3.id) - end - end - end - end - - describe 'GET /projects/all' do - before { project } - - context 'when unauthenticated' do - it 'returns authentication error' do - get v3_api('/projects/all') - expect(response).to have_gitlab_http_status(401) - end - end - - context 'when authenticated as regular user' do - it 'returns authentication error' do - get v3_api('/projects/all', user) - expect(response).to have_gitlab_http_status(403) - end - end - - context 'when authenticated as admin' do - it 'returns an array of all projects' do - get v3_api('/projects/all', admin) - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - - expect(json_response).to satisfy do |response| - response.one? do |entry| - entry.key?('permissions') && - entry['name'] == project.name && - entry['owner']['username'] == user.username - end - end - end - - it "does not include statistics by default" do - get v3_api('/projects/all', admin) - - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.first).not_to include('statistics') - end - - it "includes statistics if requested" do - get v3_api('/projects/all', admin), statistics: true - - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.first).to include 'statistics' - end - end - end - - describe 'GET /projects/owned' do - before do - project3 - project4 - end - - context 'when unauthenticated' do - it 'returns authentication error' do - get v3_api('/projects/owned') - expect(response).to have_gitlab_http_status(401) - end - end - - context 'when authenticated as project owner' do - it 'returns an array of projects the user owns' do - get v3_api('/projects/owned', user4) - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.first['name']).to eq(project4.name) - expect(json_response.first['owner']['username']).to eq(user4.username) - end - - it "does not include statistics by default" do - get v3_api('/projects/owned', user4) - - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.first).not_to include('statistics') - end - - it "includes statistics if requested" do - attributes = { - commit_count: 23, - storage_size: 702, - repository_size: 123, - lfs_objects_size: 234, - build_artifacts_size: 345 - } - - project4.statistics.update!(attributes) - - get v3_api('/projects/owned', user4), statistics: true - - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.first['statistics']).to eq attributes.stringify_keys - end - end - end - - describe 'GET /projects/visible' do - shared_examples_for 'visible projects response' do - it 'returns the visible projects' do - get v3_api('/projects/visible', current_user) - - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.map { |p| p['id'] }).to contain_exactly(*projects.map(&:id)) - end - end - - let!(:public_project) { create(:project, :public) } - before do - project - project2 - project3 - project4 - end - - context 'when unauthenticated' do - it_behaves_like 'visible projects response' do - let(:current_user) { nil } - let(:projects) { [public_project] } - end - end - - context 'when authenticated' do - it_behaves_like 'visible projects response' do - let(:current_user) { user } - let(:projects) { [public_project, project, project2, project3] } - end - end - - context 'when authenticated as a different user' do - it_behaves_like 'visible projects response' do - let(:current_user) { user2 } - let(:projects) { [public_project] } - end - end - end - - describe 'GET /projects/starred' do - let(:public_project) { create(:project, :public) } - - before do - project_member - user3.update_attributes(starred_projects: [project, project2, project3, public_project]) - end - - it 'returns the starred projects viewable by the user' do - get v3_api('/projects/starred', user3) - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.map { |project| project['id'] }).to contain_exactly(project.id, public_project.id) - end - end - - describe 'POST /projects' do - context 'maximum number of projects reached' do - it 'does not create new project and respond with 403' do - allow_any_instance_of(User).to receive(:projects_limit_left).and_return(0) - expect { post v3_api('/projects', user2), name: 'foo' } - .to change {Project.count}.by(0) - expect(response).to have_gitlab_http_status(403) - end - end - - it 'creates new project without path but with name and returns 201' do - expect { post v3_api('/projects', user), name: 'Foo Project' } - .to change { Project.count }.by(1) - expect(response).to have_gitlab_http_status(201) - - project = Project.first - - expect(project.name).to eq('Foo Project') - expect(project.path).to eq('foo-project') - end - - it 'creates new project without name but with path and returns 201' do - expect { post v3_api('/projects', user), path: 'foo_project' } - .to change { Project.count }.by(1) - expect(response).to have_gitlab_http_status(201) - - project = Project.first - - expect(project.name).to eq('foo_project') - expect(project.path).to eq('foo_project') - end - - it 'creates new project name and path and returns 201' do - expect { post v3_api('/projects', user), path: 'foo-Project', name: 'Foo Project' } - .to change { Project.count }.by(1) - expect(response).to have_gitlab_http_status(201) - - project = Project.first - - expect(project.name).to eq('Foo Project') - expect(project.path).to eq('foo-Project') - end - - it 'creates last project before reaching project limit' do - allow_any_instance_of(User).to receive(:projects_limit_left).and_return(1) - post v3_api('/projects', user2), name: 'foo' - expect(response).to have_gitlab_http_status(201) - end - - it 'does not create new project without name or path and return 400' do - expect { post v3_api('/projects', user) }.not_to change { Project.count } - expect(response).to have_gitlab_http_status(400) - end - - it "assigns attributes to project" do - project = attributes_for(:project, { - path: 'camelCasePath', - issues_enabled: false, - merge_requests_enabled: false, - wiki_enabled: false, - only_allow_merge_if_build_succeeds: false, - request_access_enabled: true, - only_allow_merge_if_all_discussions_are_resolved: false - }) - - post v3_api('/projects', user), project - - project.each_pair do |k, v| - next if %i[storage_version has_external_issue_tracker issues_enabled merge_requests_enabled wiki_enabled].include?(k) - - expect(json_response[k.to_s]).to eq(v) - end - - # Check feature permissions attributes - project = Project.find_by_path(project[:path]) - expect(project.project_feature.issues_access_level).to eq(ProjectFeature::DISABLED) - expect(project.project_feature.merge_requests_access_level).to eq(ProjectFeature::DISABLED) - expect(project.project_feature.wiki_access_level).to eq(ProjectFeature::DISABLED) - end - - it 'sets a project as public' do - project = attributes_for(:project, :public) - post v3_api('/projects', user), project - expect(json_response['public']).to be_truthy - expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::PUBLIC) - end - - it 'sets a project as public using :public' do - project = attributes_for(:project, { public: true }) - post v3_api('/projects', user), project - expect(json_response['public']).to be_truthy - expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::PUBLIC) - end - - it 'sets a project as internal' do - project = attributes_for(:project, :internal) - post v3_api('/projects', user), project - expect(json_response['public']).to be_falsey - expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::INTERNAL) - end - - it 'sets a project as internal overriding :public' do - project = attributes_for(:project, :internal, { public: true }) - post v3_api('/projects', user), project - expect(json_response['public']).to be_falsey - expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::INTERNAL) - end - - it 'sets a project as private' do - project = attributes_for(:project, :private) - post v3_api('/projects', user), project - expect(json_response['public']).to be_falsey - expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::PRIVATE) - end - - it 'sets a project as private using :public' do - project = attributes_for(:project, { public: false }) - post v3_api('/projects', user), project - expect(json_response['public']).to be_falsey - expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::PRIVATE) - end - - it 'sets a project as allowing merge even if build fails' do - project = attributes_for(:project, { only_allow_merge_if_build_succeeds: false }) - post v3_api('/projects', user), project - expect(json_response['only_allow_merge_if_build_succeeds']).to be_falsey - end - - it 'sets a project as allowing merge only if merge_when_pipeline_succeeds' do - project = attributes_for(:project, { only_allow_merge_if_build_succeeds: true }) - post v3_api('/projects', user), project - expect(json_response['only_allow_merge_if_build_succeeds']).to be_truthy - end - - it 'sets a project as allowing merge even if discussions are unresolved' do - project = attributes_for(:project, { only_allow_merge_if_all_discussions_are_resolved: false }) - - post v3_api('/projects', user), project - - expect(json_response['only_allow_merge_if_all_discussions_are_resolved']).to be_falsey - end - - it 'sets a project as allowing merge if only_allow_merge_if_all_discussions_are_resolved is nil' do - project = attributes_for(:project, only_allow_merge_if_all_discussions_are_resolved: nil) - - post v3_api('/projects', user), project - - expect(json_response['only_allow_merge_if_all_discussions_are_resolved']).to be_falsey - end - - it 'sets a project as allowing merge only if all discussions are resolved' do - project = attributes_for(:project, { only_allow_merge_if_all_discussions_are_resolved: true }) - - post v3_api('/projects', user), project - - expect(json_response['only_allow_merge_if_all_discussions_are_resolved']).to be_truthy - end - - context 'when a visibility level is restricted' do - before do - @project = attributes_for(:project, { public: true }) - stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC]) - end - - it 'does not allow a non-admin to use a restricted visibility level' do - post v3_api('/projects', user), @project - - expect(response).to have_gitlab_http_status(400) - expect(json_response['message']['visibility_level'].first).to( - match('restricted by your GitLab administrator') - ) - end - - it 'allows an admin to override restricted visibility settings' do - post v3_api('/projects', admin), @project - expect(json_response['public']).to be_truthy - expect(json_response['visibility_level']).to( - eq(Gitlab::VisibilityLevel::PUBLIC) - ) - end - end - end - - describe 'POST /projects/user/:id' do - before { project } - before { admin } - - it 'should create new project without path and return 201' do - expect { post v3_api("/projects/user/#{user.id}", admin), name: 'foo' }.to change {Project.count}.by(1) - expect(response).to have_gitlab_http_status(201) - end - - it 'responds with 400 on failure and not project' do - expect { post v3_api("/projects/user/#{user.id}", admin) } - .not_to change { Project.count } - - expect(response).to have_gitlab_http_status(400) - expect(json_response['error']).to eq('name is missing') - end - - it 'assigns attributes to project' do - project = attributes_for(:project, { - issues_enabled: false, - merge_requests_enabled: false, - wiki_enabled: false, - request_access_enabled: true - }) - - post v3_api("/projects/user/#{user.id}", admin), project - - expect(response).to have_gitlab_http_status(201) - project.each_pair do |k, v| - next if %i[storage_version has_external_issue_tracker path].include?(k) - - expect(json_response[k.to_s]).to eq(v) - end - end - - it 'sets a project as public' do - project = attributes_for(:project, :public) - post v3_api("/projects/user/#{user.id}", admin), project - - expect(response).to have_gitlab_http_status(201) - expect(json_response['public']).to be_truthy - expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::PUBLIC) - end - - it 'sets a project as public using :public' do - project = attributes_for(:project, { public: true }) - post v3_api("/projects/user/#{user.id}", admin), project - - expect(response).to have_gitlab_http_status(201) - expect(json_response['public']).to be_truthy - expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::PUBLIC) - end - - it 'sets a project as internal' do - project = attributes_for(:project, :internal) - post v3_api("/projects/user/#{user.id}", admin), project - - expect(response).to have_gitlab_http_status(201) - expect(json_response['public']).to be_falsey - expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::INTERNAL) - end - - it 'sets a project as internal overriding :public' do - project = attributes_for(:project, :internal, { public: true }) - post v3_api("/projects/user/#{user.id}", admin), project - expect(response).to have_gitlab_http_status(201) - expect(json_response['public']).to be_falsey - expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::INTERNAL) - end - - it 'sets a project as private' do - project = attributes_for(:project, :private) - post v3_api("/projects/user/#{user.id}", admin), project - expect(json_response['public']).to be_falsey - expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::PRIVATE) - end - - it 'sets a project as private using :public' do - project = attributes_for(:project, { public: false }) - post v3_api("/projects/user/#{user.id}", admin), project - expect(json_response['public']).to be_falsey - expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::PRIVATE) - end - - it 'sets a project as allowing merge even if build fails' do - project = attributes_for(:project, { only_allow_merge_if_build_succeeds: false }) - post v3_api("/projects/user/#{user.id}", admin), project - expect(json_response['only_allow_merge_if_build_succeeds']).to be_falsey - end - - it 'sets a project as allowing merge only if merge_when_pipeline_succeeds' do - project = attributes_for(:project, { only_allow_merge_if_build_succeeds: true }) - post v3_api("/projects/user/#{user.id}", admin), project - expect(json_response['only_allow_merge_if_build_succeeds']).to be_truthy - end - - it 'sets a project as allowing merge even if discussions are unresolved' do - project = attributes_for(:project, { only_allow_merge_if_all_discussions_are_resolved: false }) - - post v3_api("/projects/user/#{user.id}", admin), project - - expect(json_response['only_allow_merge_if_all_discussions_are_resolved']).to be_falsey - end - - it 'sets a project as allowing merge only if all discussions are resolved' do - project = attributes_for(:project, { only_allow_merge_if_all_discussions_are_resolved: true }) - - post v3_api("/projects/user/#{user.id}", admin), project - - expect(json_response['only_allow_merge_if_all_discussions_are_resolved']).to be_truthy - end - end - - describe "POST /projects/:id/uploads" do - before { project } - - it "uploads the file and returns its info" do - post v3_api("/projects/#{project.id}/uploads", user), file: fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "image/png") - - expect(response).to have_gitlab_http_status(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") - end - end - - describe 'GET /projects/:id' do - context 'when unauthenticated' do - it 'returns the public projects' do - public_project = create(:project, :public) - - get v3_api("/projects/#{public_project.id}") - - expect(response).to have_gitlab_http_status(200) - expect(json_response['id']).to eq(public_project.id) - expect(json_response['description']).to eq(public_project.description) - expect(json_response['default_branch']).to eq(public_project.default_branch) - expect(json_response.keys).not_to include('permissions') - end - end - - context 'when authenticated' do - before do - project - end - - it 'returns a project by id' do - group = create(:group) - link = create(:project_group_link, project: project, group: group) - - get v3_api("/projects/#{project.id}", user) - - expect(response).to have_gitlab_http_status(200) - expect(json_response['id']).to eq(project.id) - expect(json_response['description']).to eq(project.description) - expect(json_response['default_branch']).to eq(project.default_branch) - expect(json_response['tag_list']).to be_an Array - expect(json_response['public']).to be_falsey - expect(json_response['archived']).to be_falsey - expect(json_response['visibility_level']).to be_present - expect(json_response['ssh_url_to_repo']).to be_present - expect(json_response['http_url_to_repo']).to be_present - expect(json_response['web_url']).to be_present - expect(json_response['owner']).to be_a Hash - expect(json_response['owner']).to be_a Hash - expect(json_response['name']).to eq(project.name) - expect(json_response['path']).to be_present - expect(json_response['issues_enabled']).to be_present - expect(json_response['merge_requests_enabled']).to be_present - expect(json_response['wiki_enabled']).to be_present - expect(json_response['builds_enabled']).to be_present - expect(json_response['snippets_enabled']).to be_present - expect(json_response['resolve_outdated_diff_discussions']).to eq(project.resolve_outdated_diff_discussions) - expect(json_response['container_registry_enabled']).to be_present - expect(json_response['created_at']).to be_present - expect(json_response['last_activity_at']).to be_present - expect(json_response['shared_runners_enabled']).to be_present - expect(json_response['creator_id']).to be_present - expect(json_response['namespace']).to be_present - expect(json_response['avatar_url']).to be_nil - expect(json_response['star_count']).to be_present - expect(json_response['forks_count']).to be_present - expect(json_response['public_builds']).to be_present - expect(json_response['shared_with_groups']).to be_an Array - expect(json_response['shared_with_groups'].length).to eq(1) - expect(json_response['shared_with_groups'][0]['group_id']).to eq(group.id) - expect(json_response['shared_with_groups'][0]['group_name']).to eq(group.name) - expect(json_response['shared_with_groups'][0]['group_access_level']).to eq(link.group_access) - expect(json_response['only_allow_merge_if_build_succeeds']).to eq(project.only_allow_merge_if_pipeline_succeeds) - expect(json_response['only_allow_merge_if_all_discussions_are_resolved']).to eq(project.only_allow_merge_if_all_discussions_are_resolved) - end - - it 'returns a project by path name' do - get v3_api("/projects/#{project.id}", user) - expect(response).to have_gitlab_http_status(200) - expect(json_response['name']).to eq(project.name) - end - - it 'returns a 404 error if not found' do - get v3_api('/projects/42', user) - expect(response).to have_gitlab_http_status(404) - expect(json_response['message']).to eq('404 Project Not Found') - end - - it 'returns a 404 error if user is not a member' do - other_user = create(:user) - get v3_api("/projects/#{project.id}", other_user) - expect(response).to have_gitlab_http_status(404) - end - - it 'handles users with dots' do - dot_user = create(:user, username: 'dot.user') - project = create(:project, creator_id: dot_user.id, namespace: dot_user.namespace) - - get v3_api("/projects/#{CGI.escape(project.full_path)}", dot_user) - expect(response).to have_gitlab_http_status(200) - expect(json_response['name']).to eq(project.name) - end - - it 'exposes namespace fields' do - get v3_api("/projects/#{project.id}", user) - - expect(response).to have_gitlab_http_status(200) - expect(json_response['namespace']).to eq({ - 'id' => user.namespace.id, - 'name' => user.namespace.name, - 'path' => user.namespace.path, - 'kind' => user.namespace.kind, - 'full_path' => user.namespace.full_path, - 'parent_id' => nil - }) - end - - describe 'permissions' do - context 'all projects' do - before { project.add_master(user) } - - it 'contains permission information' do - get v3_api("/projects", user) - - expect(response).to have_gitlab_http_status(200) - expect(json_response.first['permissions']['project_access']['access_level']) - .to eq(Gitlab::Access::MASTER) - expect(json_response.first['permissions']['group_access']).to be_nil - end - end - - context 'personal project' do - it 'sets project access and returns 200' do - project.add_master(user) - get v3_api("/projects/#{project.id}", user) - - expect(response).to have_gitlab_http_status(200) - expect(json_response['permissions']['project_access']['access_level']) - .to eq(Gitlab::Access::MASTER) - expect(json_response['permissions']['group_access']).to be_nil - end - end - - context 'group project' do - let(:project2) { create(:project, group: create(:group)) } - - before { project2.group.add_owner(user) } - - it 'sets the owner and return 200' do - get v3_api("/projects/#{project2.id}", user) - - expect(response).to have_gitlab_http_status(200) - expect(json_response['permissions']['project_access']).to be_nil - expect(json_response['permissions']['group_access']['access_level']) - .to eq(Gitlab::Access::OWNER) - end - end - end - end - end - - describe 'GET /projects/:id/events' do - shared_examples_for 'project events response' do - it 'returns the project events' do - member = create(:user) - create(:project_member, :developer, user: member, project: project) - note = create(:note_on_issue, note: 'What an awesome day!', project: project) - EventCreateService.new.leave_note(note, note.author) - - get v3_api("/projects/#{project.id}/events", current_user) - - expect(response).to have_gitlab_http_status(200) - - first_event = json_response.first - - expect(first_event['action_name']).to eq('commented on') - expect(first_event['note']['body']).to eq('What an awesome day!') - - last_event = json_response.last - - expect(last_event['action_name']).to eq('joined') - expect(last_event['project_id'].to_i).to eq(project.id) - expect(last_event['author_username']).to eq(member.username) - expect(last_event['author']['name']).to eq(member.name) - end - end - - context 'when unauthenticated' do - it_behaves_like 'project events response' do - let(:project) { create(:project, :public) } - let(:current_user) { nil } - end - end - - context 'when authenticated' do - context 'valid request' do - it_behaves_like 'project events response' do - let(:current_user) { user } - end - end - - it 'returns a 404 error if not found' do - get v3_api('/projects/42/events', user) - - expect(response).to have_gitlab_http_status(404) - expect(json_response['message']).to eq('404 Project Not Found') - end - - it 'returns a 404 error if user is not a member' do - other_user = create(:user) - - get v3_api("/projects/#{project.id}/events", other_user) - - expect(response).to have_gitlab_http_status(404) - end - end - end - - describe 'GET /projects/:id/users' do - shared_examples_for 'project users response' do - it 'returns the project users' do - member = project.owner - - get v3_api("/projects/#{project.id}/users", current_user) - - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.size).to eq(1) - - first_user = json_response.first - - expect(first_user['username']).to eq(member.username) - expect(first_user['name']).to eq(member.name) - expect(first_user.keys).to contain_exactly(*%w[name username id state avatar_url web_url]) - end - end - - context 'when unauthenticated' do - it_behaves_like 'project users response' do - let(:project) { create(:project, :public) } - let(:current_user) { nil } - end - end - - context 'when authenticated' do - context 'valid request' do - it_behaves_like 'project users response' do - let(:current_user) { user } - end - end - - it 'returns a 404 error if not found' do - get v3_api('/projects/42/users', user) - - expect(response).to have_gitlab_http_status(404) - expect(json_response['message']).to eq('404 Project Not Found') - end - - it 'returns a 404 error if user is not a member' do - other_user = create(:user) - - get v3_api("/projects/#{project.id}/users", other_user) - - expect(response).to have_gitlab_http_status(404) - end - end - end - - describe 'GET /projects/:id/snippets' do - before { snippet } - - it 'returns an array of project snippets' do - get v3_api("/projects/#{project.id}/snippets", user) - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.first['title']).to eq(snippet.title) - end - end - - describe 'GET /projects/:id/snippets/:snippet_id' do - it 'returns a project snippet' do - get v3_api("/projects/#{project.id}/snippets/#{snippet.id}", user) - expect(response).to have_gitlab_http_status(200) - expect(json_response['title']).to eq(snippet.title) - end - - it 'returns a 404 error if snippet id not found' do - get v3_api("/projects/#{project.id}/snippets/1234", user) - expect(response).to have_gitlab_http_status(404) - end - end - - describe 'POST /projects/:id/snippets' do - it 'creates a new project snippet' do - post v3_api("/projects/#{project.id}/snippets", user), - title: 'v3_api test', file_name: 'sample.rb', code: 'test', - visibility_level: '0' - expect(response).to have_gitlab_http_status(201) - expect(json_response['title']).to eq('v3_api test') - end - - it 'returns a 400 error if invalid snippet is given' do - post v3_api("/projects/#{project.id}/snippets", user) - expect(status).to eq(400) - end - end - - describe 'PUT /projects/:id/snippets/:snippet_id' do - it 'updates an existing project snippet' do - put v3_api("/projects/#{project.id}/snippets/#{snippet.id}", user), - code: 'updated code' - expect(response).to have_gitlab_http_status(200) - expect(json_response['title']).to eq('example') - expect(snippet.reload.content).to eq('updated code') - end - - it 'updates an existing project snippet with new title' do - put v3_api("/projects/#{project.id}/snippets/#{snippet.id}", user), - title: 'other v3_api test' - expect(response).to have_gitlab_http_status(200) - expect(json_response['title']).to eq('other v3_api test') - end - end - - describe 'DELETE /projects/:id/snippets/:snippet_id' do - before { snippet } - - it 'deletes existing project snippet' do - expect do - delete v3_api("/projects/#{project.id}/snippets/#{snippet.id}", user) - end.to change { Snippet.count }.by(-1) - expect(response).to have_gitlab_http_status(200) - end - - it 'returns 404 when deleting unknown snippet id' do - delete v3_api("/projects/#{project.id}/snippets/1234", user) - expect(response).to have_gitlab_http_status(404) - end - end - - describe 'GET /projects/:id/snippets/:snippet_id/raw' do - it 'gets a raw project snippet' do - get v3_api("/projects/#{project.id}/snippets/#{snippet.id}/raw", user) - expect(response).to have_gitlab_http_status(200) - end - - it 'returns a 404 error if raw project snippet not found' do - get v3_api("/projects/#{project.id}/snippets/5555/raw", user) - expect(response).to have_gitlab_http_status(404) - end - end - - describe 'fork management' do - let(:project_fork_target) { create(:project) } - let(:project_fork_source) { create(:project, :public) } - - describe 'POST /projects/:id/fork/:forked_from_id' do - let(:new_project_fork_source) { create(:project, :public) } - - it "is not available for non admin users" do - post v3_api("/projects/#{project_fork_target.id}/fork/#{project_fork_source.id}", user) - expect(response).to have_gitlab_http_status(403) - end - - it 'allows project to be forked from an existing project' do - expect(project_fork_target.forked?).not_to be_truthy - post v3_api("/projects/#{project_fork_target.id}/fork/#{project_fork_source.id}", admin) - expect(response).to have_gitlab_http_status(201) - project_fork_target.reload - expect(project_fork_target.forked_from_project.id).to eq(project_fork_source.id) - expect(project_fork_target.forked_project_link).not_to be_nil - expect(project_fork_target.forked?).to be_truthy - end - - it 'refreshes the forks count cachce' do - expect(project_fork_source.forks_count).to be_zero - - post v3_api("/projects/#{project_fork_target.id}/fork/#{project_fork_source.id}", admin) - - expect(project_fork_source.forks_count).to eq(1) - end - - it 'fails if forked_from project which does not exist' do - post v3_api("/projects/#{project_fork_target.id}/fork/9999", admin) - expect(response).to have_gitlab_http_status(404) - end - - it 'fails with 409 if already forked' do - post v3_api("/projects/#{project_fork_target.id}/fork/#{project_fork_source.id}", admin) - project_fork_target.reload - expect(project_fork_target.forked_from_project.id).to eq(project_fork_source.id) - post v3_api("/projects/#{project_fork_target.id}/fork/#{new_project_fork_source.id}", admin) - expect(response).to have_gitlab_http_status(409) - project_fork_target.reload - expect(project_fork_target.forked_from_project.id).to eq(project_fork_source.id) - expect(project_fork_target.forked?).to be_truthy - end - end - - describe 'DELETE /projects/:id/fork' do - it "is not visible to users outside group" do - delete v3_api("/projects/#{project_fork_target.id}/fork", user) - expect(response).to have_gitlab_http_status(404) - end - - context 'when users belong to project group' do - let(:project_fork_target) { create(:project, group: create(:group)) } - - before do - project_fork_target.group.add_owner user - project_fork_target.group.add_developer user2 - end - - it 'is forbidden to non-owner users' do - delete v3_api("/projects/#{project_fork_target.id}/fork", user2) - expect(response).to have_gitlab_http_status(403) - end - - it 'makes forked project unforked' do - post v3_api("/projects/#{project_fork_target.id}/fork/#{project_fork_source.id}", admin) - project_fork_target.reload - expect(project_fork_target.forked_from_project).not_to be_nil - expect(project_fork_target.forked?).to be_truthy - delete v3_api("/projects/#{project_fork_target.id}/fork", admin) - expect(response).to have_gitlab_http_status(200) - project_fork_target.reload - expect(project_fork_target.forked_from_project).to be_nil - expect(project_fork_target.forked?).not_to be_truthy - end - - it 'is idempotent if not forked' do - expect(project_fork_target.forked_from_project).to be_nil - delete v3_api("/projects/#{project_fork_target.id}/fork", admin) - expect(response).to have_gitlab_http_status(304) - expect(project_fork_target.reload.forked_from_project).to be_nil - end - end - end - end - - describe "POST /projects/:id/share" do - let(:group) { create(:group) } - - it "shares project with group" do - expires_at = 10.days.from_now.to_date - - expect do - post v3_api("/projects/#{project.id}/share", user), group_id: group.id, group_access: Gitlab::Access::DEVELOPER, expires_at: expires_at - end.to change { ProjectGroupLink.count }.by(1) - - expect(response).to have_gitlab_http_status(201) - expect(json_response['group_id']).to eq(group.id) - expect(json_response['group_access']).to eq(Gitlab::Access::DEVELOPER) - expect(json_response['expires_at']).to eq(expires_at.to_s) - end - - it "returns a 400 error when group id is not given" do - post v3_api("/projects/#{project.id}/share", user), group_access: Gitlab::Access::DEVELOPER - expect(response).to have_gitlab_http_status(400) - end - - it "returns a 400 error when access level is not given" do - post v3_api("/projects/#{project.id}/share", user), group_id: group.id - expect(response).to have_gitlab_http_status(400) - end - - it "returns a 400 error when sharing is disabled" do - project.namespace.update(share_with_group_lock: true) - post v3_api("/projects/#{project.id}/share", user), group_id: group.id, group_access: Gitlab::Access::DEVELOPER - expect(response).to have_gitlab_http_status(400) - end - - it 'returns a 404 error when user cannot read group' do - private_group = create(:group, :private) - - post v3_api("/projects/#{project.id}/share", user), group_id: private_group.id, group_access: Gitlab::Access::DEVELOPER - - expect(response).to have_gitlab_http_status(404) - end - - it 'returns a 404 error when group does not exist' do - post v3_api("/projects/#{project.id}/share", user), group_id: 1234, group_access: Gitlab::Access::DEVELOPER - - expect(response).to have_gitlab_http_status(404) - end - - it "returns a 400 error when wrong params passed" do - post v3_api("/projects/#{project.id}/share", user), group_id: group.id, group_access: 1234 - - expect(response).to have_gitlab_http_status(400) - expect(json_response['error']).to eq 'group_access does not have a valid value' - end - end - - describe 'DELETE /projects/:id/share/:group_id' do - it 'returns 204 when deleting a group share' do - group = create(:group, :public) - create(:project_group_link, group: group, project: project) - - delete v3_api("/projects/#{project.id}/share/#{group.id}", user) - - expect(response).to have_gitlab_http_status(204) - expect(project.project_group_links).to be_empty - end - - it 'returns a 400 when group id is not an integer' do - delete v3_api("/projects/#{project.id}/share/foo", user) - - expect(response).to have_gitlab_http_status(400) - end - - it 'returns a 404 error when group link does not exist' do - delete v3_api("/projects/#{project.id}/share/1234", user) - - expect(response).to have_gitlab_http_status(404) - end - - it 'returns a 404 error when project does not exist' do - delete v3_api("/projects/123/share/1234", user) - - expect(response).to have_gitlab_http_status(404) - end - end - - describe 'GET /projects/search/:query' do - let!(:query) { 'query'} - let!(:search) { create(:project, name: query, creator_id: user.id, namespace: user.namespace) } - let!(:pre) { create(:project, name: "pre_#{query}", creator_id: user.id, namespace: user.namespace) } - let!(:post) { create(:project, name: "#{query}_post", creator_id: user.id, namespace: user.namespace) } - let!(:pre_post) { create(:project, name: "pre_#{query}_post", creator_id: user.id, namespace: user.namespace) } - let!(:unfound) { create(:project, name: 'unfound', creator_id: user.id, namespace: user.namespace) } - let!(:internal) { create(:project, :internal, name: "internal #{query}") } - let!(:unfound_internal) { create(:project, :internal, name: 'unfound internal') } - let!(:public) { create(:project, :public, name: "public #{query}") } - let!(:unfound_public) { create(:project, :public, name: 'unfound public') } - let!(:one_dot_two) { create(:project, :public, name: "one.dot.two") } - - shared_examples_for 'project search response' do |args = {}| - it 'returns project search responses' do - get v3_api("/projects/search/#{args[:query]}", current_user) - - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.size).to eq(args[:results]) - json_response.each { |project| expect(project['name']).to match(args[:match_regex] || /.*#{args[:query]}.*/) } - end - end - - context 'when unauthenticated' do - it_behaves_like 'project search response', query: 'query', results: 1 do - let(:current_user) { nil } - end - end - - context 'when authenticated' do - it_behaves_like 'project search response', query: 'query', results: 6 do - let(:current_user) { user } - end - it_behaves_like 'project search response', query: 'one.dot.two', results: 1 do - let(:current_user) { user } - end - end - - context 'when authenticated as a different user' do - it_behaves_like 'project search response', query: 'query', results: 2, match_regex: /(internal|public) query/ do - let(:current_user) { user2 } - end - end - end - - describe 'PUT /projects/:id' do - before { project } - before { user } - before { user3 } - before { user4 } - before { project3 } - before { project4 } - before { project_member2 } - before { project_member } - - context 'when unauthenticated' do - it 'returns authentication error' do - project_param = { name: 'bar' } - put v3_api("/projects/#{project.id}"), project_param - expect(response).to have_gitlab_http_status(401) - end - end - - context 'when authenticated as project owner' do - it 'updates name' do - project_param = { name: 'bar' } - put v3_api("/projects/#{project.id}", user), project_param - expect(response).to have_gitlab_http_status(200) - project_param.each_pair do |k, v| - expect(json_response[k.to_s]).to eq(v) - end - end - - it 'updates visibility_level' do - project_param = { visibility_level: 20 } - put v3_api("/projects/#{project3.id}", user), project_param - expect(response).to have_gitlab_http_status(200) - project_param.each_pair do |k, v| - expect(json_response[k.to_s]).to eq(v) - end - end - - it 'updates visibility_level from public to private' do - project3.update_attributes({ visibility_level: Gitlab::VisibilityLevel::PUBLIC }) - project_param = { public: false } - put v3_api("/projects/#{project3.id}", user), project_param - expect(response).to have_gitlab_http_status(200) - project_param.each_pair do |k, v| - expect(json_response[k.to_s]).to eq(v) - end - expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::PRIVATE) - end - - it 'does not update name to existing name' do - project_param = { name: project3.name } - put v3_api("/projects/#{project.id}", user), project_param - expect(response).to have_gitlab_http_status(400) - expect(json_response['message']['name']).to eq(['has already been taken']) - end - - it 'updates request_access_enabled' do - project_param = { request_access_enabled: false } - - put v3_api("/projects/#{project.id}", user), project_param - - expect(response).to have_gitlab_http_status(200) - expect(json_response['request_access_enabled']).to eq(false) - end - - it 'updates path & name to existing path & name in different namespace' do - project_param = { path: project4.path, name: project4.name } - put v3_api("/projects/#{project3.id}", user), project_param - expect(response).to have_gitlab_http_status(200) - project_param.each_pair do |k, v| - expect(json_response[k.to_s]).to eq(v) - end - end - end - - context 'when authenticated as project master' do - it 'updates path' do - project_param = { path: 'bar' } - put v3_api("/projects/#{project3.id}", user4), project_param - expect(response).to have_gitlab_http_status(200) - project_param.each_pair do |k, v| - expect(json_response[k.to_s]).to eq(v) - end - end - - it 'updates other attributes' do - project_param = { issues_enabled: true, - wiki_enabled: true, - snippets_enabled: true, - merge_requests_enabled: true, - description: 'new description' } - - put v3_api("/projects/#{project3.id}", user4), project_param - expect(response).to have_gitlab_http_status(200) - project_param.each_pair do |k, v| - expect(json_response[k.to_s]).to eq(v) - end - end - - it 'does not update path to existing path' do - project_param = { path: project.path } - put v3_api("/projects/#{project3.id}", user4), project_param - expect(response).to have_gitlab_http_status(400) - expect(json_response['message']['path']).to eq(['has already been taken']) - end - - it 'does not update name' do - project_param = { name: 'bar' } - put v3_api("/projects/#{project3.id}", user4), project_param - expect(response).to have_gitlab_http_status(403) - end - - it 'does not update visibility_level' do - project_param = { visibility_level: 20 } - put v3_api("/projects/#{project3.id}", user4), project_param - expect(response).to have_gitlab_http_status(403) - end - end - - context 'when authenticated as project developer' do - it 'does not update other attributes' do - project_param = { path: 'bar', - issues_enabled: true, - wiki_enabled: true, - snippets_enabled: true, - merge_requests_enabled: true, - description: 'new description', - request_access_enabled: true } - put v3_api("/projects/#{project.id}", user3), project_param - expect(response).to have_gitlab_http_status(403) - end - end - end - - describe 'POST /projects/:id/archive' do - context 'on an unarchived project' do - it 'archives the project' do - post v3_api("/projects/#{project.id}/archive", user) - - expect(response).to have_gitlab_http_status(201) - expect(json_response['archived']).to be_truthy - end - end - - context 'on an archived project' do - before do - project.archive! - end - - it 'remains archived' do - post v3_api("/projects/#{project.id}/archive", user) - - expect(response).to have_gitlab_http_status(201) - expect(json_response['archived']).to be_truthy - end - end - - context 'user without archiving rights to the project' do - before do - project.add_developer(user3) - end - - it 'rejects the action' do - post v3_api("/projects/#{project.id}/archive", user3) - - expect(response).to have_gitlab_http_status(403) - end - end - end - - describe 'POST /projects/:id/unarchive' do - context 'on an unarchived project' do - it 'remains unarchived' do - post v3_api("/projects/#{project.id}/unarchive", user) - - expect(response).to have_gitlab_http_status(201) - expect(json_response['archived']).to be_falsey - end - end - - context 'on an archived project' do - before do - project.archive! - end - - it 'unarchives the project' do - post v3_api("/projects/#{project.id}/unarchive", user) - - expect(response).to have_gitlab_http_status(201) - expect(json_response['archived']).to be_falsey - end - end - - context 'user without archiving rights to the project' do - before do - project.add_developer(user3) - end - - it 'rejects the action' do - post v3_api("/projects/#{project.id}/unarchive", user3) - - expect(response).to have_gitlab_http_status(403) - end - end - end - - describe 'POST /projects/:id/star' do - context 'on an unstarred project' do - it 'stars the project' do - expect { post v3_api("/projects/#{project.id}/star", user) }.to change { project.reload.star_count }.by(1) - - expect(response).to have_gitlab_http_status(201) - expect(json_response['star_count']).to eq(1) - end - end - - context 'on a starred project' do - before do - user.toggle_star(project) - project.reload - end - - it 'does not modify the star count' do - expect { post v3_api("/projects/#{project.id}/star", user) }.not_to change { project.reload.star_count } - - expect(response).to have_gitlab_http_status(304) - end - end - end - - describe 'DELETE /projects/:id/star' do - context 'on a starred project' do - before do - user.toggle_star(project) - project.reload - end - - it 'unstars the project' do - expect { delete v3_api("/projects/#{project.id}/star", user) }.to change { project.reload.star_count }.by(-1) - - expect(response).to have_gitlab_http_status(200) - expect(json_response['star_count']).to eq(0) - end - end - - context 'on an unstarred project' do - it 'does not modify the star count' do - expect { delete v3_api("/projects/#{project.id}/star", user) }.not_to change { project.reload.star_count } - - expect(response).to have_gitlab_http_status(304) - end - end - end - - describe 'DELETE /projects/:id' do - context 'when authenticated as user' do - it 'removes project' do - delete v3_api("/projects/#{project.id}", user) - expect(response).to have_gitlab_http_status(200) - end - - it 'does not remove a project if not an owner' do - user3 = create(:user) - project.add_developer(user3) - delete v3_api("/projects/#{project.id}", user3) - expect(response).to have_gitlab_http_status(403) - end - - it 'does not remove a non existing project' do - delete v3_api('/projects/1328', user) - expect(response).to have_gitlab_http_status(404) - end - - it 'does not remove a project not attached to user' do - delete v3_api("/projects/#{project.id}", user2) - expect(response).to have_gitlab_http_status(404) - end - end - - context 'when authenticated as admin' do - it 'removes any existing project' do - delete v3_api("/projects/#{project.id}", admin) - expect(response).to have_gitlab_http_status(200) - end - - it 'does not remove a non existing project' do - delete v3_api('/projects/1328', admin) - expect(response).to have_gitlab_http_status(404) - end - end - end -end diff --git a/spec/requests/api/v3/repositories_spec.rb b/spec/requests/api/v3/repositories_spec.rb deleted file mode 100644 index 0167eb2c4f6..00000000000 --- a/spec/requests/api/v3/repositories_spec.rb +++ /dev/null @@ -1,366 +0,0 @@ -require 'spec_helper' -require 'mime/types' - -describe API::V3::Repositories do - include RepoHelpers - include WorkhorseHelpers - - let(:user) { create(:user) } - let(:guest) { create(:user).tap { |u| create(:project_member, :guest, user: u, project: project) } } - let!(:project) { create(:project, :repository, creator: user) } - let!(:master) { create(:project_member, :master, user: user, project: project) } - - describe "GET /projects/:id/repository/tree" do - let(:route) { "/projects/#{project.id}/repository/tree" } - - shared_examples_for 'repository tree' do - it 'returns the repository tree' do - get v3_api(route, current_user) - - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - - first_commit = json_response.first - expect(first_commit['name']).to eq('bar') - expect(first_commit['type']).to eq('tree') - expect(first_commit['mode']).to eq('040000') - end - - context 'when ref does not exist' do - it_behaves_like '404 response' do - let(:request) { get v3_api("#{route}?ref_name=foo", current_user) } - let(:message) { '404 Tree Not Found' } - end - end - - context 'when repository is disabled' do - include_context 'disabled repository' - - it_behaves_like '403 response' do - let(:request) { get v3_api(route, current_user) } - end - end - - context 'with recursive=1' do - it 'returns recursive project paths tree' do - get v3_api("#{route}?recursive=1", current_user) - - expect(response.status).to eq(200) - expect(json_response).to be_an Array - expect(json_response[4]['name']).to eq('html') - expect(json_response[4]['path']).to eq('files/html') - expect(json_response[4]['type']).to eq('tree') - expect(json_response[4]['mode']).to eq('040000') - end - - context 'when repository is disabled' do - include_context 'disabled repository' - - it_behaves_like '403 response' do - let(:request) { get v3_api(route, current_user) } - end - end - - context 'when ref does not exist' do - it_behaves_like '404 response' do - let(:request) { get v3_api("#{route}?recursive=1&ref_name=foo", current_user) } - let(:message) { '404 Tree Not Found' } - end - end - end - end - - context 'when unauthenticated', 'and project is public' do - it_behaves_like 'repository tree' do - let(:project) { create(:project, :public, :repository) } - let(:current_user) { nil } - end - end - - context 'when unauthenticated', 'and project is private' do - it_behaves_like '404 response' do - let(:request) { get v3_api(route) } - let(:message) { '404 Project Not Found' } - end - end - - context 'when authenticated', 'as a developer' do - it_behaves_like 'repository tree' do - let(:current_user) { user } - end - end - - context 'when authenticated', 'as a guest' do - it_behaves_like '403 response' do - let(:request) { get v3_api(route, guest) } - end - end - end - - [ - ['blobs/:sha', 'blobs/master'], - ['blobs/:sha', 'blobs/v1.1.0'], - ['commits/:sha/blob', 'commits/master/blob'] - ].each do |desc_path, example_path| - describe "GET /projects/:id/repository/#{desc_path}" do - let(:route) { "/projects/#{project.id}/repository/#{example_path}?filepath=README.md" } - shared_examples_for 'repository blob' do - it 'returns the repository blob' do - get v3_api(route, current_user) - expect(response).to have_gitlab_http_status(200) - end - context 'when sha does not exist' do - it_behaves_like '404 response' do - let(:request) { get v3_api("/projects/#{project.id}/repository/#{desc_path.sub(':sha', 'invalid_branch_name')}?filepath=README.md", current_user) } - let(:message) { '404 Commit Not Found' } - end - end - context 'when filepath does not exist' do - it_behaves_like '404 response' do - let(:request) { get v3_api(route.sub('README.md', 'README.invalid'), current_user) } - let(:message) { '404 File Not Found' } - end - end - context 'when no filepath is given' do - it_behaves_like '400 response' do - let(:request) { get v3_api(route.sub('?filepath=README.md', ''), current_user) } - end - end - context 'when repository is disabled' do - include_context 'disabled repository' - it_behaves_like '403 response' do - let(:request) { get v3_api(route, current_user) } - end - end - end - context 'when unauthenticated', 'and project is public' do - it_behaves_like 'repository blob' do - let(:project) { create(:project, :public, :repository) } - let(:current_user) { nil } - end - end - context 'when unauthenticated', 'and project is private' do - it_behaves_like '404 response' do - let(:request) { get v3_api(route) } - let(:message) { '404 Project Not Found' } - end - end - context 'when authenticated', 'as a developer' do - it_behaves_like 'repository blob' do - let(:current_user) { user } - end - end - context 'when authenticated', 'as a guest' do - it_behaves_like '403 response' do - let(:request) { get v3_api(route, guest) } - end - end - end - end - describe "GET /projects/:id/repository/raw_blobs/:sha" do - let(:route) { "/projects/#{project.id}/repository/raw_blobs/#{sample_blob.oid}" } - shared_examples_for 'repository raw blob' do - it 'returns the repository raw blob' do - get v3_api(route, current_user) - expect(response).to have_gitlab_http_status(200) - end - context 'when sha does not exist' do - it_behaves_like '404 response' do - let(:request) { get v3_api(route.sub(sample_blob.oid, '123456'), current_user) } - let(:message) { '404 Blob Not Found' } - end - end - context 'when repository is disabled' do - include_context 'disabled repository' - it_behaves_like '403 response' do - let(:request) { get v3_api(route, current_user) } - end - end - end - context 'when unauthenticated', 'and project is public' do - it_behaves_like 'repository raw blob' do - let(:project) { create(:project, :public, :repository) } - let(:current_user) { nil } - end - end - context 'when unauthenticated', 'and project is private' do - it_behaves_like '404 response' do - let(:request) { get v3_api(route) } - let(:message) { '404 Project Not Found' } - end - end - context 'when authenticated', 'as a developer' do - it_behaves_like 'repository raw blob' do - let(:current_user) { user } - end - end - context 'when authenticated', 'as a guest' do - it_behaves_like '403 response' do - let(:request) { get v3_api(route, guest) } - end - end - end - describe "GET /projects/:id/repository/archive(.:format)?:sha" do - let(:route) { "/projects/#{project.id}/repository/archive" } - shared_examples_for 'repository archive' do - it 'returns the repository archive' do - get v3_api(route, current_user) - expect(response).to have_gitlab_http_status(200) - repo_name = project.repository.name.gsub("\.git", "") - type, params = workhorse_send_data - expect(type).to eq('git-archive') - expect(params['ArchivePath']).to match(/#{repo_name}\-[^\.]+\.tar.gz/) - end - it 'returns the repository archive archive.zip' do - get v3_api("/projects/#{project.id}/repository/archive.zip", user) - expect(response).to have_gitlab_http_status(200) - repo_name = project.repository.name.gsub("\.git", "") - type, params = workhorse_send_data - expect(type).to eq('git-archive') - expect(params['ArchivePath']).to match(/#{repo_name}\-[^\.]+\.zip/) - end - it 'returns the repository archive archive.tar.bz2' do - get v3_api("/projects/#{project.id}/repository/archive.tar.bz2", user) - expect(response).to have_gitlab_http_status(200) - repo_name = project.repository.name.gsub("\.git", "") - type, params = workhorse_send_data - expect(type).to eq('git-archive') - expect(params['ArchivePath']).to match(/#{repo_name}\-[^\.]+\.tar.bz2/) - end - context 'when sha does not exist' do - it_behaves_like '404 response' do - let(:request) { get v3_api("#{route}?sha=xxx", current_user) } - let(:message) { '404 File Not Found' } - end - end - end - context 'when unauthenticated', 'and project is public' do - it_behaves_like 'repository archive' do - let(:project) { create(:project, :public, :repository) } - let(:current_user) { nil } - end - end - context 'when unauthenticated', 'and project is private' do - it_behaves_like '404 response' do - let(:request) { get v3_api(route) } - let(:message) { '404 Project Not Found' } - end - end - context 'when authenticated', 'as a developer' do - it_behaves_like 'repository archive' do - let(:current_user) { user } - end - end - context 'when authenticated', 'as a guest' do - it_behaves_like '403 response' do - let(:request) { get v3_api(route, guest) } - end - end - end - - describe 'GET /projects/:id/repository/compare' do - let(:route) { "/projects/#{project.id}/repository/compare" } - shared_examples_for 'repository compare' do - it "compares branches" do - get v3_api(route, current_user), from: 'master', to: 'feature' - expect(response).to have_gitlab_http_status(200) - expect(json_response['commits']).to be_present - expect(json_response['diffs']).to be_present - end - it "compares tags" do - get v3_api(route, current_user), from: 'v1.0.0', to: 'v1.1.0' - expect(response).to have_gitlab_http_status(200) - expect(json_response['commits']).to be_present - expect(json_response['diffs']).to be_present - end - it "compares commits" do - get v3_api(route, current_user), from: sample_commit.id, to: sample_commit.parent_id - expect(response).to have_gitlab_http_status(200) - expect(json_response['commits']).to be_empty - expect(json_response['diffs']).to be_empty - expect(json_response['compare_same_ref']).to be_falsey - end - it "compares commits in reverse order" do - get v3_api(route, current_user), from: sample_commit.parent_id, to: sample_commit.id - expect(response).to have_gitlab_http_status(200) - expect(json_response['commits']).to be_present - expect(json_response['diffs']).to be_present - end - it "compares same refs" do - get v3_api(route, current_user), from: 'master', to: 'master' - expect(response).to have_gitlab_http_status(200) - expect(json_response['commits']).to be_empty - expect(json_response['diffs']).to be_empty - expect(json_response['compare_same_ref']).to be_truthy - end - end - context 'when unauthenticated', 'and project is public' do - it_behaves_like 'repository compare' do - let(:project) { create(:project, :public, :repository) } - let(:current_user) { nil } - end - end - context 'when unauthenticated', 'and project is private' do - it_behaves_like '404 response' do - let(:request) { get v3_api(route) } - let(:message) { '404 Project Not Found' } - end - end - context 'when authenticated', 'as a developer' do - it_behaves_like 'repository compare' do - let(:current_user) { user } - end - end - context 'when authenticated', 'as a guest' do - it_behaves_like '403 response' do - let(:request) { get v3_api(route, guest) } - end - end - end - - describe 'GET /projects/:id/repository/contributors' do - let(:route) { "/projects/#{project.id}/repository/contributors" } - - shared_examples_for 'repository contributors' do - it 'returns valid data' do - get v3_api(route, current_user) - - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - - first_contributor = json_response.first - expect(first_contributor['email']).to eq('tiagonbotelho@hotmail.com') - expect(first_contributor['name']).to eq('tiagonbotelho') - expect(first_contributor['commits']).to eq(1) - expect(first_contributor['additions']).to eq(0) - expect(first_contributor['deletions']).to eq(0) - end - end - - context 'when unauthenticated', 'and project is public' do - it_behaves_like 'repository contributors' do - let(:project) { create(:project, :public, :repository) } - let(:current_user) { nil } - end - end - - context 'when unauthenticated', 'and project is private' do - it_behaves_like '404 response' do - let(:request) { get v3_api(route) } - let(:message) { '404 Project Not Found' } - end - end - - context 'when authenticated', 'as a developer' do - it_behaves_like 'repository contributors' do - let(:current_user) { user } - end - end - - context 'when authenticated', 'as a guest' do - it_behaves_like '403 response' do - let(:request) { get v3_api(route, guest) } - end - end - end -end diff --git a/spec/requests/api/v3/runners_spec.rb b/spec/requests/api/v3/runners_spec.rb deleted file mode 100644 index c91b097a3c7..00000000000 --- a/spec/requests/api/v3/runners_spec.rb +++ /dev/null @@ -1,152 +0,0 @@ -require 'spec_helper' - -describe API::V3::Runners do - 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, :reporter, user: user2, project: project) - end - - describe 'DELETE /runners/:id' do - context 'admin user' do - context 'when runner is shared' do - it 'deletes runner' do - expect do - delete v3_api("/runners/#{shared_runner.id}", admin) - - expect(response).to have_gitlab_http_status(200) - end.to change { Ci::Runner.shared.count }.by(-1) - end - end - - context 'when runner is not shared' do - it 'deletes unused runner' do - expect do - delete v3_api("/runners/#{unused_specific_runner.id}", admin) - - expect(response).to have_gitlab_http_status(200) - end.to change { Ci::Runner.specific.count }.by(-1) - end - - it 'deletes used runner' do - expect do - delete v3_api("/runners/#{specific_runner.id}", admin) - - expect(response).to have_gitlab_http_status(200) - end.to change { Ci::Runner.specific.count }.by(-1) - end - end - - it 'returns 404 if runner does not exists' do - delete v3_api('/runners/9999', admin) - - expect(response).to have_gitlab_http_status(404) - end - end - - context 'authorized user' do - context 'when runner is shared' do - it 'does not delete runner' do - delete v3_api("/runners/#{shared_runner.id}", user) - expect(response).to have_gitlab_http_status(403) - end - end - - context 'when runner is not shared' do - it 'does not delete runner without access to it' do - delete v3_api("/runners/#{specific_runner.id}", user2) - expect(response).to have_gitlab_http_status(403) - end - - it 'does not delete runner with more than one associated project' do - delete v3_api("/runners/#{two_projects_runner.id}", user) - expect(response).to have_gitlab_http_status(403) - end - - it 'deletes runner for one owned project' do - expect do - delete v3_api("/runners/#{specific_runner.id}", user) - - expect(response).to have_gitlab_http_status(200) - end.to change { Ci::Runner.specific.count }.by(-1) - end - end - end - - context 'unauthorized user' do - it 'does not delete runner' do - delete v3_api("/runners/#{specific_runner.id}") - - expect(response).to have_gitlab_http_status(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 "disables project's runner" do - expect do - delete v3_api("/projects/#{project.id}/runners/#{two_projects_runner.id}", user) - - expect(response).to have_gitlab_http_status(200) - end.to change { project.runners.count }.by(-1) - end - end - - context 'when runner have one associated projects' do - it "does not disable project's runner" do - expect do - delete v3_api("/projects/#{project.id}/runners/#{specific_runner.id}", user) - end.to change { project.runners.count }.by(0) - expect(response).to have_gitlab_http_status(403) - end - end - - it 'returns 404 is runner is not found' do - delete v3_api("/projects/#{project.id}/runners/9999", user) - - expect(response).to have_gitlab_http_status(404) - end - end - - context 'authorized user without permissions' do - it "does not disable project's runner" do - delete v3_api("/projects/#{project.id}/runners/#{specific_runner.id}", user2) - - expect(response).to have_gitlab_http_status(403) - end - end - - context 'unauthorized user' do - it "does not disable project's runner" do - delete v3_api("/projects/#{project.id}/runners/#{specific_runner.id}") - - expect(response).to have_gitlab_http_status(401) - end - end - end -end diff --git a/spec/requests/api/v3/services_spec.rb b/spec/requests/api/v3/services_spec.rb deleted file mode 100644 index c69a7d58ca6..00000000000 --- a/spec/requests/api/v3/services_spec.rb +++ /dev/null @@ -1,26 +0,0 @@ -require "spec_helper" - -describe API::V3::Services do - let(:user) { create(:user) } - let(:project) { create(:project, creator_id: user.id, namespace: user.namespace) } - - available_services = Service.available_services_names - available_services.delete('prometheus') - available_services.each do |service| - describe "DELETE /projects/:id/services/#{service.dasherize}" do - include_context service - - before do - initialize_service(service) - end - - it "deletes #{service}" do - delete v3_api("/projects/#{project.id}/services/#{dashed_service}", user) - - expect(response).to have_gitlab_http_status(200) - project.send(service_method).reload - expect(project.send(service_method).activated?).to be_falsey - end - end - end -end diff --git a/spec/requests/api/v3/settings_spec.rb b/spec/requests/api/v3/settings_spec.rb deleted file mode 100644 index 985bfbfa09c..00000000000 --- a/spec/requests/api/v3/settings_spec.rb +++ /dev/null @@ -1,63 +0,0 @@ -require 'spec_helper' - -describe API::V3::Settings, 'Settings' do - let(:user) { create(:user) } - let(:admin) { create(:admin) } - - describe "GET /application/settings" do - it "returns application settings" do - get v3_api("/application/settings", admin) - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Hash - expect(json_response['default_projects_limit']).to eq(42) - expect(json_response['password_authentication_enabled']).to be_truthy - expect(json_response['repository_storage']).to eq('default') - expect(json_response['koding_enabled']).to be_falsey - expect(json_response['koding_url']).to be_nil - expect(json_response['plantuml_enabled']).to be_falsey - expect(json_response['plantuml_url']).to be_nil - end - end - - describe "PUT /application/settings" do - context "custom repository storage type set in the config" do - before do - storages = { 'custom' => 'tmp/tests/custom_repositories' } - allow(Gitlab.config.repositories).to receive(:storages).and_return(storages) - end - - it "updates application settings" do - put v3_api("/application/settings", admin), - default_projects_limit: 3, password_authentication_enabled_for_web: false, repository_storage: 'custom', koding_enabled: true, koding_url: 'http://koding.example.com', - plantuml_enabled: true, plantuml_url: 'http://plantuml.example.com' - expect(response).to have_gitlab_http_status(200) - expect(json_response['default_projects_limit']).to eq(3) - expect(json_response['password_authentication_enabled_for_web']).to be_falsey - expect(json_response['repository_storage']).to eq('custom') - expect(json_response['repository_storages']).to eq(['custom']) - expect(json_response['koding_enabled']).to be_truthy - expect(json_response['koding_url']).to eq('http://koding.example.com') - expect(json_response['plantuml_enabled']).to be_truthy - expect(json_response['plantuml_url']).to eq('http://plantuml.example.com') - end - end - - context "missing koding_url value when koding_enabled is true" do - it "returns a blank parameter error message" do - put v3_api("/application/settings", admin), koding_enabled: true - - expect(response).to have_gitlab_http_status(400) - expect(json_response['error']).to eq('koding_url is missing') - end - end - - context "missing plantuml_url value when plantuml_enabled is true" do - it "returns a blank parameter error message" do - put v3_api("/application/settings", admin), plantuml_enabled: true - - expect(response).to have_gitlab_http_status(400) - expect(json_response['error']).to eq('plantuml_url is missing') - end - end - end -end diff --git a/spec/requests/api/v3/snippets_spec.rb b/spec/requests/api/v3/snippets_spec.rb deleted file mode 100644 index e8913039194..00000000000 --- a/spec/requests/api/v3/snippets_spec.rb +++ /dev/null @@ -1,186 +0,0 @@ -require 'rails_helper' - -describe API::V3::Snippets do - let!(:user) { create(:user) } - - describe 'GET /snippets/' do - it 'returns snippets available' do - public_snippet = create(:personal_snippet, :public, author: user) - private_snippet = create(:personal_snippet, :private, author: user) - internal_snippet = create(:personal_snippet, :internal, author: user) - - get v3_api("/snippets/", user) - - expect(response).to have_gitlab_http_status(200) - expect(json_response.map { |snippet| snippet['id']} ).to contain_exactly( - public_snippet.id, - internal_snippet.id, - private_snippet.id) - expect(json_response.last).to have_key('web_url') - expect(json_response.last).to have_key('raw_url') - end - - it 'hides private snippets from regular user' do - create(:personal_snippet, :private) - - get v3_api("/snippets/", user) - expect(response).to have_gitlab_http_status(200) - expect(json_response.size).to eq(0) - end - end - - describe 'GET /snippets/public' do - let!(:other_user) { create(:user) } - let!(:public_snippet) { create(:personal_snippet, :public, author: user) } - let!(:private_snippet) { create(:personal_snippet, :private, author: user) } - let!(:internal_snippet) { create(:personal_snippet, :internal, author: user) } - let!(:public_snippet_other) { create(:personal_snippet, :public, author: other_user) } - let!(:private_snippet_other) { create(:personal_snippet, :private, author: other_user) } - let!(:internal_snippet_other) { create(:personal_snippet, :internal, author: other_user) } - - it 'returns all snippets with public visibility from all users' do - get v3_api("/snippets/public", user) - - expect(response).to have_gitlab_http_status(200) - expect(json_response.map { |snippet| snippet['id']} ).to contain_exactly( - public_snippet.id, - public_snippet_other.id) - expect(json_response.map { |snippet| snippet['web_url']} ).to include( - "http://localhost/snippets/#{public_snippet.id}", - "http://localhost/snippets/#{public_snippet_other.id}") - expect(json_response.map { |snippet| snippet['raw_url']} ).to include( - "http://localhost/snippets/#{public_snippet.id}/raw", - "http://localhost/snippets/#{public_snippet_other.id}/raw") - end - end - - describe 'GET /snippets/:id/raw' do - let(:snippet) { create(:personal_snippet, author: user) } - - it 'returns raw text' do - get v3_api("/snippets/#{snippet.id}/raw", user) - - expect(response).to have_gitlab_http_status(200) - expect(response.content_type).to eq 'text/plain' - expect(response.body).to eq(snippet.content) - end - - it 'returns 404 for invalid snippet id' do - delete v3_api("/snippets/1234", user) - - expect(response).to have_gitlab_http_status(404) - expect(json_response['message']).to eq('404 Snippet Not Found') - end - end - - describe 'POST /snippets/' do - let(:params) do - { - title: 'Test Title', - file_name: 'test.rb', - content: 'puts "hello world"', - visibility_level: Snippet::PUBLIC - } - end - - it 'creates a new snippet' do - expect do - post v3_api("/snippets/", user), params - end.to change { PersonalSnippet.count }.by(1) - - expect(response).to have_gitlab_http_status(201) - expect(json_response['title']).to eq(params[:title]) - expect(json_response['file_name']).to eq(params[:file_name]) - end - - it 'returns 400 for missing parameters' do - params.delete(:title) - - post v3_api("/snippets/", user), params - - expect(response).to have_gitlab_http_status(400) - end - - context 'when the snippet is spam' do - def create_snippet(snippet_params = {}) - post v3_api('/snippets', user), params.merge(snippet_params) - end - - before do - allow_any_instance_of(AkismetService).to receive(:spam?).and_return(true) - end - - context 'when the snippet is private' do - it 'creates the snippet' do - expect { create_snippet(visibility_level: Snippet::PRIVATE) } - .to change { Snippet.count }.by(1) - end - end - - context 'when the snippet is public' do - it 'rejects the shippet' do - expect { create_snippet(visibility_level: Snippet::PUBLIC) } - .not_to change { Snippet.count } - expect(response).to have_gitlab_http_status(400) - end - - it 'creates a spam log' do - expect { create_snippet(visibility_level: Snippet::PUBLIC) } - .to change { SpamLog.count }.by(1) - end - end - end - end - - describe 'PUT /snippets/:id' do - let(:other_user) { create(:user) } - let(:public_snippet) { create(:personal_snippet, :public, author: user) } - it 'updates snippet' do - new_content = 'New content' - - put v3_api("/snippets/#{public_snippet.id}", user), content: new_content - - expect(response).to have_gitlab_http_status(200) - public_snippet.reload - expect(public_snippet.content).to eq(new_content) - end - - it 'returns 404 for invalid snippet id' do - put v3_api("/snippets/1234", user), title: 'foo' - - expect(response).to have_gitlab_http_status(404) - expect(json_response['message']).to eq('404 Snippet Not Found') - end - - it "returns 404 for another user's snippet" do - put v3_api("/snippets/#{public_snippet.id}", other_user), title: 'fubar' - - expect(response).to have_gitlab_http_status(404) - expect(json_response['message']).to eq('404 Snippet Not Found') - end - - it 'returns 400 for missing parameters' do - put v3_api("/snippets/1234", user) - - expect(response).to have_gitlab_http_status(400) - end - end - - describe 'DELETE /snippets/:id' do - let!(:public_snippet) { create(:personal_snippet, :public, author: user) } - it 'deletes snippet' do - expect do - delete v3_api("/snippets/#{public_snippet.id}", user) - - expect(response).to have_gitlab_http_status(204) - end.to change { PersonalSnippet.count }.by(-1) - end - - it 'returns 404 for invalid snippet id' do - delete v3_api("/snippets/1234", user) - - expect(response).to have_gitlab_http_status(404) - expect(json_response['message']).to eq('404 Snippet Not Found') - end - end -end diff --git a/spec/requests/api/v3/system_hooks_spec.rb b/spec/requests/api/v3/system_hooks_spec.rb deleted file mode 100644 index 30711c60faa..00000000000 --- a/spec/requests/api/v3/system_hooks_spec.rb +++ /dev/null @@ -1,56 +0,0 @@ -require 'spec_helper' - -describe API::V3::SystemHooks do - let(:user) { create(:user) } - let(:admin) { create(:admin) } - let!(:hook) { create(:system_hook, url: "http://example.com") } - - before { stub_request(:post, hook.url) } - - describe "GET /hooks" do - context "when no user" do - it "returns authentication error" do - get v3_api("/hooks") - - expect(response).to have_gitlab_http_status(401) - end - end - - context "when not an admin" do - it "returns forbidden error" do - get v3_api("/hooks", user) - - expect(response).to have_gitlab_http_status(403) - end - end - - context "when authenticated as admin" do - it "returns an array of hooks" do - get v3_api("/hooks", admin) - - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.first['url']).to eq(hook.url) - expect(json_response.first['push_events']).to be false - expect(json_response.first['tag_push_events']).to be false - expect(json_response.first['repository_update_events']).to be true - end - end - end - - describe "DELETE /hooks/:id" do - it "deletes a hook" do - expect do - delete v3_api("/hooks/#{hook.id}", admin) - - expect(response).to have_gitlab_http_status(200) - end.to change { SystemHook.count }.by(-1) - end - - it 'returns 404 if the system hook does not exist' do - delete v3_api('/hooks/12345', admin) - - expect(response).to have_gitlab_http_status(404) - end - end -end diff --git a/spec/requests/api/v3/tags_spec.rb b/spec/requests/api/v3/tags_spec.rb deleted file mode 100644 index e6ad005fa87..00000000000 --- a/spec/requests/api/v3/tags_spec.rb +++ /dev/null @@ -1,88 +0,0 @@ -require 'spec_helper' -require 'mime/types' - -describe API::V3::Tags do - include RepoHelpers - - let(:user) { create(:user) } - let(:user2) { create(:user) } - let!(:project) { create(:project, :repository, creator: user) } - let!(:master) { create(:project_member, :master, user: user, project: project) } - - describe "GET /projects/:id/repository/tags" do - let(:tag_name) { project.repository.tag_names.sort.reverse.first } - let(:description) { 'Awesome release!' } - - shared_examples_for 'repository tags' do - it 'returns the repository tags' do - get v3_api("/projects/#{project.id}/repository/tags", current_user) - - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.first['name']).to eq(tag_name) - end - end - - context 'when unauthenticated' do - it_behaves_like 'repository tags' do - let(:project) { create(:project, :public, :repository) } - let(:current_user) { nil } - end - end - - context 'when authenticated' do - it_behaves_like 'repository tags' do - let(:current_user) { user } - end - end - - context 'without releases' do - it "returns an array of project tags" do - get v3_api("/projects/#{project.id}/repository/tags", user) - - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.first['name']).to eq(tag_name) - end - end - - context 'with releases' do - before do - release = project.releases.find_or_initialize_by(tag: tag_name) - release.update_attributes(description: description) - end - - it "returns an array of project tags with release info" do - get v3_api("/projects/#{project.id}/repository/tags", user) - - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.first['name']).to eq(tag_name) - expect(json_response.first['message']).to eq('Version 1.1.0') - expect(json_response.first['release']['description']).to eq(description) - end - 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 'deletes an existing tag' do - delete v3_api("/projects/#{project.id}/repository/tags/#{tag_name}", user) - - expect(response).to have_gitlab_http_status(200) - expect(json_response['tag_name']).to eq(tag_name) - end - - it 'raises 404 if the tag does not exist' do - delete v3_api("/projects/#{project.id}/repository/tags/foobar", user) - expect(response).to have_gitlab_http_status(404) - end - end - end -end diff --git a/spec/requests/api/v3/templates_spec.rb b/spec/requests/api/v3/templates_spec.rb deleted file mode 100644 index 1a637f3cf96..00000000000 --- a/spec/requests/api/v3/templates_spec.rb +++ /dev/null @@ -1,201 +0,0 @@ -require 'spec_helper' - -describe API::V3::Templates do - shared_examples_for 'the Template Entity' do |path| - before { get v3_api(path) } - - it { expect(json_response['name']).to eq('Ruby') } - it { expect(json_response['content']).to include('*.gem') } - end - - shared_examples_for 'the TemplateList Entity' do |path| - before { get v3_api(path) } - - it { expect(json_response.first['name']).not_to be_nil } - it { expect(json_response.first['content']).to be_nil } - end - - shared_examples_for 'requesting gitignores' do |path| - it 'returns a list of available gitignore templates' do - get v3_api(path) - - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.size).to be > 15 - end - end - - shared_examples_for 'requesting gitlab-ci-ymls' do |path| - it 'returns a list of available gitlab_ci_ymls' do - get v3_api(path) - - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.first['name']).not_to be_nil - end - end - - shared_examples_for 'requesting gitlab-ci-yml for Ruby' do |path| - it 'adds a disclaimer on the top' do - get v3_api(path) - - expect(response).to have_gitlab_http_status(200) - expect(json_response['content']).to start_with("# This file is a template,") - end - end - - shared_examples_for 'the License Template Entity' do |path| - before { get v3_api(path) } - - it 'returns a license template' do - expect(json_response['key']).to eq('mit') - expect(json_response['name']).to eq('MIT License') - expect(json_response['nickname']).to be_nil - expect(json_response['popular']).to be true - expect(json_response['html_url']).to eq('http://choosealicense.com/licenses/mit/') - expect(json_response['source_url']).to eq('https://opensource.org/licenses/MIT') - expect(json_response['description']).to include('A short and simple permissive license with conditions') - expect(json_response['conditions']).to eq(%w[include-copyright]) - expect(json_response['permissions']).to eq(%w[commercial-use modifications distribution private-use]) - expect(json_response['limitations']).to eq(%w[liability warranty]) - expect(json_response['content']).to include('MIT License') - end - end - - shared_examples_for 'GET licenses' do |path| - it 'returns a list of available license templates' do - get v3_api(path) - - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.size).to eq(12) - expect(json_response.map { |l| l['key'] }).to include('agpl-3.0') - end - - describe 'the popular parameter' do - context 'with popular=1' do - it 'returns a list of available popular license templates' do - get v3_api("#{path}?popular=1") - - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.size).to eq(3) - expect(json_response.map { |l| l['key'] }).to include('apache-2.0') - end - end - end - end - - shared_examples_for 'GET licenses/:name' do |path| - context 'with :project and :fullname given' do - before do - get v3_api("#{path}/#{license_type}?project=My+Awesome+Project&fullname=Anton+#{license_type.upcase}") - end - - context 'for the mit license' do - let(:license_type) { 'mit' } - - it 'returns the license text' do - expect(json_response['content']).to include('MIT License') - end - - it 'replaces placeholder values' do - expect(json_response['content']).to include("Copyright (c) #{Time.now.year} Anton") - end - end - - context 'for the agpl-3.0 license' do - let(:license_type) { 'agpl-3.0' } - - it 'returns the license text' do - expect(json_response['content']).to include('GNU AFFERO GENERAL PUBLIC LICENSE') - end - - it 'replaces placeholder values' do - expect(json_response['content']).to include('My Awesome Project') - expect(json_response['content']).to include("Copyright (C) #{Time.now.year} Anton") - end - end - - context 'for the gpl-3.0 license' do - let(:license_type) { 'gpl-3.0' } - - it 'returns the license text' do - expect(json_response['content']).to include('GNU GENERAL PUBLIC LICENSE') - end - - it 'replaces placeholder values' do - expect(json_response['content']).to include('My Awesome Project') - expect(json_response['content']).to include("Copyright (C) #{Time.now.year} Anton") - end - end - - context 'for the gpl-2.0 license' do - let(:license_type) { 'gpl-2.0' } - - it 'returns the license text' do - expect(json_response['content']).to include('GNU GENERAL PUBLIC LICENSE') - end - - it 'replaces placeholder values' do - expect(json_response['content']).to include('My Awesome Project') - expect(json_response['content']).to include("Copyright (C) #{Time.now.year} Anton") - end - end - - context 'for the apache-2.0 license' do - let(:license_type) { 'apache-2.0' } - - it 'returns the license text' do - expect(json_response['content']).to include('Apache License') - end - - it 'replaces placeholder values' do - expect(json_response['content']).to include("Copyright #{Time.now.year} Anton") - end - end - - context 'for an uknown license' do - let(:license_type) { 'muth-over9000' } - - it 'returns a 404' do - expect(response).to have_gitlab_http_status(404) - end - end - end - - context 'with no :fullname given' do - context 'with an authenticated user' do - let(:user) { create(:user) } - - it 'replaces the copyright owner placeholder with the name of the current user' do - get v3_api('/templates/licenses/mit', user) - - expect(json_response['content']).to include("Copyright (c) #{Time.now.year} #{user.name}") - end - end - end - end - - describe 'with /templates namespace' do - it_behaves_like 'the Template Entity', '/templates/gitignores/Ruby' - it_behaves_like 'the TemplateList Entity', '/templates/gitignores' - it_behaves_like 'requesting gitignores', '/templates/gitignores' - it_behaves_like 'requesting gitlab-ci-ymls', '/templates/gitlab_ci_ymls' - it_behaves_like 'requesting gitlab-ci-yml for Ruby', '/templates/gitlab_ci_ymls/Ruby' - it_behaves_like 'the License Template Entity', '/templates/licenses/mit' - it_behaves_like 'GET licenses', '/templates/licenses' - it_behaves_like 'GET licenses/:name', '/templates/licenses' - end - - describe 'without /templates namespace' do - it_behaves_like 'the Template Entity', '/gitignores/Ruby' - it_behaves_like 'the TemplateList Entity', '/gitignores' - it_behaves_like 'requesting gitignores', '/gitignores' - it_behaves_like 'requesting gitlab-ci-ymls', '/gitlab_ci_ymls' - it_behaves_like 'requesting gitlab-ci-yml for Ruby', '/gitlab_ci_ymls/Ruby' - it_behaves_like 'the License Template Entity', '/licenses/mit' - it_behaves_like 'GET licenses', '/licenses' - it_behaves_like 'GET licenses/:name', '/licenses' - end -end diff --git a/spec/requests/api/v3/todos_spec.rb b/spec/requests/api/v3/todos_spec.rb deleted file mode 100644 index ea648e3917f..00000000000 --- a/spec/requests/api/v3/todos_spec.rb +++ /dev/null @@ -1,77 +0,0 @@ -require 'spec_helper' - -describe API::V3::Todos do - let(:project_1) { create(:project) } - let(:project_2) { create(:project) } - let(:author_1) { create(:user) } - let(:author_2) { create(:user) } - let(:john_doe) { create(:user, username: 'john_doe') } - let!(:pending_1) { create(:todo, :mentioned, project: project_1, author: author_1, user: john_doe) } - let!(:pending_2) { create(:todo, project: project_2, author: author_2, user: john_doe) } - let!(:pending_3) { create(:todo, project: project_1, author: author_2, user: john_doe) } - let!(:done) { create(:todo, :done, project: project_1, author: author_1, user: john_doe) } - - before do - project_1.add_developer(john_doe) - project_2.add_developer(john_doe) - end - - describe 'DELETE /todos/:id' do - context 'when unauthenticated' do - it 'returns authentication error' do - delete v3_api("/todos/#{pending_1.id}") - - expect(response.status).to eq(401) - end - end - - context 'when authenticated' do - it 'marks a todo as done' do - delete v3_api("/todos/#{pending_1.id}", john_doe) - - expect(response.status).to eq(200) - expect(pending_1.reload).to be_done - end - - it 'updates todos cache' do - expect_any_instance_of(User).to receive(:update_todos_count_cache).and_call_original - - delete v3_api("/todos/#{pending_1.id}", john_doe) - end - - it 'returns 404 if the todo does not belong to the current user' do - delete v3_api("/todos/#{pending_1.id}", author_1) - - expect(response.status).to eq(404) - end - end - end - - describe 'DELETE /todos' do - context 'when unauthenticated' do - it 'returns authentication error' do - delete v3_api('/todos') - - expect(response.status).to eq(401) - end - end - - context 'when authenticated' do - it 'marks all todos as done' do - delete v3_api('/todos', john_doe) - - expect(response.status).to eq(200) - expect(response.body).to eq('3') - expect(pending_1.reload).to be_done - expect(pending_2.reload).to be_done - expect(pending_3.reload).to be_done - end - - it 'updates todos cache' do - expect_any_instance_of(User).to receive(:update_todos_count_cache).and_call_original - - delete v3_api("/todos", john_doe) - end - end - end -end diff --git a/spec/requests/api/v3/triggers_spec.rb b/spec/requests/api/v3/triggers_spec.rb deleted file mode 100644 index e8e2f49d7a0..00000000000 --- a/spec/requests/api/v3/triggers_spec.rb +++ /dev/null @@ -1,235 +0,0 @@ -require 'spec_helper' - -describe API::V3::Triggers do - let(:user) { create(:user) } - let(:user2) { create(:user) } - let!(:trigger_token) { 'secure_token' } - let!(:project) { create(:project, :repository, creator: user) } - let!(:master) { create(:project_member, :master, user: user, project: project) } - let!(:developer) { create(:project_member, :developer, user: user2, project: project) } - - let!(:trigger) do - create(:ci_trigger, project: project, token: trigger_token, owner: user) - end - - describe 'POST /projects/:project_id/trigger' do - let!(:project2) { create(:project) } - let(:options) do - { - token: trigger_token - } - end - - before do - stub_ci_pipeline_to_return_yaml_file - end - - context 'Handles errors' do - it 'returns bad request if token is missing' do - post v3_api("/projects/#{project.id}/trigger/builds"), ref: 'master' - expect(response).to have_gitlab_http_status(400) - end - - it 'returns not found if project is not found' do - post v3_api('/projects/0/trigger/builds'), options.merge(ref: 'master') - expect(response).to have_gitlab_http_status(404) - end - - it 'returns unauthorized if token is for different project' do - post v3_api("/projects/#{project2.id}/trigger/builds"), options.merge(ref: 'master') - expect(response).to have_gitlab_http_status(404) - end - end - - context 'Have a commit' do - let(:pipeline) { project.pipelines.last } - - it 'creates builds' do - post v3_api("/projects/#{project.id}/trigger/builds"), options.merge(ref: 'master') - expect(response).to have_gitlab_http_status(201) - pipeline.builds.reload - expect(pipeline.builds.pending.size).to eq(2) - expect(pipeline.builds.size).to eq(5) - end - - it 'returns bad request with no builds created if there\'s no commit for that ref' do - post v3_api("/projects/#{project.id}/trigger/builds"), options.merge(ref: 'other-branch') - expect(response).to have_gitlab_http_status(400) - expect(json_response['message']['base']) - .to contain_exactly('Reference not found') - end - - context 'Validates variables' do - let(:variables) do - { 'TRIGGER_KEY' => 'TRIGGER_VALUE' } - end - - it 'validates variables to be a hash' do - post v3_api("/projects/#{project.id}/trigger/builds"), options.merge(variables: 'value', ref: 'master') - expect(response).to have_gitlab_http_status(400) - expect(json_response['error']).to eq('variables is invalid') - end - - it 'validates variables needs to be a map of key-valued strings' do - post v3_api("/projects/#{project.id}/trigger/builds"), options.merge(variables: { key: %w(1 2) }, ref: 'master') - expect(response).to have_gitlab_http_status(400) - expect(json_response['message']).to eq('variables needs to be a map of key-valued strings') - end - - it 'creates trigger request with variables' do - post v3_api("/projects/#{project.id}/trigger/builds"), options.merge(variables: variables, ref: 'master') - expect(response).to have_gitlab_http_status(201) - pipeline.builds.reload - expect(pipeline.variables.map { |v| { v.key => v.value } }.first).to eq(variables) - expect(json_response['variables']).to eq(variables) - end - end - end - - context 'when triggering a pipeline from a trigger token' do - it 'creates builds from the ref given in the URL, not in the body' do - expect do - post v3_api("/projects/#{project.id}/ref/master/trigger/builds?token=#{trigger_token}"), { ref: 'refs/heads/other-branch' } - end.to change(project.builds, :count).by(5) - expect(response).to have_gitlab_http_status(201) - end - - context 'when ref contains a dot' do - it 'creates builds from the ref given in the URL, not in the body' do - project.repository.create_file(user, '.gitlab/gitlabhq/new_feature.md', 'something valid', message: 'new_feature', branch_name: 'v.1-branch') - - expect do - post v3_api("/projects/#{project.id}/ref/v.1-branch/trigger/builds?token=#{trigger_token}"), { ref: 'refs/heads/other-branch' } - end.to change(project.builds, :count).by(4) - - expect(response).to have_gitlab_http_status(201) - end - end - end - end - - describe 'GET /projects/:id/triggers' do - context 'authenticated user with valid permissions' do - it 'returns list of triggers' do - get v3_api("/projects/#{project.id}/triggers", user) - - expect(response).to have_gitlab_http_status(200) - expect(response).to include_pagination_headers - 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 'does not return triggers list' do - get v3_api("/projects/#{project.id}/triggers", user2) - - expect(response).to have_gitlab_http_status(403) - end - end - - context 'unauthenticated user' do - it 'does not return triggers list' do - get v3_api("/projects/#{project.id}/triggers") - - expect(response).to have_gitlab_http_status(401) - end - end - end - - describe 'GET /projects/:id/triggers/:token' do - context 'authenticated user with valid permissions' do - it 'returns trigger details' do - get v3_api("/projects/#{project.id}/triggers/#{trigger.token}", user) - - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_a(Hash) - end - - it 'responds with 404 Not Found if requesting non-existing trigger' do - get v3_api("/projects/#{project.id}/triggers/abcdef012345", user) - - expect(response).to have_gitlab_http_status(404) - end - end - - context 'authenticated user with invalid permissions' do - it 'does not return triggers list' do - get v3_api("/projects/#{project.id}/triggers/#{trigger.token}", user2) - - expect(response).to have_gitlab_http_status(403) - end - end - - context 'unauthenticated user' do - it 'does not return triggers list' do - get v3_api("/projects/#{project.id}/triggers/#{trigger.token}") - - expect(response).to have_gitlab_http_status(401) - end - end - end - - describe 'POST /projects/:id/triggers' do - context 'authenticated user with valid permissions' do - it 'creates trigger' do - expect do - post v3_api("/projects/#{project.id}/triggers", user) - end.to change {project.triggers.count}.by(1) - - expect(response).to have_gitlab_http_status(201) - expect(json_response).to be_a(Hash) - end - end - - context 'authenticated user with invalid permissions' do - it 'does not create trigger' do - post v3_api("/projects/#{project.id}/triggers", user2) - - expect(response).to have_gitlab_http_status(403) - end - end - - context 'unauthenticated user' do - it 'does not create trigger' do - post v3_api("/projects/#{project.id}/triggers") - - expect(response).to have_gitlab_http_status(401) - end - end - end - - describe 'DELETE /projects/:id/triggers/:token' do - context 'authenticated user with valid permissions' do - it 'deletes trigger' do - expect do - delete v3_api("/projects/#{project.id}/triggers/#{trigger.token}", user) - - expect(response).to have_gitlab_http_status(200) - end.to change {project.triggers.count}.by(-1) - end - - it 'responds with 404 Not Found if requesting non-existing trigger' do - delete v3_api("/projects/#{project.id}/triggers/abcdef012345", user) - - expect(response).to have_gitlab_http_status(404) - end - end - - context 'authenticated user with invalid permissions' do - it 'does not delete trigger' do - delete v3_api("/projects/#{project.id}/triggers/#{trigger.token}", user2) - - expect(response).to have_gitlab_http_status(403) - end - end - - context 'unauthenticated user' do - it 'does not delete trigger' do - delete v3_api("/projects/#{project.id}/triggers/#{trigger.token}") - - expect(response).to have_gitlab_http_status(401) - end - end - end -end diff --git a/spec/requests/api/v3/users_spec.rb b/spec/requests/api/v3/users_spec.rb deleted file mode 100644 index bbd05f240d2..00000000000 --- a/spec/requests/api/v3/users_spec.rb +++ /dev/null @@ -1,362 +0,0 @@ -require 'spec_helper' - -describe API::V3::Users do - let(:user) { create(:user) } - let(:admin) { create(:admin) } - let(:key) { create(:key, user: user) } - let(:email) { create(:email, user: user) } - let(:ldap_blocked_user) { create(:omniauth_user, provider: 'ldapmain', state: 'ldap_blocked') } - - describe 'GET /users' do - context 'when authenticated' do - it 'returns an array of users' do - get v3_api('/users', user) - - expect(response).to have_gitlab_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array - username = user.username - expect(json_response.detect do |user| - user['username'] == username - end['username']).to eq(username) - end - end - - context 'when authenticated as user' do - it 'does not reveal the `is_admin` flag of the user' do - get v3_api('/users', user) - - expect(json_response.first.keys).not_to include 'is_admin' - end - end - - context 'when authenticated as admin' do - it 'reveals the `is_admin` flag of the user' do - get v3_api('/users', admin) - - expect(json_response.first.keys).to include 'is_admin' - end - end - end - - describe 'GET /user/:id/keys' do - before { admin } - - context 'when unauthenticated' do - it 'returns authentication error' do - get v3_api("/users/#{user.id}/keys") - expect(response).to have_gitlab_http_status(401) - end - end - - context 'when authenticated' do - it 'returns 404 for non-existing user' do - get v3_api('/users/999999/keys', admin) - expect(response).to have_gitlab_http_status(404) - expect(json_response['message']).to eq('404 User Not Found') - end - - it 'returns array of ssh keys' do - user.keys << key - user.save - - get v3_api("/users/#{user.id}/keys", admin) - - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.first['title']).to eq(key.title) - end - end - - context "scopes" do - let(:user) { admin } - let(:path) { "/users/#{user.id}/keys" } - let(:api_call) { method(:v3_api) } - - before do - user.keys << key - user.save - end - - include_examples 'allows the "read_user" scope' - end - end - - describe 'GET /user/:id/emails' do - before { admin } - - context 'when unauthenticated' do - it 'returns authentication error' do - get v3_api("/users/#{user.id}/emails") - expect(response).to have_gitlab_http_status(401) - end - end - - context 'when authenticated' do - it 'returns 404 for non-existing user' do - get v3_api('/users/999999/emails', admin) - expect(response).to have_gitlab_http_status(404) - expect(json_response['message']).to eq('404 User Not Found') - end - - it 'returns array of emails' do - user.emails << email - user.save - - get v3_api("/users/#{user.id}/emails", admin) - - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.first['email']).to eq(email.email) - end - - it "returns a 404 for invalid ID" do - put v3_api("/users/ASDF/emails", admin) - - expect(response).to have_gitlab_http_status(404) - end - end - end - - describe "GET /user/keys" do - context "when unauthenticated" do - it "returns authentication error" do - get v3_api("/user/keys") - expect(response).to have_gitlab_http_status(401) - end - end - - context "when authenticated" do - it "returns array of ssh keys" do - user.keys << key - user.save - - get v3_api("/user/keys", user) - - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.first["title"]).to eq(key.title) - end - end - end - - describe "GET /user/emails" do - context "when unauthenticated" do - it "returns authentication error" do - get v3_api("/user/emails") - expect(response).to have_gitlab_http_status(401) - end - end - - context "when authenticated" do - it "returns array of emails" do - user.emails << email - user.save - - get v3_api("/user/emails", user) - - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.first["email"]).to eq(email.email) - end - end - end - - describe 'PUT /users/:id/block' do - before { admin } - it 'blocks existing user' do - put v3_api("/users/#{user.id}/block", admin) - expect(response).to have_gitlab_http_status(200) - expect(user.reload.state).to eq('blocked') - end - - it 'does not re-block ldap blocked users' do - put v3_api("/users/#{ldap_blocked_user.id}/block", admin) - expect(response).to have_gitlab_http_status(403) - expect(ldap_blocked_user.reload.state).to eq('ldap_blocked') - end - - it 'does not be available for non admin users' do - put v3_api("/users/#{user.id}/block", user) - expect(response).to have_gitlab_http_status(403) - expect(user.reload.state).to eq('active') - end - - it 'returns a 404 error if user id not found' do - put v3_api('/users/9999/block', admin) - expect(response).to have_gitlab_http_status(404) - expect(json_response['message']).to eq('404 User Not Found') - end - end - - describe 'PUT /users/:id/unblock' do - let(:blocked_user) { create(:user, state: 'blocked') } - before { admin } - - it 'unblocks existing user' do - put v3_api("/users/#{user.id}/unblock", admin) - expect(response).to have_gitlab_http_status(200) - expect(user.reload.state).to eq('active') - end - - it 'unblocks a blocked user' do - put v3_api("/users/#{blocked_user.id}/unblock", admin) - expect(response).to have_gitlab_http_status(200) - expect(blocked_user.reload.state).to eq('active') - end - - it 'does not unblock ldap blocked users' do - put v3_api("/users/#{ldap_blocked_user.id}/unblock", admin) - expect(response).to have_gitlab_http_status(403) - expect(ldap_blocked_user.reload.state).to eq('ldap_blocked') - end - - it 'does not be available for non admin users' do - put v3_api("/users/#{user.id}/unblock", user) - expect(response).to have_gitlab_http_status(403) - expect(user.reload.state).to eq('active') - end - - it 'returns a 404 error if user id not found' do - put v3_api('/users/9999/block', admin) - expect(response).to have_gitlab_http_status(404) - expect(json_response['message']).to eq('404 User Not Found') - end - - it "returns a 404 for invalid ID" do - put v3_api("/users/ASDF/block", admin) - - expect(response).to have_gitlab_http_status(404) - end - end - - describe 'GET /users/:id/events' do - let(:user) { create(:user) } - let(:project) { create(:project) } - let(:note) { create(:note_on_issue, note: 'What an awesome day!', project: project) } - - before do - project.add_user(user, :developer) - EventCreateService.new.leave_note(note, user) - end - - context "as a user than cannot see the event's project" do - it 'returns no events' do - other_user = create(:user) - - get api("/users/#{user.id}/events", other_user) - - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_empty - end - end - - context "as a user than can see the event's project" do - context 'when the list of events includes push events' do - let(:event) { create(:push_event, author: user, project: project) } - let!(:payload) { create(:push_event_payload, event: event) } - let(:payload_hash) { json_response[0]['push_data'] } - - before do - get api("/users/#{user.id}/events?action=pushed", user) - end - - it 'responds with HTTP 200 OK' do - expect(response).to have_gitlab_http_status(200) - end - - it 'includes the push payload as a Hash' do - expect(payload_hash).to be_an_instance_of(Hash) - end - - it 'includes the push payload details' do - expect(payload_hash['commit_count']).to eq(payload.commit_count) - expect(payload_hash['action']).to eq(payload.action) - expect(payload_hash['ref_type']).to eq(payload.ref_type) - expect(payload_hash['commit_to']).to eq(payload.commit_to) - end - end - - context 'joined event' do - it 'returns the "joined" event' do - get v3_api("/users/#{user.id}/events", user) - - expect(response).to have_gitlab_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array - - comment_event = json_response.find { |e| e['action_name'] == 'commented on' } - - expect(comment_event['project_id'].to_i).to eq(project.id) - expect(comment_event['author_username']).to eq(user.username) - expect(comment_event['note']['id']).to eq(note.id) - expect(comment_event['note']['body']).to eq('What an awesome day!') - - joined_event = json_response.find { |e| e['action_name'] == 'joined' } - - expect(joined_event['project_id'].to_i).to eq(project.id) - expect(joined_event['author_username']).to eq(user.username) - expect(joined_event['author']['name']).to eq(user.name) - end - end - - context 'when there are multiple events from different projects' do - let(:second_note) { create(:note_on_issue, project: create(:project)) } - let(:third_note) { create(:note_on_issue, project: project) } - - before do - second_note.project.add_user(user, :developer) - - [second_note, third_note].each do |note| - EventCreateService.new.leave_note(note, user) - end - end - - it 'returns events in the correct order (from newest to oldest)' do - get v3_api("/users/#{user.id}/events", user) - - comment_events = json_response.select { |e| e['action_name'] == 'commented on' } - - expect(comment_events[0]['target_id']).to eq(third_note.id) - expect(comment_events[1]['target_id']).to eq(second_note.id) - expect(comment_events[2]['target_id']).to eq(note.id) - end - end - end - - it 'returns a 404 error if not found' do - get v3_api('/users/420/events', user) - - expect(response).to have_gitlab_http_status(404) - expect(json_response['message']).to eq('404 User Not Found') - end - end - - describe 'POST /users' do - it 'creates confirmed user when confirm parameter is false' do - optional_attributes = { confirm: false } - attributes = attributes_for(:user).merge(optional_attributes) - - post v3_api('/users', admin), attributes - - user_id = json_response['id'] - new_user = User.find(user_id) - - expect(new_user).to be_confirmed - end - - it 'does not reveal the `is_admin` flag of the user' do - post v3_api('/users', admin), attributes_for(:user) - - expect(json_response['is_admin']).to be_nil - end - - context "scopes" do - let(:user) { admin } - let(:path) { '/users' } - let(:api_call) { method(:v3_api) } - - include_examples 'does not allow the "read_user" scope' - end - end -end diff --git a/spec/requests/rack_attack_global_spec.rb b/spec/requests/rack_attack_global_spec.rb index b18e922b063..c0a3ea397df 100644 --- a/spec/requests/rack_attack_global_spec.rb +++ b/spec/requests/rack_attack_global_spec.rb @@ -349,7 +349,7 @@ describe 'Rack Attack global throttles' do end def rss_url(user) - "/dashboard/projects.atom?rss_token=#{user.rss_token}" + "/dashboard/projects.atom?feed_token=#{user.feed_token}" end def private_token_headers(user) diff --git a/spec/routing/routing_spec.rb b/spec/routing/routing_spec.rb index 9345671a1a7..dd8f6239587 100644 --- a/spec/routing/routing_spec.rb +++ b/spec/routing/routing_spec.rb @@ -162,8 +162,8 @@ describe ProfilesController, "routing" do expect(get("/profile/audit_log")).to route_to('profiles#audit_log') end - it "to #reset_rss_token" do - expect(put("/profile/reset_rss_token")).to route_to('profiles#reset_rss_token') + it "to #reset_feed_token" do + expect(put("/profile/reset_feed_token")).to route_to('profiles#reset_feed_token') end it "to #show" do @@ -249,7 +249,11 @@ describe DashboardController, "routing" do end it "to #issues" do - expect(get("/dashboard/issues")).to route_to('dashboard#issues') + expect(get("/dashboard/issues.html")).to route_to('dashboard#issues', format: 'html') + end + + it "to #calendar_issues" do + expect(get("/dashboard/issues.ics")).to route_to('dashboard#issues_calendar', format: 'ics') end it "to #merge_requests" do diff --git a/spec/serializers/job_entity_spec.rb b/spec/serializers/job_entity_spec.rb index c90396ebb28..a5581a34517 100644 --- a/spec/serializers/job_entity_spec.rb +++ b/spec/serializers/job_entity_spec.rb @@ -131,7 +131,7 @@ describe JobEntity do end context 'when job failed' do - let(:job) { create(:ci_build, :script_failure) } + let(:job) { create(:ci_build, :api_failure) } it 'contains details' do expect(subject[:status]).to include :icon, :favicon, :text, :label, :tooltip @@ -142,20 +142,20 @@ describe JobEntity do end it 'should indicate the failure reason on tooltip' do - expect(subject[:status][:tooltip]).to eq('failed <br> (script failure)') + expect(subject[:status][:tooltip]).to eq('failed <br> (API failure)') end it 'should include a callout message with a verbose output' do - expect(subject[:callout_message]).to eq('There has been a script failure. Check the job log for more information') + expect(subject[:callout_message]).to eq('There has been an API failure, please try again') end it 'should state that it is not recoverable' do - expect(subject[:recoverable]).to be_falsy + expect(subject[:recoverable]).to be_truthy end end context 'when job is allowed to fail' do - let(:job) { create(:ci_build, :allowed_to_fail, :script_failure) } + let(:job) { create(:ci_build, :allowed_to_fail, :api_failure) } it 'contains details' do expect(subject[:status]).to include :icon, :favicon, :text, :label, :tooltip @@ -166,15 +166,24 @@ describe JobEntity do end it 'should indicate the failure reason on tooltip' do - expect(subject[:status][:tooltip]).to eq('failed <br> (script failure) (allowed to fail)') + expect(subject[:status][:tooltip]).to eq('failed <br> (API failure) (allowed to fail)') end it 'should include a callout message with a verbose output' do - expect(subject[:callout_message]).to eq('There has been a script failure. Check the job log for more information') + expect(subject[:callout_message]).to eq('There has been an API failure, please try again') end it 'should state that it is not recoverable' do - expect(subject[:recoverable]).to be_falsy + expect(subject[:recoverable]).to be_truthy + end + end + + context 'when the job failed with a script failure' do + let(:job) { create(:ci_build, :failed, :script_failure) } + + it 'should not include callout message or recoverable keys' do + expect(subject).not_to include('callout_message') + expect(subject).not_to include('recoverable') end end diff --git a/spec/support/api/v3/time_tracking_shared_examples.rb b/spec/support/api/v3/time_tracking_shared_examples.rb deleted file mode 100644 index f27a2d06c83..00000000000 --- a/spec/support/api/v3/time_tracking_shared_examples.rb +++ /dev/null @@ -1,128 +0,0 @@ -shared_examples 'V3 time tracking endpoints' do |issuable_name| - issuable_collection_name = issuable_name.pluralize - - describe "POST /projects/:id/#{issuable_collection_name}/:#{issuable_name}_id/time_estimate" do - context 'with an unauthorized user' do - subject { post(v3_api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.id}/time_estimate", non_member), duration: '1w') } - - it_behaves_like 'an unauthorized API user' - end - - it "sets the time estimate for #{issuable_name}" do - post v3_api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.id}/time_estimate", user), duration: '1w' - - expect(response).to have_gitlab_http_status(200) - expect(json_response['human_time_estimate']).to eq('1w') - end - - describe 'updating the current estimate' do - before do - post v3_api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.id}/time_estimate", user), duration: '1w' - end - - context 'when duration has a bad format' do - it 'does not modify the original estimate' do - post v3_api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.id}/time_estimate", user), duration: 'foo' - - expect(response).to have_gitlab_http_status(400) - expect(issuable.reload.human_time_estimate).to eq('1w') - end - end - - context 'with a valid duration' do - it 'updates the estimate' do - post v3_api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.id}/time_estimate", user), duration: '3w1h' - - expect(response).to have_gitlab_http_status(200) - expect(issuable.reload.human_time_estimate).to eq('3w 1h') - end - end - end - end - - describe "POST /projects/:id/#{issuable_collection_name}/:#{issuable_name}_id/reset_time_estimate" do - context 'with an unauthorized user' do - subject { post(v3_api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.id}/reset_time_estimate", non_member)) } - - it_behaves_like 'an unauthorized API user' - end - - it "resets the time estimate for #{issuable_name}" do - post v3_api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.id}/reset_time_estimate", user) - - expect(response).to have_gitlab_http_status(200) - expect(json_response['time_estimate']).to eq(0) - end - end - - describe "POST /projects/:id/#{issuable_collection_name}/:#{issuable_name}_id/add_spent_time" do - context 'with an unauthorized user' do - subject do - post v3_api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.id}/add_spent_time", non_member), - duration: '2h' - end - - it_behaves_like 'an unauthorized API user' - end - - it "add spent time for #{issuable_name}" do - post v3_api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.id}/add_spent_time", user), - duration: '2h' - - expect(response).to have_gitlab_http_status(201) - expect(json_response['human_total_time_spent']).to eq('2h') - end - - context 'when subtracting time' do - it 'subtracts time of the total spent time' do - issuable.update_attributes!(spend_time: { duration: 7200, user_id: user.id }) - - post v3_api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.id}/add_spent_time", user), - duration: '-1h' - - expect(response).to have_gitlab_http_status(201) - expect(json_response['total_time_spent']).to eq(3600) - end - end - - context 'when time to subtract is greater than the total spent time' do - it 'does not modify the total time spent' do - issuable.update_attributes!(spend_time: { duration: 7200, user_id: user.id }) - - post v3_api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.id}/add_spent_time", user), - duration: '-1w' - - expect(response).to have_gitlab_http_status(400) - expect(json_response['message']['time_spent'].first).to match(/exceeds the total time spent/) - end - end - end - - describe "POST /projects/:id/#{issuable_collection_name}/:#{issuable_name}_id/reset_spent_time" do - context 'with an unauthorized user' do - subject { post(v3_api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.id}/reset_spent_time", non_member)) } - - it_behaves_like 'an unauthorized API user' - end - - it "resets spent time for #{issuable_name}" do - post v3_api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.id}/reset_spent_time", user) - - expect(response).to have_gitlab_http_status(200) - expect(json_response['total_time_spent']).to eq(0) - end - end - - describe "GET /projects/:id/#{issuable_collection_name}/:#{issuable_name}_id/time_stats" do - it "returns the time stats for #{issuable_name}" do - issuable.update_attributes!(spend_time: { duration: 1800, user_id: user.id }, - time_estimate: 3600) - - get v3_api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.id}/time_stats", user) - - expect(response).to have_gitlab_http_status(200) - expect(json_response['total_time_spent']).to eq(1800) - expect(json_response['time_estimate']).to eq(3600) - end - end -end diff --git a/spec/support/features/rss_shared_examples.rb b/spec/support/features/rss_shared_examples.rb index 50fbbc7f55b..0de92aedba5 100644 --- a/spec/support/features/rss_shared_examples.rb +++ b/spec/support/features/rss_shared_examples.rb @@ -1,23 +1,23 @@ -shared_examples "an autodiscoverable RSS feed with current_user's RSS token" do - it "has an RSS autodiscovery link tag with current_user's RSS token" do - expect(page).to have_css("link[type*='atom+xml'][href*='rss_token=#{user.rss_token}']", visible: false) +shared_examples "an autodiscoverable RSS feed with current_user's feed token" do + it "has an RSS autodiscovery link tag with current_user's feed token" do + expect(page).to have_css("link[type*='atom+xml'][href*='feed_token=#{user.feed_token}']", visible: false) end end -shared_examples "it has an RSS button with current_user's RSS token" do - it "shows the RSS button with current_user's RSS token" do - expect(page).to have_css("a:has(.fa-rss)[href*='rss_token=#{user.rss_token}']") +shared_examples "it has an RSS button with current_user's feed token" do + it "shows the RSS button with current_user's feed token" do + expect(page).to have_css("a:has(.fa-rss)[href*='feed_token=#{user.feed_token}']") end end -shared_examples "an autodiscoverable RSS feed without an RSS token" do - it "has an RSS autodiscovery link tag without an RSS token" do - expect(page).to have_css("link[type*='atom+xml']:not([href*='rss_token'])", visible: false) +shared_examples "an autodiscoverable RSS feed without a feed token" do + it "has an RSS autodiscovery link tag without a feed token" do + expect(page).to have_css("link[type*='atom+xml']:not([href*='feed_token'])", visible: false) end end -shared_examples "it has an RSS button without an RSS token" do - it "shows the RSS button without an RSS token" do - expect(page).to have_css("a:has(.fa-rss):not([href*='rss_token'])") +shared_examples "it has an RSS button without a feed token" do + it "shows the RSS button without a feed token" do + expect(page).to have_css("a:has(.fa-rss):not([href*='feed_token'])") end end diff --git a/spec/support/gitlab-git-test.git/packed-refs b/spec/support/gitlab-git-test.git/packed-refs index ea50e4ad3f6..6a61e5df267 100644 --- a/spec/support/gitlab-git-test.git/packed-refs +++ b/spec/support/gitlab-git-test.git/packed-refs @@ -9,6 +9,7 @@ 4b4918a572fa86f9771e5ba40fbd48e1eb03e2c6 refs/heads/master 5937ac0a7beb003549fc5fd26fc247adbce4a52e refs/heads/merge-test 9596bc54a6f0c0c98248fe97077eb5ccf48a98d0 refs/heads/missing-gitmodules +4b4918a572fa86f9771e5ba40fbd48e1eb03e2c6 refs/heads/Ääh-test-utf-8 f4e6814c3e4e7a0de82a9e7cd20c626cc963a2f8 refs/tags/v1.0.0 ^6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 8a2a6eb295bb170b34c24c76c49ed0e9b2eaf34b refs/tags/v1.1.0 diff --git a/spec/support/gitlab_stubs/project_8.json b/spec/support/gitlab_stubs/project_8.json index f0a9fce859c..81b08ab8288 100644 --- a/spec/support/gitlab_stubs/project_8.json +++ b/spec/support/gitlab_stubs/project_8.json @@ -1,38 +1,38 @@ { - "id":8, - "description":"ssh access and repository management app for GitLab", - "default_branch":"master", - "public":false, - "visibility_level":0, - "ssh_url_to_repo":"git@demo.gitlab.com:gitlab/gitlab-shell.git", - "http_url_to_repo":"http://demo.gitlab.com/gitlab/gitlab-shell.git", - "web_url":"http://demo.gitlab.com/gitlab/gitlab-shell", - "owner": { - "id":4, - "name":"GitLab", - "created_at":"2012-12-21T13:03:05Z" - }, - "name":"gitlab-shell", - "name_with_namespace":"GitLab / gitlab-shell", - "path":"gitlab-shell", - "path_with_namespace":"gitlab/gitlab-shell", - "issues_enabled":true, - "merge_requests_enabled":true, - "wall_enabled":false, - "wiki_enabled":true, - "snippets_enabled":false, - "created_at":"2013-03-20T13:28:53Z", - "last_activity_at":"2013-11-30T00:11:17Z", - "namespace":{ - "created_at":"2012-12-21T13:03:05Z", - "description":"Self hosted Git management software", - "id":4, - "name":"GitLab", - "owner_id":1, - "path":"gitlab", - "updated_at":"2013-03-20T13:29:13Z" + "id": 8, + "description": "ssh access and repository management app for GitLab", + "default_branch": "master", + "visibility": "private", + "ssh_url_to_repo": "git@demo.gitlab.com:gitlab/gitlab-shell.git", + "http_url_to_repo": "http://demo.gitlab.com/gitlab/gitlab-shell.git", + "web_url": "http://demo.gitlab.com/gitlab/gitlab-shell", + "owner": { + "id": 4, + "name": "GitLab", + "username": "gitlab", + "state": "active", + "avatar_url": "https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61", + "web_url": "http://demo.gitlab.com/gitlab" }, - "permissions":{ + "name": "gitlab-shell", + "name_with_namespace": "GitLab / gitlab-shell", + "path": "gitlab-shell", + "path_with_namespace": "gitlab/gitlab-shell", + "issues_enabled": true, + "merge_requests_enabled": true, + "wiki_enabled": true, + "snippets_enabled": false, + "created_at": "2013-03-20T13:28:53Z", + "last_activity_at": "2013-11-30T00:11:17Z", + "namespace": { + "id": 4, + "name": "GitLab", + "path": "gitlab", + "kind": "group", + "full_path": "gitlab", + "parent_id": null + }, + "permissions": { "project_access": { "access_level": 10, "notification_level": 3 @@ -42,4 +42,4 @@ "notification_level": 3 } } -}
\ No newline at end of file +} diff --git a/spec/support/gitlab_stubs/projects.json b/spec/support/gitlab_stubs/projects.json index ca42c14c5d8..67ce1acca2c 100644 --- a/spec/support/gitlab_stubs/projects.json +++ b/spec/support/gitlab_stubs/projects.json @@ -1 +1,282 @@ -[{"id":3,"description":"GitLab is open source software to collaborate on code. Create projects and repositories, manage access and do code reviews.","default_branch":"master","public":true,"visibility_level":20,"ssh_url_to_repo":"git@demo.gitlab.com:gitlab/gitlabhq.git","http_url_to_repo":"http://demo.gitlab.com/gitlab/gitlabhq.git","web_url":"http://demo.gitlab.com/gitlab/gitlabhq","owner":{"id":4,"name":"GitLab","created_at":"2012-12-21T13:03:05Z"},"name":"gitlabhq","name_with_namespace":"GitLab / gitlabhq","path":"gitlabhq","path_with_namespace":"gitlab/gitlabhq","issues_enabled":true,"merge_requests_enabled":true,"wall_enabled":true,"wiki_enabled":true,"snippets_enabled":true,"created_at":"2012-12-21T13:06:34Z","last_activity_at":"2013-12-02T19:10:10Z","namespace":{"created_at":"2012-12-21T13:03:05Z","description":"Self hosted Git management software","id":4,"name":"GitLab","owner_id":1,"path":"gitlab","updated_at":"2013-03-20T13:29:13Z"}},{"id":4,"description":"Component of GitLab CI. Web application","default_branch":"master","public":false,"visibility_level":0,"ssh_url_to_repo":"git@demo.gitlab.com:gitlab/gitlab-ci.git","http_url_to_repo":"http://demo.gitlab.com/gitlab/gitlab-ci.git","web_url":"http://demo.gitlab.com/gitlab/gitlab-ci","owner":{"id":4,"name":"GitLab","created_at":"2012-12-21T13:03:05Z"},"name":"gitlab-ci","name_with_namespace":"GitLab / gitlab-ci","path":"gitlab-ci","path_with_namespace":"gitlab/gitlab-ci","issues_enabled":true,"merge_requests_enabled":true,"wall_enabled":true,"wiki_enabled":true,"snippets_enabled":true,"created_at":"2012-12-21T13:06:50Z","last_activity_at":"2013-11-28T19:26:54Z","namespace":{"created_at":"2012-12-21T13:03:05Z","description":"Self hosted Git management software","id":4,"name":"GitLab","owner_id":1,"path":"gitlab","updated_at":"2013-03-20T13:29:13Z"}},{"id":5,"description":"","default_branch":"master","public":true,"visibility_level":20,"ssh_url_to_repo":"git@demo.gitlab.com:gitlab/gitlab-recipes.git","http_url_to_repo":"http://demo.gitlab.com/gitlab/gitlab-recipes.git","web_url":"http://demo.gitlab.com/gitlab/gitlab-recipes","owner":{"id":4,"name":"GitLab","created_at":"2012-12-21T13:03:05Z"},"name":"gitlab-recipes","name_with_namespace":"GitLab / gitlab-recipes","path":"gitlab-recipes","path_with_namespace":"gitlab/gitlab-recipes","issues_enabled":true,"merge_requests_enabled":true,"wall_enabled":true,"wiki_enabled":true,"snippets_enabled":true,"created_at":"2012-12-21T13:07:02Z","last_activity_at":"2013-12-02T13:54:10Z","namespace":{"created_at":"2012-12-21T13:03:05Z","description":"Self hosted Git management software","id":4,"name":"GitLab","owner_id":1,"path":"gitlab","updated_at":"2013-03-20T13:29:13Z"}},{"id":8,"description":"ssh access and repository management app for GitLab","default_branch":"master","public":false,"visibility_level":0,"ssh_url_to_repo":"git@demo.gitlab.com:gitlab/gitlab-shell.git","http_url_to_repo":"http://demo.gitlab.com/gitlab/gitlab-shell.git","web_url":"http://demo.gitlab.com/gitlab/gitlab-shell","owner":{"id":4,"name":"GitLab","created_at":"2012-12-21T13:03:05Z"},"name":"gitlab-shell","name_with_namespace":"GitLab / gitlab-shell","path":"gitlab-shell","path_with_namespace":"gitlab/gitlab-shell","issues_enabled":true,"merge_requests_enabled":true,"wall_enabled":false,"wiki_enabled":true,"snippets_enabled":false,"created_at":"2013-03-20T13:28:53Z","last_activity_at":"2013-11-30T00:11:17Z","namespace":{"created_at":"2012-12-21T13:03:05Z","description":"Self hosted Git management software","id":4,"name":"GitLab","owner_id":1,"path":"gitlab","updated_at":"2013-03-20T13:29:13Z"}},{"id":9,"description":null,"default_branch":"master","public":false,"visibility_level":0,"ssh_url_to_repo":"git@demo.gitlab.com:gitlab/gitlab_git.git","http_url_to_repo":"http://demo.gitlab.com/gitlab/gitlab_git.git","web_url":"http://demo.gitlab.com/gitlab/gitlab_git","owner":{"id":4,"name":"GitLab","created_at":"2012-12-21T13:03:05Z"},"name":"gitlab_git","name_with_namespace":"GitLab / gitlab_git","path":"gitlab_git","path_with_namespace":"gitlab/gitlab_git","issues_enabled":true,"merge_requests_enabled":true,"wall_enabled":false,"wiki_enabled":true,"snippets_enabled":false,"created_at":"2013-04-28T19:15:08Z","last_activity_at":"2013-12-02T13:07:13Z","namespace":{"created_at":"2012-12-21T13:03:05Z","description":"Self hosted Git management software","id":4,"name":"GitLab","owner_id":1,"path":"gitlab","updated_at":"2013-03-20T13:29:13Z"}},{"id":10,"description":"ultra lite authorization library http://randx.github.com/six/\\r\\n ","default_branch":"master","public":true,"visibility_level":20,"ssh_url_to_repo":"git@demo.gitlab.com:sandbox/six.git","http_url_to_repo":"http://demo.gitlab.com/sandbox/six.git","web_url":"http://demo.gitlab.com/sandbox/six","owner":{"id":8,"name":"Sandbox","created_at":"2013-08-01T16:44:17Z"},"name":"Six","name_with_namespace":"Sandbox / Six","path":"six","path_with_namespace":"sandbox/six","issues_enabled":true,"merge_requests_enabled":true,"wall_enabled":false,"wiki_enabled":true,"snippets_enabled":false,"created_at":"2013-08-01T16:45:02Z","last_activity_at":"2013-11-29T11:30:56Z","namespace":{"created_at":"2013-08-01T16:44:17Z","description":"","id":8,"name":"Sandbox","owner_id":1,"path":"sandbox","updated_at":"2013-08-01T16:44:17Z"}},{"id":11,"description":"Simple HTML5 Charts using the <canvas> tag ","default_branch":"master","public":false,"visibility_level":0,"ssh_url_to_repo":"git@demo.gitlab.com:sandbox/charts-js.git","http_url_to_repo":"http://demo.gitlab.com/sandbox/charts-js.git","web_url":"http://demo.gitlab.com/sandbox/charts-js","owner":{"id":8,"name":"Sandbox","created_at":"2013-08-01T16:44:17Z"},"name":"Charts.js","name_with_namespace":"Sandbox / Charts.js","path":"charts-js","path_with_namespace":"sandbox/charts-js","issues_enabled":true,"merge_requests_enabled":true,"wall_enabled":false,"wiki_enabled":true,"snippets_enabled":false,"created_at":"2013-08-01T16:47:29Z","last_activity_at":"2013-12-02T15:18:11Z","namespace":{"created_at":"2013-08-01T16:44:17Z","description":"","id":8,"name":"Sandbox","owner_id":1,"path":"sandbox","updated_at":"2013-08-01T16:44:17Z"}},{"id":13,"description":"","default_branch":"master","public":false,"visibility_level":0,"ssh_url_to_repo":"git@demo.gitlab.com:sandbox/afro.git","http_url_to_repo":"http://demo.gitlab.com/sandbox/afro.git","web_url":"http://demo.gitlab.com/sandbox/afro","owner":{"id":8,"name":"Sandbox","created_at":"2013-08-01T16:44:17Z"},"name":"Afro","name_with_namespace":"Sandbox / Afro","path":"afro","path_with_namespace":"sandbox/afro","issues_enabled":true,"merge_requests_enabled":true,"wall_enabled":false,"wiki_enabled":true,"snippets_enabled":false,"created_at":"2013-11-14T17:45:19Z","last_activity_at":"2013-12-02T17:41:45Z","namespace":{"created_at":"2013-08-01T16:44:17Z","description":"","id":8,"name":"Sandbox","owner_id":1,"path":"sandbox","updated_at":"2013-08-01T16:44:17Z"}}]
\ No newline at end of file +[ + { + "id": 3, + "description": "GitLab is open source software to collaborate on code. Create projects and repositories, manage access and do code reviews.", + "default_branch": "master", + "visibility": "public", + "ssh_url_to_repo": "git@demo.gitlab.com:gitlab/gitlabhq.git", + "http_url_to_repo": "http://demo.gitlab.com/gitlab/gitlabhq.git", + "web_url": "http://demo.gitlab.com/gitlab/gitlabhq", + "owner": { + "id": 4, + "name": "GitLab", + "username": "gitlab", + "state": "active", + "avatar_url": "https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61", + "web_url": "http://demo.gitlab.com/gitlab" + }, + "name": "gitlabhq", + "name_with_namespace": "GitLab / gitlabhq", + "path": "gitlabhq", + "path_with_namespace": "gitlab/gitlabhq", + "issues_enabled": true, + "merge_requests_enabled": true, + "wiki_enabled": true, + "snippets_enabled": true, + "created_at": "2012-12-21T13:06:34Z", + "last_activity_at": "2013-12-02T19:10:10Z", + "namespace": { + "id": 4, + "name": "GitLab", + "path": "gitlab", + "kind": "group", + "full_path": "gitlab", + "parent_id": null + } + }, + { + "id": 4, + "description": "Component of GitLab CI. Web application", + "default_branch": "master", + "visibility": "private", + "ssh_url_to_repo": "git@demo.gitlab.com:gitlab/gitlab-ci.git", + "http_url_to_repo": "http://demo.gitlab.com/gitlab/gitlab-ci.git", + "web_url": "http://demo.gitlab.com/gitlab/gitlab-ci", + "owner": { + "id": 4, + "name": "GitLab", + "username": "gitlab", + "state": "active", + "avatar_url": "https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61", + "web_url": "http://demo.gitlab.com/gitlab" + }, + "name": "gitlab-ci", + "name_with_namespace": "GitLab / gitlab-ci", + "path": "gitlab-ci", + "path_with_namespace": "gitlab/gitlab-ci", + "issues_enabled": true, + "merge_requests_enabled": true, + "wiki_enabled": true, + "snippets_enabled": true, + "created_at": "2012-12-21T13:06:50Z", + "last_activity_at": "2013-11-28T19:26:54Z", + "namespace": { + "id": 4, + "name": "GitLab", + "path": "gitlab", + "kind": "group", + "full_path": "gitlab", + "parent_id": null + } + }, + { + "id": 5, + "description": "", + "default_branch": "master", + "visibility": "public", + "ssh_url_to_repo": "git@demo.gitlab.com:gitlab/gitlab-recipes.git", + "http_url_to_repo": "http://demo.gitlab.com/gitlab/gitlab-recipes.git", + "web_url": "http://demo.gitlab.com/gitlab/gitlab-recipes", + "owner": { + "id": 4, + "name": "GitLab", + "username": "gitlab", + "state": "active", + "avatar_url": "https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61", + "web_url": "http://demo.gitlab.com/gitlab" + }, + "name": "gitlab-recipes", + "name_with_namespace": "GitLab / gitlab-recipes", + "path": "gitlab-recipes", + "path_with_namespace": "gitlab/gitlab-recipes", + "issues_enabled": true, + "merge_requests_enabled": true, + "wiki_enabled": true, + "snippets_enabled": true, + "created_at": "2012-12-21T13:07:02Z", + "last_activity_at": "2013-12-02T13:54:10Z", + "namespace": { + "id": 4, + "name": "GitLab", + "path": "gitlab", + "kind": "group", + "full_path": "gitlab", + "parent_id": null + } + }, + { + "id": 8, + "description": "ssh access and repository management app for GitLab", + "default_branch": "master", + "visibility": "private", + "ssh_url_to_repo": "git@demo.gitlab.com:gitlab/gitlab-shell.git", + "http_url_to_repo": "http://demo.gitlab.com/gitlab/gitlab-shell.git", + "web_url": "http://demo.gitlab.com/gitlab/gitlab-shell", + "owner": { + "id": 4, + "name": "GitLab", + "username": "gitlab", + "state": "active", + "avatar_url": "https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61", + "web_url": "http://demo.gitlab.com/gitlab" + }, + "name": "gitlab-shell", + "name_with_namespace": "GitLab / gitlab-shell", + "path": "gitlab-shell", + "path_with_namespace": "gitlab/gitlab-shell", + "issues_enabled": true, + "merge_requests_enabled": true, + "wiki_enabled": true, + "snippets_enabled": false, + "created_at": "2013-03-20T13:28:53Z", + "last_activity_at": "2013-11-30T00:11:17Z", + "namespace": { + "id": 4, + "name": "GitLab", + "path": "gitlab", + "kind": "group", + "full_path": "gitlab", + "parent_id": null + } + }, + { + "id": 9, + "description": null, + "default_branch": "master", + "visibility": "private", + "ssh_url_to_repo": "git@demo.gitlab.com:gitlab/gitlab_git.git", + "http_url_to_repo": "http://demo.gitlab.com/gitlab/gitlab_git.git", + "web_url": "http://demo.gitlab.com/gitlab/gitlab_git", + "owner": { + "id": 4, + "name": "GitLab", + "username": "gitlab", + "state": "active", + "avatar_url": "https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61", + "web_url": "http://demo.gitlab.com/gitlab" + }, + "name": "gitlab_git", + "name_with_namespace": "GitLab / gitlab_git", + "path": "gitlab_git", + "path_with_namespace": "gitlab/gitlab_git", + "issues_enabled": true, + "merge_requests_enabled": true, + "wiki_enabled": true, + "snippets_enabled": false, + "created_at": "2013-04-28T19:15:08Z", + "last_activity_at": "2013-12-02T13:07:13Z", + "namespace": { + "id": 4, + "name": "GitLab", + "path": "gitlab", + "kind": "group", + "full_path": "gitlab", + "parent_id": null + } + }, + { + "id": 10, + "description": "ultra lite authorization library http://randx.github.com/six/\\r\\n ", + "default_branch": "master", + "visibility": "public", + "ssh_url_to_repo": "git@demo.gitlab.com:sandbox/six.git", + "http_url_to_repo": "http://demo.gitlab.com/sandbox/six.git", + "web_url": "http://demo.gitlab.com/sandbox/six", + "owner": { + "id": 4, + "name": "GitLab", + "username": "gitlab", + "state": "active", + "avatar_url": "https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61", + "web_url": "http://demo.gitlab.com/gitlab" + }, + "name": "Six", + "name_with_namespace": "Sandbox / Six", + "path": "six", + "path_with_namespace": "sandbox/six", + "issues_enabled": true, + "merge_requests_enabled": true, + "wiki_enabled": true, + "snippets_enabled": false, + "created_at": "2013-08-01T16:45:02Z", + "last_activity_at": "2013-11-29T11:30:56Z", + "namespace": { + "id": 4, + "name": "GitLab", + "path": "gitlab", + "kind": "group", + "full_path": "gitlab", + "parent_id": null + } + }, + { + "id": 11, + "description": "Simple HTML5 Charts using the <canvas> tag ", + "default_branch": "master", + "visibility": "private", + "ssh_url_to_repo": "git@demo.gitlab.com:sandbox/charts-js.git", + "http_url_to_repo": "http://demo.gitlab.com/sandbox/charts-js.git", + "web_url": "http://demo.gitlab.com/sandbox/charts-js", + "owner": { + "id": 4, + "name": "GitLab", + "username": "gitlab", + "state": "active", + "avatar_url": "https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61", + "web_url": "http://demo.gitlab.com/gitlab" + }, + "name": "Charts.js", + "name_with_namespace": "Sandbox / Charts.js", + "path": "charts-js", + "path_with_namespace": "sandbox/charts-js", + "issues_enabled": true, + "merge_requests_enabled": true, + "wiki_enabled": true, + "snippets_enabled": false, + "created_at": "2013-08-01T16:47:29Z", + "last_activity_at": "2013-12-02T15:18:11Z", + "namespace": { + "id": 4, + "name": "GitLab", + "path": "gitlab", + "kind": "group", + "full_path": "gitlab", + "parent_id": null + } + }, + { + "id": 13, + "description": "", + "default_branch": "master", + "visibility": "private", + "ssh_url_to_repo": "git@demo.gitlab.com:sandbox/afro.git", + "http_url_to_repo": "http://demo.gitlab.com/sandbox/afro.git", + "web_url": "http://demo.gitlab.com/sandbox/afro", + "owner": { + "id": 4, + "name": "GitLab", + "username": "gitlab", + "state": "active", + "avatar_url": "https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61", + "web_url": "http://demo.gitlab.com/gitlab" + }, + "name": "Afro", + "name_with_namespace": "Sandbox / Afro", + "path": "afro", + "path_with_namespace": "sandbox/afro", + "issues_enabled": true, + "merge_requests_enabled": true, + "wiki_enabled": true, + "snippets_enabled": false, + "created_at": "2013-11-14T17:45:19Z", + "last_activity_at": "2013-12-02T17:41:45Z", + "namespace": { + "id": 4, + "name": "GitLab", + "path": "gitlab", + "kind": "group", + "full_path": "gitlab", + "parent_id": null + } + } +] diff --git a/spec/support/gitlab_stubs/session.json b/spec/support/gitlab_stubs/session.json deleted file mode 100644 index 658ff5871b0..00000000000 --- a/spec/support/gitlab_stubs/session.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "id":2, - "username":"jsmith", - "email":"test@test.com", - "name":"John Smith", - "bio":"", - "skype":"aertert", - "linkedin":"", - "twitter":"", - "theme_id":2,"color_scheme_id":2, - "state":"active", - "created_at":"2012-12-21T13:02:20Z", - "extern_uid":null, - "provider":null, - "is_admin":false, - "can_create_group":false, - "can_create_project":false -} diff --git a/spec/support/helpers/api_helpers.rb b/spec/support/helpers/api_helpers.rb index ac0c7a9b493..a57a3b2cf34 100644 --- a/spec/support/helpers/api_helpers.rb +++ b/spec/support/helpers/api_helpers.rb @@ -36,15 +36,4 @@ module ApiHelpers full_path end - - # Temporary helper method for simplifying V3 exclusive API specs - def v3_api(path, user = nil, personal_access_token: nil, oauth_access_token: nil) - api( - path, - user, - version: 'v3', - personal_access_token: personal_access_token, - oauth_access_token: oauth_access_token - ) - end end diff --git a/spec/support/helpers/drag_to_helper.rb b/spec/support/helpers/drag_to_helper.rb index ae149631ed9..6d53ad0b602 100644 --- a/spec/support/helpers/drag_to_helper.rb +++ b/spec/support/helpers/drag_to_helper.rb @@ -1,6 +1,6 @@ module DragTo - def drag_to(list_from_index: 0, from_index: 0, to_index: 0, list_to_index: 0, selector: '', scrollable: 'body') - evaluate_script("simulateDrag({scrollable: $('#{scrollable}').get(0), from: {el: $('#{selector}').eq(#{list_from_index}).get(0), index: #{from_index}}, to: {el: $('#{selector}').eq(#{list_to_index}).get(0), index: #{to_index}}});") + def drag_to(list_from_index: 0, from_index: 0, to_index: 0, list_to_index: 0, selector: '', scrollable: 'body', duration: 1000) + evaluate_script("simulateDrag({scrollable: $('#{scrollable}').get(0), duration: #{duration}, from: {el: $('#{selector}').eq(#{list_from_index}).get(0), index: #{from_index}}, to: {el: $('#{selector}').eq(#{list_to_index}).get(0), index: #{to_index}}});") Timeout.timeout(Capybara.default_max_wait_time) do loop while drag_active? diff --git a/spec/support/helpers/seed_repo.rb b/spec/support/helpers/seed_repo.rb index b4868e82cd7..71f1a86b0c1 100644 --- a/spec/support/helpers/seed_repo.rb +++ b/spec/support/helpers/seed_repo.rb @@ -98,6 +98,7 @@ module SeedRepo master merge-test missing-gitmodules + Ääh-test-utf-8 ].freeze TAGS = %w[ v1.0.0 diff --git a/spec/support/helpers/stub_gitlab_calls.rb b/spec/support/helpers/stub_gitlab_calls.rb index c1618f5086c..2933f2c78dc 100644 --- a/spec/support/helpers/stub_gitlab_calls.rb +++ b/spec/support/helpers/stub_gitlab_calls.rb @@ -1,6 +1,5 @@ module StubGitlabCalls def stub_gitlab_calls - stub_session stub_user stub_project_8 stub_project_8_hooks @@ -71,23 +70,14 @@ module StubGitlabCalls Gitlab.config.gitlab.url end - def stub_session - f = File.read(Rails.root.join('spec/support/gitlab_stubs/session.json')) - - stub_request(:post, "#{gitlab_url}api/v3/session.json") - .with(body: "{\"email\":\"test@test.com\",\"password\":\"123456\"}", - headers: { 'Content-Type' => 'application/json' }) - .to_return(status: 201, body: f, headers: { 'Content-Type' => 'application/json' }) - end - def stub_user f = File.read(Rails.root.join('spec/support/gitlab_stubs/user.json')) - stub_request(:get, "#{gitlab_url}api/v3/user?private_token=Wvjy2Krpb7y8xi93owUz") + stub_request(:get, "#{gitlab_url}api/v4/user?private_token=Wvjy2Krpb7y8xi93owUz") .with(headers: { 'Content-Type' => 'application/json' }) .to_return(status: 200, body: f, headers: { 'Content-Type' => 'application/json' }) - stub_request(:get, "#{gitlab_url}api/v3/user?access_token=some_token") + stub_request(:get, "#{gitlab_url}api/v4/user?access_token=some_token") .with(headers: { 'Content-Type' => 'application/json' }) .to_return(status: 200, body: f, headers: { 'Content-Type' => 'application/json' }) end @@ -105,19 +95,19 @@ module StubGitlabCalls def stub_projects f = File.read(Rails.root.join('spec/support/gitlab_stubs/projects.json')) - stub_request(:get, "#{gitlab_url}api/v3/projects.json?archived=false&ci_enabled_first=true&private_token=Wvjy2Krpb7y8xi93owUz") + stub_request(:get, "#{gitlab_url}api/v4/projects.json?archived=false&ci_enabled_first=true&private_token=Wvjy2Krpb7y8xi93owUz") .with(headers: { 'Content-Type' => 'application/json' }) .to_return(status: 200, body: f, headers: { 'Content-Type' => 'application/json' }) end def stub_projects_owned - stub_request(:get, "#{gitlab_url}api/v3/projects/owned.json?archived=false&ci_enabled_first=true&private_token=Wvjy2Krpb7y8xi93owUz") + stub_request(:get, "#{gitlab_url}api/v4/projects?owned=true&archived=false&ci_enabled_first=true&private_token=Wvjy2Krpb7y8xi93owUz") .with(headers: { 'Content-Type' => 'application/json' }) .to_return(status: 200, body: "", headers: {}) end def stub_ci_enable - stub_request(:put, "#{gitlab_url}api/v3/projects/2/services/gitlab-ci.json?private_token=Wvjy2Krpb7y8xi93owUz") + stub_request(:put, "#{gitlab_url}api/v4/projects/2/services/gitlab-ci.json?private_token=Wvjy2Krpb7y8xi93owUz") .with(headers: { 'Content-Type' => 'application/json' }) .to_return(status: 200, body: "", headers: {}) end diff --git a/spec/support/import_export/configuration_helper.rb b/spec/support/import_export/configuration_helper.rb index f752508d48c..bbac6ca6a9c 100644 --- a/spec/support/import_export/configuration_helper.rb +++ b/spec/support/import_export/configuration_helper.rb @@ -10,7 +10,7 @@ module ConfigurationHelper def relation_class_for_name(relation_name) relation_name = Gitlab::ImportExport::RelationFactory::OVERRIDES[relation_name.to_sym] || relation_name - relation_name.to_s.classify.constantize + Gitlab::ImportExport::RelationFactory.relation_class(relation_name) end def parsed_attributes(relation_name, attributes) diff --git a/spec/support/shared_examples/requests/api/merge_requests_list.rb b/spec/support/shared_examples/requests/api/merge_requests_list.rb new file mode 100644 index 00000000000..d5e22b8cb56 --- /dev/null +++ b/spec/support/shared_examples/requests/api/merge_requests_list.rb @@ -0,0 +1,280 @@ +shared_examples 'merge requests list' do + context 'when unauthenticated' do + it 'returns merge requests for public projects' do + get api(endpoint_path) + + expect(response).to have_gitlab_http_status(200) + expect(json_response).to be_an Array + end + end + + context 'when authenticated' do + it 'avoids N+1 queries' do + control = ActiveRecord::QueryRecorder.new do + get api(endpoint_path, user) + end + + create(:merge_request, state: 'closed', milestone: milestone1, author: user, assignee: user, source_project: project, target_project: project, title: 'Test', created_at: base_time) + + create(:merge_request, milestone: milestone1, author: user, assignee: user, source_project: project, target_project: project, title: 'Test', created_at: base_time) + + expect do + get api(endpoint_path, user) + end.not_to exceed_query_limit(control) + end + + it 'returns an array of all merge_requests' do + get api(endpoint_path, user) + + expect(response).to have_gitlab_http_status(200) + expect(response).to include_pagination_headers + expect(json_response).to be_an Array + expect(json_response.length).to eq(3) + expect(json_response.last['title']).to eq(merge_request.title) + expect(json_response.last).to have_key('web_url') + expect(json_response.last['sha']).to eq(merge_request.diff_head_sha) + expect(json_response.last['merge_commit_sha']).to be_nil + expect(json_response.last['merge_commit_sha']).to eq(merge_request.merge_commit_sha) + expect(json_response.last['downvotes']).to eq(1) + expect(json_response.last['upvotes']).to eq(1) + expect(json_response.last['labels']).to eq([label2.title, label.title]) + expect(json_response.first['title']).to eq(merge_request_merged.title) + expect(json_response.first['sha']).to eq(merge_request_merged.diff_head_sha) + expect(json_response.first['merge_commit_sha']).not_to be_nil + expect(json_response.first['merge_commit_sha']).to eq(merge_request_merged.merge_commit_sha) + end + + it 'returns an array of all merge_requests using simple mode' do + path = endpoint_path + '?view=simple' + + get api(path, user) + + expect(response).to have_gitlab_http_status(200) + expect(response).to include_pagination_headers + expect(json_response.last.keys).to match_array(%w(id iid title web_url created_at description project_id state updated_at)) + expect(json_response).to be_an Array + expect(json_response.length).to eq(3) + expect(json_response.last['iid']).to eq(merge_request.iid) + expect(json_response.last['title']).to eq(merge_request.title) + expect(json_response.last).to have_key('web_url') + expect(json_response.first['iid']).to eq(merge_request_merged.iid) + expect(json_response.first['title']).to eq(merge_request_merged.title) + expect(json_response.first).to have_key('web_url') + end + + it 'returns an array of all merge_requests' do + path = endpoint_path + '?state' + + get api(path, user) + + expect(response).to have_gitlab_http_status(200) + expect(response).to include_pagination_headers + expect(json_response).to be_an Array + expect(json_response.length).to eq(3) + expect(json_response.last['title']).to eq(merge_request.title) + end + + it 'returns an array of open merge_requests' do + path = endpoint_path + '?state=opened' + + get api(path, user) + + expect(response).to have_gitlab_http_status(200) + expect(response).to include_pagination_headers + expect(json_response).to be_an Array + expect(json_response.length).to eq(1) + expect(json_response.last['title']).to eq(merge_request.title) + end + + it 'returns an array of closed merge_requests' do + path = endpoint_path + '?state=closed' + + get api(path, user) + + expect(response).to have_gitlab_http_status(200) + expect(response).to include_pagination_headers + expect(json_response).to be_an Array + expect(json_response.length).to eq(1) + expect(json_response.first['title']).to eq(merge_request_closed.title) + end + + it 'returns an array of merged merge_requests' do + path = endpoint_path + '?state=merged' + + get api(path, user) + + expect(response).to have_gitlab_http_status(200) + expect(response).to include_pagination_headers + expect(json_response).to be_an Array + expect(json_response.length).to eq(1) + expect(json_response.first['title']).to eq(merge_request_merged.title) + end + + it 'matches V4 response schema' do + get api(endpoint_path, user) + + expect(response).to have_gitlab_http_status(200) + expect(response).to match_response_schema('public_api/v4/merge_requests') + end + + it 'returns an empty array if no issue matches milestone' do + get api(endpoint_path, user), milestone: '1.0.0' + + expect(response).to have_gitlab_http_status(200) + expect(json_response).to be_an Array + expect(json_response.length).to eq(0) + end + + it 'returns an empty array if milestone does not exist' do + get api(endpoint_path, user), milestone: 'foo' + + expect(response).to have_gitlab_http_status(200) + expect(json_response).to be_an Array + expect(json_response.length).to eq(0) + end + + it 'returns an array of merge requests in given milestone' do + get api(endpoint_path, user), milestone: '0.9' + + expect(json_response.first['title']).to eq merge_request_closed.title + expect(json_response.first['id']).to eq merge_request_closed.id + end + + it 'returns an array of merge requests matching state in milestone' do + get api(endpoint_path, user), milestone: '0.9', state: 'closed' + + expect(response).to have_gitlab_http_status(200) + expect(json_response).to be_an Array + expect(json_response.length).to eq(1) + expect(json_response.first['id']).to eq(merge_request_closed.id) + end + + it 'returns an array of labeled merge requests' do + path = endpoint_path + "?labels=#{label.title}" + + get api(path, user) + + expect(response).to have_gitlab_http_status(200) + expect(json_response).to be_an Array + expect(json_response.length).to eq(1) + expect(json_response.first['labels']).to eq([label2.title, label.title]) + end + + it 'returns an array of labeled merge requests where all labels match' do + path = endpoint_path + "?labels=#{label.title},foo,bar" + + get api(path, user) + + expect(response).to have_gitlab_http_status(200) + expect(json_response).to be_an Array + expect(json_response.length).to eq(0) + end + + it 'returns an empty array if no merge request matches labels' do + path = endpoint_path + '?labels=foo,bar' + + get api(path, user) + + expect(response).to have_gitlab_http_status(200) + expect(json_response).to be_an Array + expect(json_response.length).to eq(0) + end + + it 'returns an array of labeled merge requests that are merged for a milestone' do + bug_label = create(:label, title: 'bug', color: '#FFAABB', project: project) + + mr1 = create(:merge_request, state: 'merged', source_project: project, target_project: project, milestone: milestone) + mr2 = create(:merge_request, state: 'merged', source_project: project, target_project: project, milestone: milestone1) + mr3 = create(:merge_request, state: 'closed', source_project: project, target_project: project, milestone: milestone1) + _mr = create(:merge_request, state: 'merged', source_project: project, target_project: project, milestone: milestone1) + + create(:label_link, label: bug_label, target: mr1) + create(:label_link, label: bug_label, target: mr2) + create(:label_link, label: bug_label, target: mr3) + + path = endpoint_path + "?labels=#{bug_label.title}&milestone=#{milestone1.title}&state=merged" + + get api(path, user) + + expect(response).to have_gitlab_http_status(200) + expect(json_response).to be_an Array + expect(json_response.length).to eq(1) + expect(json_response.first['id']).to eq(mr2.id) + end + + context 'with ordering' do + before do + @mr_later = mr_with_later_created_and_updated_at_time + @mr_earlier = mr_with_earlier_created_and_updated_at_time + end + + it 'returns an array of merge_requests in ascending order' do + path = endpoint_path + '?sort=asc' + + get api(path, user) + + expect(response).to have_gitlab_http_status(200) + expect(response).to include_pagination_headers + expect(json_response).to be_an Array + expect(json_response.length).to eq(3) + response_dates = json_response.map { |merge_request| merge_request['created_at'] } + expect(response_dates).to eq(response_dates.sort) + end + + it 'returns an array of merge_requests in descending order' do + path = endpoint_path + '?sort=desc' + + get api(path, user) + + expect(response).to have_gitlab_http_status(200) + expect(response).to include_pagination_headers + expect(json_response).to be_an Array + expect(json_response.length).to eq(3) + response_dates = json_response.map { |merge_request| merge_request['created_at'] } + expect(response_dates).to eq(response_dates.sort.reverse) + end + + it 'returns an array of merge_requests ordered by updated_at' do + path = endpoint_path + '?order_by=updated_at' + + get api(path, user) + + expect(response).to have_gitlab_http_status(200) + expect(response).to include_pagination_headers + expect(json_response).to be_an Array + expect(json_response.length).to eq(3) + response_dates = json_response.map { |merge_request| merge_request['updated_at'] } + expect(response_dates).to eq(response_dates.sort.reverse) + end + + it 'returns an array of merge_requests ordered by created_at' do + path = endpoint_path + '?order_by=created_at&sort=asc' + + get api(path, user) + + expect(response).to have_gitlab_http_status(200) + expect(response).to include_pagination_headers + expect(json_response).to be_an Array + expect(json_response.length).to eq(3) + response_dates = json_response.map { |merge_request| merge_request['created_at'] } + expect(response_dates).to eq(response_dates.sort) + end + end + + context 'source_branch param' do + it 'returns merge requests with the given source branch' do + get api(endpoint_path, user), source_branch: merge_request_closed.source_branch, state: 'all' + + expect_response_contain_exactly(merge_request_closed, merge_request_merged) + end + end + + context 'target_branch param' do + it 'returns merge requests with the given target branch' do + get api(endpoint_path, user), target_branch: merge_request_closed.target_branch, state: 'all' + + expect_response_contain_exactly(merge_request_closed, merge_request_merged) + end + end + end +end diff --git a/spec/tasks/gitlab/storage_rake_spec.rb b/spec/tasks/gitlab/storage_rake_spec.rb index f59792c3d36..35e451b2f9a 100644 --- a/spec/tasks/gitlab/storage_rake_spec.rb +++ b/spec/tasks/gitlab/storage_rake_spec.rb @@ -1,13 +1,49 @@ require 'rake_helper' -describe 'gitlab:storage rake tasks' do +describe 'gitlab:storage:*' do before do Rake.application.rake_require 'tasks/gitlab/storage' stub_warn_user_is_not_gitlab end - describe 'migrate_to_hashed rake task' do + shared_examples "rake listing entities" do |entity_name, storage_type| + context 'limiting to 2' do + before do + stub_env('LIMIT' => 2) + end + + it "lists 2 out of 3 #{storage_type.downcase} #{entity_name}" do + create_collection + + expect { run_rake_task(task) }.to output(/Found 3 #{entity_name} using #{storage_type} Storage.*Displaying first 2 #{entity_name}/m).to_stdout + end + end + + context "without any #{storage_type.downcase} #{entity_name.singularize}" do + it 'displays message for empty results' do + expect { run_rake_task(task) }.to output(/Found 0 #{entity_name} using #{storage_type} Storage/).to_stdout + end + end + end + + shared_examples "rake entities summary" do |entity_name, storage_type| + context "with existing 3 #{storage_type.downcase} #{entity_name}" do + it "reports 3 #{storage_type.downcase} #{entity_name}" do + create_collection + + expect { run_rake_task(task) }.to output(/Found 3 #{entity_name} using #{storage_type} Storage/).to_stdout + end + end + + context "without any #{storage_type.downcase} #{entity_name.singularize}" do + it 'displays message for empty results' do + expect { run_rake_task(task) }.to output(/Found 0 #{entity_name} using #{storage_type} Storage/).to_stdout + end + end + end + + describe 'gitlab:storage:migrate_to_hashed' do context '0 legacy projects' do it 'does nothing' do expect(StorageMigratorWorker).not_to receive(:perform_async) @@ -16,8 +52,8 @@ describe 'gitlab:storage rake tasks' do end end - context '5 legacy projects' do - let(:projects) { create_list(:project, 5, storage_version: 0) } + context '3 legacy projects' do + let(:projects) { create_list(:project, 3, storage_version: 0) } context 'in batches of 1' do before do @@ -49,4 +85,64 @@ describe 'gitlab:storage rake tasks' do end end end + + describe 'gitlab:storage:legacy_projects' do + it_behaves_like 'rake entities summary', 'projects', 'Legacy' do + let(:task) { 'gitlab:storage:legacy_projects' } + let(:create_collection) { create_list(:project, 3, storage_version: 0) } + end + end + + describe 'gitlab:storage:list_legacy_projects' do + it_behaves_like 'rake listing entities', 'projects', 'Legacy' do + let(:task) { 'gitlab:storage:list_legacy_projects' } + let(:create_collection) { create_list(:project, 3, storage_version: 0) } + end + end + + describe 'gitlab:storage:hashed_projects' do + it_behaves_like 'rake entities summary', 'projects', 'Hashed' do + let(:task) { 'gitlab:storage:hashed_projects' } + let(:create_collection) { create_list(:project, 3, storage_version: 1) } + end + end + + describe 'gitlab:storage:list_hashed_projects' do + it_behaves_like 'rake listing entities', 'projects', 'Hashed' do + let(:task) { 'gitlab:storage:list_hashed_projects' } + let(:create_collection) { create_list(:project, 3, storage_version: 1) } + end + end + + describe 'gitlab:storage:legacy_attachments' do + it_behaves_like 'rake entities summary', 'attachments', 'Legacy' do + let(:task) { 'gitlab:storage:legacy_attachments' } + let(:project) { create(:project, storage_version: 1) } + let(:create_collection) { create_list(:upload, 3, model: project) } + end + end + + describe 'gitlab:storage:list_legacy_attachments' do + it_behaves_like 'rake listing entities', 'attachments', 'Legacy' do + let(:task) { 'gitlab:storage:list_legacy_attachments' } + let(:project) { create(:project, storage_version: 1) } + let(:create_collection) { create_list(:upload, 3, model: project) } + end + end + + describe 'gitlab:storage:hashed_attachments' do + it_behaves_like 'rake entities summary', 'attachments', 'Hashed' do + let(:task) { 'gitlab:storage:hashed_attachments' } + let(:project) { create(:project, storage_version: 2) } + let(:create_collection) { create_list(:upload, 3, model: project) } + end + end + + describe 'gitlab:storage:list_hashed_attachments' do + it_behaves_like 'rake listing entities', 'attachments', 'Hashed' do + let(:task) { 'gitlab:storage:list_hashed_attachments' } + let(:project) { create(:project, storage_version: 2) } + let(:create_collection) { create_list(:upload, 3, model: project) } + end + end end diff --git a/spec/tasks/tokens_spec.rb b/spec/tasks/tokens_spec.rb index 51f7a536cbb..555a58e9aa1 100644 --- a/spec/tasks/tokens_spec.rb +++ b/spec/tasks/tokens_spec.rb @@ -13,9 +13,9 @@ describe 'tokens rake tasks' do end end - describe 'reset_all_rss task' do + describe 'reset_all_feed task' do it 'invokes create_hooks task' do - expect { run_rake_task('tokens:reset_all_rss') }.to change { user.reload.rss_token } + expect { run_rake_task('tokens:reset_all_feed') }.to change { user.reload.feed_token } end end end diff --git a/spec/uploaders/object_storage_spec.rb b/spec/uploaders/object_storage_spec.rb index 4165a005063..2dd0925a8e6 100644 --- a/spec/uploaders/object_storage_spec.rb +++ b/spec/uploaders/object_storage_spec.rb @@ -382,6 +382,8 @@ describe ObjectStorage do is_expected.to have_key(:RemoteObject) expect(subject[:RemoteObject]).to have_key(:ID) + expect(subject[:RemoteObject]).to include(Timeout: a_kind_of(Integer)) + expect(subject[:RemoteObject][:Timeout]).to be(ObjectStorage::DIRECT_UPLOAD_TIMEOUT) expect(subject[:RemoteObject]).to have_key(:GetURL) expect(subject[:RemoteObject]).to have_key(:DeleteURL) expect(subject[:RemoteObject]).to have_key(:StoreURL) diff --git a/spec/workers/concerns/waitable_worker_spec.rb b/spec/workers/concerns/waitable_worker_spec.rb index 54ab07981a4..199825b5097 100644 --- a/spec/workers/concerns/waitable_worker_spec.rb +++ b/spec/workers/concerns/waitable_worker_spec.rb @@ -7,9 +7,7 @@ describe WaitableWorker do 'Gitlab::Foo::Bar::DummyWorker' end - class << self - cattr_accessor(:counter) { 0 } - end + cattr_accessor(:counter) { 0 } include ApplicationWorker prepend WaitableWorker |