diff options
Diffstat (limited to 'spec/controllers')
54 files changed, 2044 insertions, 820 deletions
diff --git a/spec/controllers/abuse_reports_controller_spec.rb b/spec/controllers/abuse_reports_controller_spec.rb index 80a418feb3e..ada011e7595 100644 --- a/spec/controllers/abuse_reports_controller_spec.rb +++ b/spec/controllers/abuse_reports_controller_spec.rb @@ -13,6 +13,31 @@ describe AbuseReportsController do sign_in(reporter) end + describe 'GET new' do + context 'when the user has already been deleted' do + it 'redirects the reporter to root_path' do + user_id = user.id + user.destroy + + get :new, { user_id: user_id } + + expect(response).to redirect_to root_path + expect(flash[:alert]).to eq('Cannot create the abuse report. The user has been deleted.') + end + end + + context 'when the user has already been blocked' do + it 'redirects the reporter to the user\'s profile' do + user.block + + get :new, { user_id: user.id } + + expect(response).to redirect_to user + expect(flash[:alert]).to eq('Cannot create the abuse report. This user has been blocked.') + end + end + end + describe 'POST create' do context 'with valid attributes' do it 'saves the abuse report' do diff --git a/spec/controllers/admin/applications_controller_spec.rb b/spec/controllers/admin/applications_controller_spec.rb index e311b8a63b2..7bd6c0e6117 100644 --- a/spec/controllers/admin/applications_controller_spec.rb +++ b/spec/controllers/admin/applications_controller_spec.rb @@ -28,13 +28,16 @@ describe Admin::ApplicationsController do describe 'POST #create' do it 'creates the application' do + create_params = attributes_for(:application, trusted: true) + expect do - post :create, doorkeeper_application: attributes_for(:application) + post :create, doorkeeper_application: create_params end.to change { Doorkeeper::Application.count }.by(1) application = Doorkeeper::Application.last expect(response).to redirect_to(admin_application_path(application)) + expect(application).to have_attributes(create_params.except(:uid, :owner_type)) end it 'renders the application form on errors' do @@ -49,10 +52,12 @@ describe Admin::ApplicationsController do describe 'PATCH #update' do it 'updates the application' do - patch :update, id: application.id, doorkeeper_application: { redirect_uri: 'http://example.com/' } + patch :update, id: application.id, doorkeeper_application: { redirect_uri: 'http://example.com/', trusted: true } + + application.reload expect(response).to redirect_to(admin_application_path(application)) - expect(application.reload.redirect_uri).to eq 'http://example.com/' + expect(application).to have_attributes(redirect_uri: 'http://example.com/', trusted: true) end it 'renders the application form on errors' do diff --git a/spec/controllers/admin/dashboard_controller_spec.rb b/spec/controllers/admin/dashboard_controller_spec.rb new file mode 100644 index 00000000000..6eb9f7867d5 --- /dev/null +++ b/spec/controllers/admin/dashboard_controller_spec.rb @@ -0,0 +1,21 @@ +require 'spec_helper' + +describe Admin::DashboardController do + describe '#index' do + context 'with pending_delete projects' do + render_views + + it 'does not retrieve projects that are pending deletion' do + sign_in(create(:admin)) + + project = create(:project) + pending_delete_project = create(:project, pending_delete: true) + + get :index + + expect(response.body).to match(project.name) + expect(response.body).not_to match(pending_delete_project.name) + end + end + end +end diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb index 3f99e2ff596..1641bddea11 100644 --- a/spec/controllers/application_controller_spec.rb +++ b/spec/controllers/application_controller_spec.rb @@ -30,6 +30,15 @@ describe ApplicationController do expect(controller).not_to receive(:redirect_to) controller.send(:check_password_expiration) end + + it 'does not redirect if the user is over their password expiry but sign-in is disabled' do + stub_application_setting(password_authentication_enabled: false) + user.password_expires_at = Time.new(2002) + allow(controller).to receive(:current_user).and_return(user) + expect(controller).not_to receive(:redirect_to) + + controller.send(:check_password_expiration) + end end describe "#authenticate_user_from_token!" do @@ -99,6 +108,36 @@ describe ApplicationController do end end + describe 'response format' do + controller(described_class) do + def index + respond_to do |format| + format.json do + head :ok + end + end + end + end + + context 'when format is handled' do + let(:requested_format) { :json } + + it 'returns 200 response' do + get :index, private_token: user.private_token, format: requested_format + + expect(response).to have_http_status 200 + end + end + + context 'when format is not handled' do + it 'returns 404 response' do + get :index, private_token: user.private_token + + expect(response).to have_http_status 404 + end + end + end + describe '#authenticate_user_from_rss_token' do describe "authenticating a user from an RSS token" do controller(described_class) do diff --git a/spec/controllers/autocomplete_controller_spec.rb b/spec/controllers/autocomplete_controller_spec.rb index b40f647644d..58486f33229 100644 --- a/spec/controllers/autocomplete_controller_spec.rb +++ b/spec/controllers/autocomplete_controller_spec.rb @@ -97,6 +97,21 @@ describe AutocompleteController do it { expect(body.size).to eq User.count } end + context 'user order' do + it 'shows exact matches first' do + reported_user = create(:user, username: 'reported_user', name: 'Doug') + user = create(:user, username: 'user', name: 'User') + user1 = create(:user, username: 'user1', name: 'Ian') + + sign_in(user) + get(:users, search: 'user') + + response_usernames = JSON.parse(response.body).map { |user| user['username'] } + + expect(response_usernames.take(3)).to match_array([user.username, reported_user.username, user1.username]) + end + end + context 'limited users per page' do let(:per_page) { 2 } diff --git a/spec/controllers/dashboard/labels_controller_spec.rb b/spec/controllers/dashboard/labels_controller_spec.rb new file mode 100644 index 00000000000..2b63933008f --- /dev/null +++ b/spec/controllers/dashboard/labels_controller_spec.rb @@ -0,0 +1,25 @@ +require 'spec_helper' + +describe Dashboard::LabelsController do + let(:project) { create(:empty_project) } + let(:user) { create(:user) } + let!(:label) { create(:label, project: project) } + + before do + sign_in(user) + project.add_reporter(user) + end + + describe "#index" do + let!(:unrelated_label) { create(:label, project: create(:empty_project, :public)) } + + it 'returns global labels for projects the user has a relationship with' do + get :index, format: :json + + expect(json_response).to be_kind_of(Array) + expect(json_response.size).to eq(1) + expect(json_response[0]["id"]).to be_nil + expect(json_response[0]["title"]).to eq(label.title) + end + end +end diff --git a/spec/controllers/dashboard/todos_controller_spec.rb b/spec/controllers/dashboard/todos_controller_spec.rb index 085f3fd8543..4a48621abe1 100644 --- a/spec/controllers/dashboard/todos_controller_spec.rb +++ b/spec/controllers/dashboard/todos_controller_spec.rb @@ -12,6 +12,36 @@ describe Dashboard::TodosController do end describe 'GET #index' do + context 'project authorization' do + it 'renders 404 when user does not have read access on given project' do + unauthorized_project = create(:empty_project, :private) + + get :index, project_id: unauthorized_project.id + + expect(response).to have_http_status(404) + end + + it 'renders 404 when given project does not exists' do + get :index, project_id: 999 + + expect(response).to have_http_status(404) + end + + it 'renders 200 when filtering for "any project" todos' do + get :index, project_id: '' + + expect(response).to have_http_status(200) + end + + it 'renders 200 when user has access on given project' do + authorized_project = create(:empty_project, :public) + + get :index, project_id: authorized_project.id + + expect(response).to have_http_status(200) + end + end + context 'when using pagination' do let(:last_page) { user.todos.page.total_pages } let!(:issues) { create_list(:issue, 2, project: project, assignees: [user]) } diff --git a/spec/controllers/groups/milestones_controller_spec.rb b/spec/controllers/groups/milestones_controller_spec.rb index f3263bc177d..aad67dd0164 100644 --- a/spec/controllers/groups/milestones_controller_spec.rb +++ b/spec/controllers/groups/milestones_controller_spec.rb @@ -2,8 +2,8 @@ require 'spec_helper' describe Groups::MilestonesController do let(:group) { create(:group) } - let(:project) { create(:empty_project, group: group) } - let(:project2) { create(:empty_project, group: group) } + let!(:project) { create(:empty_project, group: group) } + let!(:project2) { create(:empty_project, group: group) } let(:user) { create(:user) } let(:title) { '肯定不是中文的问题' } let(:milestone) do @@ -17,28 +17,127 @@ describe Groups::MilestonesController do end let(:milestone_path) { group_milestone_path(group, milestone.safe_title, title: milestone.title) } + let(:milestone_params) do + { + title: title, + start_date: Date.today, + due_date: 1.month.from_now.to_date + } + end + before do sign_in(user) group.add_owner(user) project.team << [user, :master] end + describe '#index' do + it 'shows group milestones page' do + get :index, group_id: group.to_param + + expect(response).to have_http_status(200) + end + + context 'as JSON' do + let!(:milestone) { create(:milestone, group: group, title: 'group milestone') } + let!(:legacy_milestone1) { create(:milestone, project: project, title: 'legacy') } + let!(:legacy_milestone2) { create(:milestone, project: project2, title: 'legacy') } + + it 'lists legacy group milestones and group milestones' do + get :index, group_id: group.to_param, format: :json + + milestones = JSON.parse(response.body) + + expect(milestones.count).to eq(2) + expect(milestones.first["title"]).to eq("group milestone") + expect(milestones.second["title"]).to eq("legacy") + expect(response).to have_http_status(200) + expect(response.content_type).to eq 'application/json' + end + end + end + + describe '#show' do + let(:milestone1) { create(:milestone, project: project, title: 'legacy') } + let(:milestone2) { create(:milestone, project: project, title: 'legacy') } + let(:group_milestone) { create(:milestone, group: group) } + + context 'when there is a title parameter' do + it 'searchs for a legacy group milestone' do + expect(GlobalMilestone).to receive(:build) + expect(Milestone).not_to receive(:find_by_iid) + + get :show, group_id: group.to_param, id: title, title: milestone1.safe_title + end + end + + context 'when there is not a title parameter' do + it 'searchs for a group milestone' do + expect(GlobalMilestone).not_to receive(:build) + expect(Milestone).to receive(:find_by_iid) + + get :show, group_id: group.to_param, id: group_milestone.id + end + end + end + it_behaves_like 'milestone tabs' describe "#create" do it "creates group milestone with Chinese title" do post :create, group_id: group.to_param, - milestone: { project_ids: [project.id, project2.id], title: title } + milestone: milestone_params + + milestone = Milestone.find_by_title(title) + + expect(response).to redirect_to(group_milestone_path(group, milestone.iid)) + expect(milestone.group_id).to eq(group.id) + expect(milestone.due_date).to eq(milestone_params[:due_date]) + expect(milestone.start_date).to eq(milestone_params[:start_date]) + end + end + + describe "#update" do + let(:milestone) { create(:milestone, group: group) } + + it "updates group milestone" do + milestone_params[:title] = "title changed" + + put :update, + id: milestone.iid, + group_id: group.to_param, + milestone: milestone_params - expect(response).to redirect_to(group_milestone_path(group, title.to_slug.to_s, title: title)) - expect(Milestone.where(title: title).count).to eq(2) + milestone.reload + expect(response).to redirect_to(group_milestone_path(group, milestone.iid)) + expect(milestone.title).to eq("title changed") end - it "redirects to new when there are no project ids" do - post :create, group_id: group.to_param, milestone: { title: title, project_ids: [""] } - expect(response).to render_template :new - expect(assigns(:milestone).errors).not_to be_nil + context "legacy group milestones" do + let!(:milestone1) { create(:milestone, project: project, title: 'legacy milestone', description: "old description") } + let!(:milestone2) { create(:milestone, project: project2, title: 'legacy milestone', description: "old description") } + + it "updates only group milestones state" do + milestone_params[:title] = "title changed" + milestone_params[:description] = "description changed" + milestone_params[:state_event] = "close" + + put :update, + id: milestone1.title.to_slug.to_s, + group_id: group.to_param, + milestone: milestone_params, + title: milestone1.title + + expect(response).to redirect_to(group_milestone_path(group, milestone1.safe_title, title: milestone1.title)) + + [milestone1, milestone2].each do |milestone| + milestone.reload + expect(milestone.title).to eq("legacy milestone") + expect(milestone.description).to eq("old description") + expect(milestone.state).to eq("closed") + end + end end end @@ -141,7 +240,7 @@ describe Groups::MilestonesController do it 'does not 404' do post :create, group_id: group.to_param, - milestone: { project_ids: [project.id, project2.id], title: title } + milestone: { title: title } expect(response).not_to have_http_status(404) end @@ -149,7 +248,7 @@ describe Groups::MilestonesController do it 'does not redirect to the correct casing' do post :create, group_id: group.to_param, - milestone: { project_ids: [project.id, project2.id], title: title } + milestone: { title: title } expect(response).not_to have_http_status(301) end @@ -161,7 +260,7 @@ describe Groups::MilestonesController do it 'returns not found' do post :create, group_id: redirect_route.path, - milestone: { project_ids: [project.id, project2.id], title: title } + milestone: { title: title } expect(response).to have_http_status(404) end diff --git a/spec/controllers/groups/settings/ci_cd_controller_spec.rb b/spec/controllers/groups/settings/ci_cd_controller_spec.rb new file mode 100644 index 00000000000..2e0efb57c74 --- /dev/null +++ b/spec/controllers/groups/settings/ci_cd_controller_spec.rb @@ -0,0 +1,20 @@ +require 'spec_helper' + +describe Groups::Settings::CiCdController do + let(:group) { create(:group) } + let(:user) { create(:user) } + + before do + group.add_master(user) + sign_in(user) + end + + describe 'GET #show' do + it 'renders show with 200 status code' do + get :show, group_id: group + + expect(response).to have_http_status(200) + expect(response).to render_template(:show) + end + end +end diff --git a/spec/controllers/groups/variables_controller_spec.rb b/spec/controllers/groups/variables_controller_spec.rb new file mode 100644 index 00000000000..02f2fa46047 --- /dev/null +++ b/spec/controllers/groups/variables_controller_spec.rb @@ -0,0 +1,56 @@ +require 'spec_helper' + +describe Groups::VariablesController do + let(:group) { create(:group) } + let(:user) { create(:user) } + + before do + sign_in(user) + group.add_master(user) + end + + describe 'POST #create' do + context 'variable is valid' do + it 'shows a success flash message' do + post :create, group_id: group, variable: { key: "one", value: "two" } + + expect(flash[:notice]).to include 'Variable was successfully created.' + expect(response).to redirect_to(group_settings_ci_cd_path(group)) + end + end + + context 'variable is invalid' do + it 'renders show' do + post :create, group_id: group, variable: { key: "..one", value: "two" } + + expect(response).to render_template("groups/variables/show") + end + end + end + + describe 'POST #update' do + let(:variable) { create(:ci_group_variable) } + + context 'updating a variable with valid characters' do + before do + group.variables << variable + end + + it 'shows a success flash message' do + post :update, group_id: group, + id: variable.id, variable: { key: variable.key, value: 'two' } + + expect(flash[:notice]).to include 'Variable was successfully updated.' + expect(response).to redirect_to(group_variables_path(group)) + end + + it 'renders the action #show if the variable key is invalid' do + post :update, group_id: group, + id: variable.id, variable: { key: '?', value: variable.value } + + expect(response).to have_http_status(200) + expect(response).to render_template :show + end + end + end +end diff --git a/spec/controllers/health_check_controller_spec.rb b/spec/controllers/health_check_controller_spec.rb index 58c16cc57e6..03da6287774 100644 --- a/spec/controllers/health_check_controller_spec.rb +++ b/spec/controllers/health_check_controller_spec.rb @@ -3,52 +3,79 @@ require 'spec_helper' describe HealthCheckController do include StubENV - let(:token) { current_application_settings.health_check_access_token } let(:json_response) { JSON.parse(response.body) } let(:xml_response) { Hash.from_xml(response.body)['hash'] } + let(:token) { current_application_settings.health_check_access_token } + let(:whitelisted_ip) { '127.0.0.1' } + let(:not_whitelisted_ip) { '127.0.0.2' } before do + allow(Settings.monitoring).to receive(:ip_whitelist).and_return([whitelisted_ip]) stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false') end describe 'GET #index' do - context 'when services are up but NO access token' do + context 'when services are up but accessed from outside whitelisted ips' do + before do + allow(Gitlab::RequestContext).to receive(:client_ip).and_return(not_whitelisted_ip) + end + it 'returns a not found page' do get :index + expect(response).to be_not_found end + + context 'when services are accessed with token' do + it 'supports passing the token in the header' do + request.headers['TOKEN'] = token + + get :index + + expect(response).to be_success + expect(response.content_type).to eq 'text/plain' + end + + it 'supports passing the token in query params' do + get :index, token: token + + expect(response).to be_success + expect(response.content_type).to eq 'text/plain' + end + end end - context 'when services are up and an access token is provided' do - it 'supports passing the token in the header' do - request.headers['TOKEN'] = token - get :index - expect(response).to be_success - expect(response.content_type).to eq 'text/plain' + context 'when services are up and accessed from whitelisted ips' do + before do + allow(Gitlab::RequestContext).to receive(:client_ip).and_return(whitelisted_ip) end - it 'supports successful plaintest response' do - get :index, token: token + it 'supports successful plaintext response' do + get :index + expect(response).to be_success expect(response.content_type).to eq 'text/plain' end it 'supports successful json response' do - get :index, token: token, format: :json + get :index, format: :json + expect(response).to be_success expect(response.content_type).to eq 'application/json' expect(json_response['healthy']).to be true end it 'supports successful xml response' do - get :index, token: token, format: :xml + get :index, format: :xml + expect(response).to be_success expect(response.content_type).to eq 'application/xml' expect(xml_response['healthy']).to be true end it 'supports successful responses for specific checks' do - get :index, token: token, checks: 'email', format: :json + get :index, checks: 'email', format: :json + expect(response).to be_success expect(response.content_type).to eq 'application/json' expect(json_response['healthy']).to be true @@ -58,33 +85,29 @@ describe HealthCheckController do context 'when a service is down but NO access token' do it 'returns a not found page' do get :index + expect(response).to be_not_found end end - context 'when a service is down and an access token is provided' do + context 'when a service is down and an endpoint is accessed from whitelisted ip' do before do allow(HealthCheck::Utils).to receive(:process_checks).with(['standard']).and_return('The server is on fire') allow(HealthCheck::Utils).to receive(:process_checks).with(['email']).and_return('Email is on fire') + allow(Gitlab::RequestContext).to receive(:client_ip).and_return(whitelisted_ip) end - it 'supports passing the token in the header' do - request.headers['TOKEN'] = token + it 'supports failure plaintext response' do get :index - expect(response).to have_http_status(500) - expect(response.content_type).to eq 'text/plain' - expect(response.body).to include('The server is on fire') - end - it 'supports failure plaintest response' do - get :index, token: token expect(response).to have_http_status(500) expect(response.content_type).to eq 'text/plain' expect(response.body).to include('The server is on fire') end it 'supports failure json response' do - get :index, token: token, format: :json + get :index, format: :json + expect(response).to have_http_status(500) expect(response.content_type).to eq 'application/json' expect(json_response['healthy']).to be false @@ -92,7 +115,8 @@ describe HealthCheckController do end it 'supports failure xml response' do - get :index, token: token, format: :xml + get :index, format: :xml + expect(response).to have_http_status(500) expect(response.content_type).to eq 'application/xml' expect(xml_response['healthy']).to be false @@ -100,7 +124,8 @@ describe HealthCheckController do end it 'supports failure responses for specific checks' do - get :index, token: token, checks: 'email', format: :json + get :index, checks: 'email', format: :json + expect(response).to have_http_status(500) expect(response.content_type).to eq 'application/json' expect(json_response['healthy']).to be false diff --git a/spec/controllers/health_controller_spec.rb b/spec/controllers/health_controller_spec.rb index e7c19b47a6a..cc389e554ad 100644 --- a/spec/controllers/health_controller_spec.rb +++ b/spec/controllers/health_controller_spec.rb @@ -3,55 +3,120 @@ require 'spec_helper' describe HealthController do include StubENV - let(:token) { current_application_settings.health_check_access_token } let(:json_response) { JSON.parse(response.body) } + let(:token) { current_application_settings.health_check_access_token } + let(:whitelisted_ip) { '127.0.0.1' } + let(:not_whitelisted_ip) { '127.0.0.2' } before do + allow(Settings.monitoring).to receive(:ip_whitelist).and_return([whitelisted_ip]) stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false') end describe '#readiness' do - context 'authorization token provided' do - before do - request.headers['TOKEN'] = token - end + shared_context 'endpoint responding with readiness data' do + let(:request_params) { {} } + + subject { get :readiness, request_params } + + it 'responds with readiness checks data' do + subject - it 'returns proper response' do - get :readiness expect(json_response['db_check']['status']).to eq('ok') - expect(json_response['redis_check']['status']).to eq('ok') + expect(json_response['cache_check']['status']).to eq('ok') + expect(json_response['queues_check']['status']).to eq('ok') + expect(json_response['shared_state_check']['status']).to eq('ok') expect(json_response['fs_shards_check']['status']).to eq('ok') expect(json_response['fs_shards_check']['labels']['shard']).to eq('default') end end - context 'without authorization token' do - it 'returns proper response' do + context 'accessed from whitelisted ip' do + before do + allow(Gitlab::RequestContext).to receive(:client_ip).and_return(whitelisted_ip) + end + + it_behaves_like 'endpoint responding with readiness data' + end + + context 'accessed from not whitelisted ip' do + before do + allow(Gitlab::RequestContext).to receive(:client_ip).and_return(not_whitelisted_ip) + end + + it 'responds with resource not found' do get :readiness + expect(response.status).to eq(404) end + + context 'accessed with valid token' do + context 'token passed in request header' do + before do + request.headers['TOKEN'] = token + end + + it_behaves_like 'endpoint responding with readiness data' + end + end + + context 'token passed as URL param' do + it_behaves_like 'endpoint responding with readiness data' do + let(:request_params) { { token: token } } + end + end end end describe '#liveness' do - context 'authorization token provided' do - before do - request.headers['TOKEN'] = token - end + shared_context 'endpoint responding with liveness data' do + subject { get :liveness } + + it 'responds with liveness checks data' do + subject - it 'returns proper response' do - get :liveness expect(json_response['db_check']['status']).to eq('ok') - expect(json_response['redis_check']['status']).to eq('ok') + expect(json_response['cache_check']['status']).to eq('ok') + expect(json_response['queues_check']['status']).to eq('ok') + expect(json_response['shared_state_check']['status']).to eq('ok') expect(json_response['fs_shards_check']['status']).to eq('ok') end end - context 'without authorization token' do - it 'returns proper response' do + context 'accessed from whitelisted ip' do + before do + allow(Gitlab::RequestContext).to receive(:client_ip).and_return(whitelisted_ip) + end + + it_behaves_like 'endpoint responding with liveness data' + end + + context 'accessed from not whitelisted ip' do + before do + allow(Gitlab::RequestContext).to receive(:client_ip).and_return(not_whitelisted_ip) + end + + it 'responds with resource not found' do get :liveness + expect(response.status).to eq(404) end + + context 'accessed with valid token' do + context 'token passed in request header' do + before do + request.headers['TOKEN'] = token + end + + it_behaves_like 'endpoint responding with liveness data' + end + + context 'token passed as URL param' do + it_behaves_like 'endpoint responding with liveness data' do + subject { get :liveness, token: token } + end + end + end end end end diff --git a/spec/controllers/metrics_controller_spec.rb b/spec/controllers/metrics_controller_spec.rb index 044c9f179ed..7b0976e3e67 100644 --- a/spec/controllers/metrics_controller_spec.rb +++ b/spec/controllers/metrics_controller_spec.rb @@ -3,28 +3,28 @@ require 'spec_helper' describe MetricsController do include StubENV - let(:token) { current_application_settings.health_check_access_token } let(:json_response) { JSON.parse(response.body) } let(:metrics_multiproc_dir) { Dir.mktmpdir } + let(:whitelisted_ip) { '127.0.0.1' } + let(:whitelisted_ip_range) { '10.0.0.0/24' } + let(:ip_in_whitelisted_range) { '10.0.0.1' } + let(:not_whitelisted_ip) { '10.0.1.1' } before do stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false') - stub_env('prometheus_multiproc_dir', metrics_multiproc_dir) + allow(Prometheus::Client.configuration).to receive(:multiprocess_files_dir).and_return(metrics_multiproc_dir) allow(Gitlab::Metrics).to receive(:prometheus_metrics_enabled?).and_return(true) + allow(Settings.monitoring).to receive(:ip_whitelist).and_return([whitelisted_ip, whitelisted_ip_range]) end describe '#index' do - context 'authorization token provided' do - before do - request.headers['TOKEN'] = token - end - + shared_examples_for 'endpoint providing metrics' do it 'returns DB ping metrics' do get :index expect(response.body).to match(/^db_ping_timeout 0$/) expect(response.body).to match(/^db_ping_success 1$/) - expect(response.body).to match(/^db_ping_latency [0-9\.]+$/) + expect(response.body).to match(/^db_ping_latency_seconds [0-9\.]+$/) end it 'returns Redis ping metrics' do @@ -32,17 +32,41 @@ describe MetricsController do expect(response.body).to match(/^redis_ping_timeout 0$/) expect(response.body).to match(/^redis_ping_success 1$/) - expect(response.body).to match(/^redis_ping_latency [0-9\.]+$/) + expect(response.body).to match(/^redis_ping_latency_seconds [0-9\.]+$/) + end + + it 'returns Caching ping metrics' do + get :index + + expect(response.body).to match(/^redis_cache_ping_timeout 0$/) + expect(response.body).to match(/^redis_cache_ping_success 1$/) + expect(response.body).to match(/^redis_cache_ping_latency_seconds [0-9\.]+$/) + end + + it 'returns Queues ping metrics' do + get :index + + expect(response.body).to match(/^redis_queues_ping_timeout 0$/) + expect(response.body).to match(/^redis_queues_ping_success 1$/) + expect(response.body).to match(/^redis_queues_ping_latency_seconds [0-9\.]+$/) + end + + it 'returns SharedState ping metrics' do + get :index + + expect(response.body).to match(/^redis_shared_state_ping_timeout 0$/) + expect(response.body).to match(/^redis_shared_state_ping_success 1$/) + expect(response.body).to match(/^redis_shared_state_ping_latency_seconds [0-9\.]+$/) end it 'returns file system check metrics' do get :index - expect(response.body).to match(/^filesystem_access_latency{shard="default"} [0-9\.]+$/) + expect(response.body).to match(/^filesystem_access_latency_seconds{shard="default"} [0-9\.]+$/) expect(response.body).to match(/^filesystem_accessible{shard="default"} 1$/) - expect(response.body).to match(/^filesystem_write_latency{shard="default"} [0-9\.]+$/) + expect(response.body).to match(/^filesystem_write_latency_seconds{shard="default"} [0-9\.]+$/) expect(response.body).to match(/^filesystem_writable{shard="default"} 1$/) - expect(response.body).to match(/^filesystem_read_latency{shard="default"} [0-9\.]+$/) + expect(response.body).to match(/^filesystem_read_latency_seconds{shard="default"} [0-9\.]+$/) expect(response.body).to match(/^filesystem_readable{shard="default"} 1$/) end @@ -59,7 +83,27 @@ describe MetricsController do end end - context 'without authorization token' do + context 'accessed from whitelisted ip' do + before do + allow(Gitlab::RequestContext).to receive(:client_ip).and_return(whitelisted_ip) + end + + it_behaves_like 'endpoint providing metrics' + end + + context 'accessed from ip in whitelisted range' do + before do + allow(Gitlab::RequestContext).to receive(:client_ip).and_return(ip_in_whitelisted_range) + end + + it_behaves_like 'endpoint providing metrics' + end + + context 'accessed from not whitelisted ip' do + before do + allow(Gitlab::RequestContext).to receive(:client_ip).and_return(not_whitelisted_ip) + end + it 'returns proper response' do get :index diff --git a/spec/controllers/oauth/authorizations_controller_spec.rb b/spec/controllers/oauth/authorizations_controller_spec.rb index d321bfcea9d..ac7f73c6e81 100644 --- a/spec/controllers/oauth/authorizations_controller_spec.rb +++ b/spec/controllers/oauth/authorizations_controller_spec.rb @@ -42,8 +42,8 @@ describe Oauth::AuthorizationsController do end it 'deletes session.user_return_to and redirects when skip authorization' do + doorkeeper.update(trusted: true) request.session['user_return_to'] = 'http://example.com' - allow(controller).to receive(:skip_authorization?).and_return(true) get :new, params diff --git a/spec/controllers/passwords_controller_spec.rb b/spec/controllers/passwords_controller_spec.rb new file mode 100644 index 00000000000..2955d01fad0 --- /dev/null +++ b/spec/controllers/passwords_controller_spec.rb @@ -0,0 +1,29 @@ +require 'spec_helper' + +describe PasswordsController do + describe '#check_password_authentication_available' do + before do + @request.env["devise.mapping"] = Devise.mappings[:user] + end + + context 'when password authentication is disabled' do + it 'prevents a password reset' do + stub_application_setting(password_authentication_enabled: false) + + post :create + + expect(flash[:alert]).to eq 'Password authentication is unavailable.' + end + end + + context 'when reset email belongs to an ldap user' do + let(:user) { create(:omniauth_user, provider: 'ldapmain', email: 'ldapuser@gitlab.com') } + + it 'prevents a password reset' do + post :create, user: { email: user.email } + + expect(flash[:alert]).to eq 'Password authentication is unavailable.' + end + end + end +end diff --git a/spec/controllers/profiles/accounts_controller_spec.rb b/spec/controllers/profiles/accounts_controller_spec.rb index 2f9d18e3a0e..d387aba227b 100644 --- a/spec/controllers/profiles/accounts_controller_spec.rb +++ b/spec/controllers/profiles/accounts_controller_spec.rb @@ -29,7 +29,7 @@ describe Profiles::AccountsController do end end - [:twitter, :facebook, :google_oauth2, :gitlab, :github, :bitbucket, :crowd, :auth0].each do |provider| + [:twitter, :facebook, :google_oauth2, :gitlab, :github, :bitbucket, :crowd, :auth0, :authentiq].each do |provider| describe "#{provider} provider" do let(:user) { create(:omniauth_user, provider: provider.to_s) } diff --git a/spec/controllers/profiles/preferences_controller_spec.rb b/spec/controllers/profiles/preferences_controller_spec.rb index 7b3aa0491c7..a5f544b4f92 100644 --- a/spec/controllers/profiles/preferences_controller_spec.rb +++ b/spec/controllers/profiles/preferences_controller_spec.rb @@ -43,7 +43,8 @@ describe Profiles::PreferencesController do dashboard: 'stars' }.with_indifferent_access - expect(user).to receive(:update_attributes).with(prefs) + expect(user).to receive(:assign_attributes).with(prefs) + expect(user).to receive(:save) go params: prefs end @@ -51,7 +52,7 @@ describe Profiles::PreferencesController do context 'on failed update' do it 'sets the flash' do - expect(user).to receive(:update_attributes).and_return(false) + expect(user).to receive(:save).and_return(false) go diff --git a/spec/controllers/projects/artifacts_controller_spec.rb b/spec/controllers/projects/artifacts_controller_spec.rb index 428bc45b842..d2c613a2423 100644 --- a/spec/controllers/projects/artifacts_controller_spec.rb +++ b/spec/controllers/projects/artifacts_controller_spec.rb @@ -134,10 +134,7 @@ describe Projects::ArtifactsController do context 'found the job and redirect' do shared_examples 'redirect to the job' do it 'redirects' do - path = browse_namespace_project_job_artifacts_path( - project.namespace, - project, - job) + path = browse_project_job_artifacts_path(project, job) expect(response).to redirect_to(path) end @@ -174,11 +171,7 @@ describe Projects::ArtifactsController do end it 'redirects' do - path = file_namespace_project_job_artifacts_path( - project.namespace, - project, - job, - 'README.md') + path = file_project_job_artifacts_path(project, job, 'README.md') expect(response).to redirect_to(path) end diff --git a/spec/controllers/projects/badges_controller_spec.rb b/spec/controllers/projects/badges_controller_spec.rb new file mode 100644 index 00000000000..d68200164e4 --- /dev/null +++ b/spec/controllers/projects/badges_controller_spec.rb @@ -0,0 +1,28 @@ +require 'spec_helper' + +describe Projects::BadgesController do + let(:project) { pipeline.project } + let!(:pipeline) { create(:ci_empty_pipeline) } + let(:user) { create(:user) } + + before do + project.add_master(user) + sign_in(user) + end + + it 'requests the pipeline badge successfully' do + get_badge(:pipeline) + + expect(response).to have_http_status(:ok) + end + + it 'requests the coverage badge successfully' do + get_badge(:coverage) + + expect(response).to have_http_status(:ok) + end + + def get_badge(badge) + get badge, namespace_id: project.namespace.to_param, project_id: project, ref: pipeline.ref, format: :svg + end +end diff --git a/spec/controllers/projects/blob_controller_spec.rb b/spec/controllers/projects/blob_controller_spec.rb index c20cf6a4291..02bbc48dc59 100644 --- a/spec/controllers/projects/blob_controller_spec.rb +++ b/spec/controllers/projects/blob_controller_spec.rb @@ -117,7 +117,7 @@ describe Projects::BlobController do end it 'redirects to blob show' do - expect(response).to redirect_to(namespace_project_blob_path(project.namespace, project, 'master/CHANGELOG')) + expect(response).to redirect_to(project_blob_path(project, 'master/CHANGELOG')) end end @@ -164,7 +164,7 @@ describe Projects::BlobController do end def blob_after_edit_path - namespace_project_blob_path(project.namespace, project, 'master/CHANGELOG') + project_blob_path(project, 'master/CHANGELOG') end before do @@ -186,7 +186,7 @@ describe Projects::BlobController do it 'redirects to MR diff' do put :update, mr_params - after_edit_path = diffs_namespace_project_merge_request_path(project.namespace, project, merge_request) + after_edit_path = diffs_project_merge_request_path(project, merge_request) file_anchor = "##{Digest::SHA1.hexdigest('CHANGELOG')}" expect(response).to redirect_to(after_edit_path + file_anchor) end @@ -223,7 +223,7 @@ describe Projects::BlobController do it 'redirects to blob' do put :update, default_params - expect(response).to redirect_to(namespace_project_blob_path(forked_project.namespace, forked_project, 'master/CHANGELOG')) + expect(response).to redirect_to(project_blob_path(forked_project, 'master/CHANGELOG')) end end @@ -235,8 +235,7 @@ describe Projects::BlobController do put :update, default_params expect(response).to redirect_to( - new_namespace_project_merge_request_path( - forked_project.namespace, + project_new_merge_request_path( forked_project, merge_request: { source_project_id: forked_project.id, diff --git a/spec/controllers/projects/branches_controller_spec.rb b/spec/controllers/projects/branches_controller_spec.rb index 14426b09c73..9cd4e9dbf84 100644 --- a/spec/controllers/projects/branches_controller_spec.rb +++ b/spec/controllers/projects/branches_controller_spec.rb @@ -110,7 +110,7 @@ describe Projects::BranchesController do branch_name: branch, issue_iid: issue.iid - expect(response).to redirect_to namespace_project_tree_path(project.namespace, project, branch) + expect(response).to redirect_to project_tree_path(project, branch) end it 'redirects to autodeploy setup page' do @@ -127,7 +127,7 @@ describe Projects::BranchesController do branch_name: branch, issue_iid: issue.iid - expect(response.location).to include(namespace_project_new_blob_path(project.namespace, project, branch)) + expect(response.location).to include(project_new_blob_path(project, branch)) expect(response).to have_http_status(302) end end @@ -303,7 +303,7 @@ describe Projects::BranchesController do it 'redirects to branches path' do expect(response) - .to redirect_to(namespace_project_branches_path(project.namespace, project)) + .to redirect_to(project_branches_path(project)) end end end @@ -323,7 +323,7 @@ describe Projects::BranchesController do it 'redirects to branches' do destroy_all_merged - expect(response).to redirect_to namespace_project_branches_path(project.namespace, project) + expect(response).to redirect_to project_branches_path(project) end it 'starts worker to delete merged branches' do diff --git a/spec/controllers/projects/commit_controller_spec.rb b/spec/controllers/projects/commit_controller_spec.rb index e10da40eaab..df53863482d 100644 --- a/spec/controllers/projects/commit_controller_spec.rb +++ b/spec/controllers/projects/commit_controller_spec.rb @@ -169,7 +169,7 @@ describe Projects::CommitController do start_branch: 'master', id: commit.id) - expect(response).to redirect_to namespace_project_commits_path(project.namespace, project, 'master') + expect(response).to redirect_to project_commits_path(project, 'master') expect(flash[:notice]).to eq('The commit has been successfully reverted.') end end @@ -191,7 +191,7 @@ describe Projects::CommitController do start_branch: 'master', id: commit.id) - expect(response).to redirect_to namespace_project_commit_path(project.namespace, project, commit.id) + expect(response).to redirect_to project_commit_path(project, commit.id) expect(flash[:alert]).to match('Sorry, we cannot revert this commit automatically.') end end @@ -218,7 +218,7 @@ describe Projects::CommitController do start_branch: 'master', id: master_pickable_commit.id) - expect(response).to redirect_to namespace_project_commits_path(project.namespace, project, 'master') + expect(response).to redirect_to project_commits_path(project, 'master') expect(flash[:notice]).to eq('The commit has been successfully cherry-picked.') end end @@ -240,7 +240,7 @@ describe Projects::CommitController do start_branch: 'master', id: master_pickable_commit.id) - expect(response).to redirect_to namespace_project_commit_path(project.namespace, project, master_pickable_commit.id) + expect(response).to redirect_to project_commit_path(project, master_pickable_commit.id) expect(flash[:alert]).to match('Sorry, we cannot cherry-pick this commit automatically.') end end @@ -343,7 +343,8 @@ describe Projects::CommitController do get_pipelines(id: commit.id, format: :json) expect(response).to be_ok - expect(JSON.parse(response.body)).not_to be_empty + expect(JSON.parse(response.body)['pipelines']).not_to be_empty + expect(JSON.parse(response.body)['count']['all']).to eq 1 end end end diff --git a/spec/controllers/projects/compare_controller_spec.rb b/spec/controllers/projects/compare_controller_spec.rb index 8f4694c9854..b4f9fd9b7a2 100644 --- a/spec/controllers/projects/compare_controller_spec.rb +++ b/spec/controllers/projects/compare_controller_spec.rb @@ -72,7 +72,7 @@ describe Projects::CompareController do from: '', to: 'master') - expect(response).to redirect_to(namespace_project_compare_index_path(project.namespace, project, to: 'master')) + expect(response).to redirect_to(project_compare_index_path(project, to: 'master')) end it 'redirects back to index when params[:to] is empty and preserves params[:from]' do @@ -82,7 +82,7 @@ describe Projects::CompareController do from: 'master', to: '') - expect(response).to redirect_to(namespace_project_compare_index_path(project.namespace, project, from: 'master')) + expect(response).to redirect_to(project_compare_index_path(project, from: 'master')) end it 'redirects back to index when params[:from] and params[:to] are empty' do diff --git a/spec/controllers/projects/deployments_controller_spec.rb b/spec/controllers/projects/deployments_controller_spec.rb index 4c69443314d..0dbfcf97f6f 100644 --- a/spec/controllers/projects/deployments_controller_spec.rb +++ b/spec/controllers/projects/deployments_controller_spec.rb @@ -42,6 +42,7 @@ describe Projects::DeploymentsController do before do allow(controller).to receive(:deployment).and_return(deployment) end + context 'when metrics are disabled' do before do allow(deployment).to receive(:has_metrics?).and_return false @@ -108,6 +109,69 @@ describe Projects::DeploymentsController do end end + describe 'GET #additional_metrics' do + let(:deployment) { create(:deployment, project: project, environment: environment) } + + before do + allow(controller).to receive(:deployment).and_return(deployment) + end + + context 'when metrics are disabled' do + before do + allow(deployment).to receive(:has_metrics?).and_return false + end + + it 'responds with not found' do + get :metrics, deployment_params(id: deployment.id) + + expect(response).to be_not_found + end + end + + context 'when metrics are enabled' do + let(:prometheus_service) { double('prometheus_service') } + + before do + allow(deployment.project).to receive(:prometheus_service).and_return(prometheus_service) + end + + context 'when environment has no metrics' do + before do + expect(deployment).to receive(:additional_metrics).and_return({}) + end + + it 'returns a empty response 204 response' do + get :additional_metrics, deployment_params(id: deployment.id, format: :json) + expect(response).to have_http_status(204) + expect(response.body).to eq('') + end + end + + context 'when environment has some metrics' do + let(:empty_metrics) do + { + success: true, + metrics: {}, + last_update: 42 + } + end + + before do + expect(deployment).to receive(:additional_metrics).and_return(empty_metrics) + end + + it 'returns a metrics JSON document' do + get :additional_metrics, deployment_params(id: deployment.id, format: :json) + + expect(response).to be_ok + expect(json_response['success']).to be(true) + expect(json_response['metrics']).to eq({}) + expect(json_response['last_update']).to eq(42) + end + end + end + end + def deployment_params(opts = {}) opts.reverse_merge(namespace_id: project.namespace, project_id: project, diff --git a/spec/controllers/projects/environments_controller_spec.rb b/spec/controllers/projects/environments_controller_spec.rb index 9ec3c53174e..f88f50c3cc6 100644 --- a/spec/controllers/projects/environments_controller_spec.rb +++ b/spec/controllers/projects/environments_controller_spec.rb @@ -58,11 +58,9 @@ describe Projects::EnvironmentsController do expect(json_response['stopped_count']).to eq 1 end - it 'does not set the polling interval header' do - # TODO, this is a temporary fix, see follow up issue: - # https://gitlab.com/gitlab-org/gitlab-ee/issues/2677 + it 'sets the polling interval header' do expect(response).to have_http_status(:ok) - expect(response.headers['Poll-Interval']).to be_nil + expect(response.headers['Poll-Interval']).to eq("3000") end end @@ -184,7 +182,7 @@ describe Projects::EnvironmentsController do expect(response).to have_http_status(200) expect(json_response).to eq( { 'redirect_url' => - namespace_project_job_url(project.namespace, project, action) }) + project_job_url(project, action) }) end end @@ -198,7 +196,7 @@ describe Projects::EnvironmentsController do expect(response).to have_http_status(200) expect(json_response).to eq( { 'redirect_url' => - namespace_project_environment_url(project.namespace, project, environment) }) + project_environment_url(project, environment) }) end end end @@ -318,6 +316,48 @@ describe Projects::EnvironmentsController do end end + describe 'GET #additional_metrics' do + before do + allow(controller).to receive(:environment).and_return(environment) + end + + context 'when environment has no metrics' do + before do + expect(environment).to receive(:additional_metrics).and_return(nil) + end + + context 'when requesting metrics as JSON' do + it 'returns a metrics JSON document' do + get :additional_metrics, environment_params(format: :json) + + expect(response).to have_http_status(204) + expect(json_response).to eq({}) + end + end + end + + context 'when environment has some metrics' do + before do + expect(environment) + .to receive(:additional_metrics) + .and_return({ + success: true, + data: {}, + last_update: 42 + }) + end + + it 'returns a metrics JSON document' do + get :additional_metrics, environment_params(format: :json) + + expect(response).to be_ok + expect(json_response['success']).to be(true) + expect(json_response['data']).to eq({}) + expect(json_response['last_update']).to eq(42) + end + end + end + def environment_params(opts = {}) opts.reverse_merge(namespace_id: project.namespace, project_id: project, diff --git a/spec/controllers/projects/group_links_controller_spec.rb b/spec/controllers/projects/group_links_controller_spec.rb index b5435357f53..019a50882ab 100644 --- a/spec/controllers/projects/group_links_controller_spec.rb +++ b/spec/controllers/projects/group_links_controller_spec.rb @@ -34,7 +34,7 @@ describe Projects::GroupLinksController do it 'redirects to project group links page' do expect(response).to redirect_to( - namespace_project_settings_members_path(project.namespace, project) + project_project_members_path(project) ) end end @@ -65,7 +65,7 @@ describe Projects::GroupLinksController do it 'redirects to project group links page' do expect(response).to redirect_to( - namespace_project_settings_members_path(project.namespace, project) + project_project_members_path(project) ) end end @@ -79,7 +79,7 @@ describe Projects::GroupLinksController do it 'redirects to project group links page' do expect(response).to redirect_to( - namespace_project_settings_members_path(project.namespace, project) + project_project_members_path(project) ) expect(flash[:alert]).to eq('Please select a group.') end diff --git a/spec/controllers/projects/hooks_controller_spec.rb b/spec/controllers/projects/hooks_controller_spec.rb new file mode 100644 index 00000000000..b93ab220f4d --- /dev/null +++ b/spec/controllers/projects/hooks_controller_spec.rb @@ -0,0 +1,21 @@ +require 'spec_helper' + +describe Projects::HooksController do + let(:project) { create(:empty_project) } + let(:user) { create(:user) } + + before do + project.team << [user, :master] + sign_in(user) + end + + describe '#index' do + it 'redirects to settings/integrations page' do + get(:index, namespace_id: project.namespace, project_id: project) + + expect(response).to redirect_to( + project_settings_integrations_path(project) + ) + end + end +end diff --git a/spec/controllers/projects/imports_controller_spec.rb b/spec/controllers/projects/imports_controller_spec.rb index 6724b474179..9be61342616 100644 --- a/spec/controllers/projects/imports_controller_spec.rb +++ b/spec/controllers/projects/imports_controller_spec.rb @@ -59,7 +59,7 @@ describe Projects::ImportsController do it 'redirects to new_namespace_project_import_path' do get :show, namespace_id: project.namespace.to_param, project_id: project - expect(response).to redirect_to new_namespace_project_import_path(project.namespace, project) + expect(response).to redirect_to new_project_import_path(project) end end @@ -75,7 +75,7 @@ describe Projects::ImportsController do get :show, namespace_id: project.namespace.to_param, project_id: project expect(flash[:notice]).to eq 'The project was successfully forked.' - expect(response).to redirect_to namespace_project_path(project.namespace, project) + expect(response).to redirect_to project_path(project) end end @@ -84,14 +84,14 @@ describe Projects::ImportsController do get :show, namespace_id: project.namespace.to_param, project_id: project expect(flash[:notice]).to eq 'The project was successfully imported.' - expect(response).to redirect_to namespace_project_path(project.namespace, project) + expect(response).to redirect_to project_path(project) end end context 'when continue params is present' do let(:params) do { - to: namespace_project_path(project.namespace, project), + to: project_path(project), notice: 'Finished' } end @@ -120,7 +120,7 @@ describe Projects::ImportsController do it 'redirects to namespace_project_path' do get :show, namespace_id: project.namespace.to_param, project_id: project - expect(response).to redirect_to namespace_project_path(project.namespace, project) + expect(response).to redirect_to project_path(project) end end end diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb index 9f98427a86b..2c57f3bcf8d 100644 --- a/spec/controllers/projects/issues_controller_spec.rb +++ b/spec/controllers/projects/issues_controller_spec.rb @@ -7,14 +7,30 @@ describe Projects::IssuesController do describe "GET #index" do context 'external issue tracker' do - it 'redirects to the external issue tracker' do - external = double(project_path: 'https://example.com/project') - allow(project).to receive(:external_issue_tracker).and_return(external) - controller.instance_variable_set(:@project, project) + before do + sign_in(user) + project.add_developer(user) + create(:jira_service, project: project) + end - get :index, namespace_id: project.namespace, project_id: project + context 'when GitLab issues disabled' do + it 'returns 404 status' do + project.issues_enabled = false + project.save! + + get :index, namespace_id: project.namespace, project_id: project - expect(response).to redirect_to('https://example.com/project') + expect(response).to have_http_status(404) + end + end + + context 'when GitLab issues enabled' do + it 'renders the "index" template' do + get :index, namespace_id: project.namespace, project_id: project + + expect(response).to have_http_status(200) + expect(response).to render_template(:index) + end end end @@ -35,20 +51,12 @@ describe Projects::IssuesController do it "returns 301 if request path doesn't match project path" do get :index, namespace_id: project.namespace, project_id: project.path.upcase - expect(response).to redirect_to(namespace_project_issues_path(project.namespace, project)) + expect(response).to redirect_to(project_issues_path(project)) end it "returns 404 when issues are disabled" do project.issues_enabled = false - project.save - - get :index, namespace_id: project.namespace, project_id: project - expect(response).to have_http_status(404) - end - - it "returns 404 when external issue tracker is enabled" do - controller.instance_variable_set(:@project, project) - allow(project).to receive(:default_issues_tracker?).and_return(false) + project.save! get :index, namespace_id: project.namespace, project_id: project expect(response).to have_http_status(404) @@ -139,19 +147,36 @@ describe Projects::IssuesController do end context 'external issue tracker' do + let!(:service) do + create(:custom_issue_tracker_service, project: project, title: 'Custom Issue Tracker', new_issue_url: 'http://test.com') + end + before do sign_in(user) project.team << [user, :developer] - end - it 'redirects to the external issue tracker' do - external = double(new_issue_path: 'https://example.com/issues/new') + external = double allow(project).to receive(:external_issue_tracker).and_return(external) - controller.instance_variable_set(:@project, project) + end - get :new, namespace_id: project.namespace, project_id: project + context 'when GitLab issues disabled' do + it 'returns 404 status' do + project.issues_enabled = false + project.save! + + get :new, namespace_id: project.namespace, project_id: project + + expect(response).to have_http_status(404) + end + end + + context 'when GitLab issues enabled' do + it 'renders the "new" template' do + get :new, namespace_id: project.namespace, project_id: project - expect(response).to redirect_to('https://example.com/issues/new') + expect(response).to have_http_status(200) + expect(response).to render_template(:new) + end end end end @@ -329,7 +354,7 @@ describe Projects::IssuesController do update_verified_issue expect(response) - .to redirect_to(namespace_project_issue_path(project.namespace, project, issue)) + .to redirect_to(project_issue_path(project, issue)) end it 'accepts an issue after recaptcha is verified' do @@ -512,6 +537,36 @@ describe Projects::IssuesController do end end + describe 'GET #realtime_changes' do + it_behaves_like 'restricted action', success: 200 + + def go(id:) + get :realtime_changes, + namespace_id: project.namespace.to_param, + project_id: project, + id: id + end + + context 'when an issue was edited by a deleted user' do + let(:deleted_user) { create(:user) } + + before do + project.team << [user, :developer] + + issue.update!(last_edited_by: deleted_user, last_edited_at: Time.now) + + deleted_user.destroy + sign_in(user) + end + + it 'returns 200' do + go(id: issue.iid) + + expect(response).to have_http_status(200) + end + end + end + describe 'GET #edit' do it_behaves_like 'restricted action', success: 200 @@ -772,7 +827,7 @@ describe Projects::IssuesController do delete :destroy, namespace_id: project.namespace, project_id: project, id: issue.iid expect(response).to have_http_status(302) - expect(controller).to set_flash[:notice].to(/The issue was successfully deleted\./).now + expect(controller).to set_flash[:notice].to(/The issue was successfully deleted\./) end it 'delegates the update of the todos count cache to TodoService' do diff --git a/spec/controllers/projects/jobs_controller_spec.rb b/spec/controllers/projects/jobs_controller_spec.rb index 472e5fc51a0..5a295ae47a6 100644 --- a/spec/controllers/projects/jobs_controller_spec.rb +++ b/spec/controllers/projects/jobs_controller_spec.rb @@ -7,6 +7,10 @@ describe Projects::JobsController do let(:pipeline) { create(:ci_pipeline, project: project) } let(:user) { create(:user) } + before do + stub_not_protect_default_branch + end + describe 'GET index' do context 'when scope is pending' do before do diff --git a/spec/controllers/projects/labels_controller_spec.rb b/spec/controllers/projects/labels_controller_spec.rb index bf1776eb320..f19ad4c2c81 100644 --- a/spec/controllers/projects/labels_controller_spec.rb +++ b/spec/controllers/projects/labels_controller_spec.rb @@ -178,7 +178,7 @@ describe Projects::LabelsController do it 'redirects to the correct casing' do get :index, namespace_id: project.namespace, project_id: project.to_param.upcase - expect(response).to redirect_to(namespace_project_labels_path(project.namespace, project)) + expect(response).to redirect_to(project_labels_path(project)) expect(controller).not_to set_flash[:notice] end end @@ -191,7 +191,7 @@ describe Projects::LabelsController do it 'redirects to the canonical path' do get :index, namespace_id: project.namespace, project_id: project.to_param + 'old' - expect(response).to redirect_to(namespace_project_labels_path(project.namespace, project)) + expect(response).to redirect_to(project_labels_path(project)) expect(controller).to set_flash[:notice].to(project_moved_message(redirect_route, project)) end end diff --git a/spec/controllers/projects/mattermosts_controller_spec.rb b/spec/controllers/projects/mattermosts_controller_spec.rb index 422a8b6fac0..12e413db902 100644 --- a/spec/controllers/projects/mattermosts_controller_spec.rb +++ b/spec/controllers/projects/mattermosts_controller_spec.rb @@ -38,7 +38,7 @@ describe Projects::MattermostsController do it 'shows the error' do allow_any_instance_of(MattermostSlashCommandsService).to receive(:configure).and_return([false, "error message"]) - expect(subject).to redirect_to(new_namespace_project_mattermost_url(project.namespace, project)) + expect(subject).to redirect_to(new_project_mattermost_url(project)) end end @@ -51,7 +51,7 @@ describe Projects::MattermostsController do subject service = project.services.last - expect(subject).to redirect_to(edit_namespace_project_service_url(project.namespace, project, service)) + expect(subject).to redirect_to(edit_project_service_url(project, service)) end end end diff --git a/spec/controllers/projects/merge_requests/conflicts_controller_spec.rb b/spec/controllers/projects/merge_requests/conflicts_controller_spec.rb new file mode 100644 index 00000000000..9278ac8edd8 --- /dev/null +++ b/spec/controllers/projects/merge_requests/conflicts_controller_spec.rb @@ -0,0 +1,307 @@ +require 'spec_helper' + +describe Projects::MergeRequests::ConflictsController do + let(:project) { create(:project) } + let(:user) { project.owner } + let(:merge_request) { create(:merge_request_with_diffs, target_project: project, source_project: project) } + let(:merge_request_with_conflicts) do + create(:merge_request, source_branch: 'conflict-resolvable', target_branch: 'conflict-start', source_project: project) do |mr| + mr.mark_as_unmergeable + end + end + + before do + sign_in(user) + end + + describe 'GET show' do + context 'when the conflicts cannot be resolved in the UI' do + before do + allow_any_instance_of(Gitlab::Conflict::Parser) + .to receive(:parse).and_raise(Gitlab::Conflict::Parser::UnmergeableFile) + + get :show, + namespace_id: merge_request_with_conflicts.project.namespace.to_param, + project_id: merge_request_with_conflicts.project, + id: merge_request_with_conflicts.iid, + format: 'json' + end + + it 'returns a 200 status code' do + expect(response).to have_http_status(:ok) + end + + it 'returns JSON with a message' do + expect(json_response.keys).to contain_exactly('message', 'type') + end + end + + context 'with valid conflicts' do + before do + get :show, + namespace_id: merge_request_with_conflicts.project.namespace.to_param, + project_id: merge_request_with_conflicts.project, + id: merge_request_with_conflicts.iid, + format: 'json' + end + + it 'matches the schema' do + expect(response).to match_response_schema('conflicts') + end + + it 'includes meta info about the MR' do + expect(json_response['commit_message']).to include('Merge branch') + expect(json_response['commit_sha']).to match(/\h{40}/) + expect(json_response['source_branch']).to eq(merge_request_with_conflicts.source_branch) + expect(json_response['target_branch']).to eq(merge_request_with_conflicts.target_branch) + end + + it 'includes each file that has conflicts' do + filenames = json_response['files'].map { |file| file['new_path'] } + + expect(filenames).to contain_exactly('files/ruby/popen.rb', 'files/ruby/regex.rb') + end + + it 'splits files into sections with lines' do + json_response['files'].each do |file| + file['sections'].each do |section| + expect(section).to include('conflict', 'lines') + + section['lines'].each do |line| + if section['conflict'] + expect(line['type']).to be_in(%w(old new)) + expect(line.values_at('old_line', 'new_line')).to contain_exactly(nil, a_kind_of(Integer)) + else + if line['type'].nil? + expect(line['old_line']).not_to eq(nil) + expect(line['new_line']).not_to eq(nil) + else + expect(line['type']).to eq('match') + expect(line['old_line']).to eq(nil) + expect(line['new_line']).to eq(nil) + end + end + end + end + end + end + + it 'has unique section IDs across files' do + section_ids = json_response['files'].flat_map do |file| + file['sections'].map { |section| section['id'] }.compact + end + + expect(section_ids.uniq).to eq(section_ids) + end + end + end + + describe 'GET conflict_for_path' do + def conflict_for_path(path) + get :conflict_for_path, + namespace_id: merge_request_with_conflicts.project.namespace.to_param, + project_id: merge_request_with_conflicts.project, + id: merge_request_with_conflicts.iid, + old_path: path, + new_path: path, + format: 'json' + end + + context 'when the conflicts cannot be resolved in the UI' do + before do + allow_any_instance_of(Gitlab::Conflict::Parser) + .to receive(:parse).and_raise(Gitlab::Conflict::Parser::UnmergeableFile) + + conflict_for_path('files/ruby/regex.rb') + end + + it 'returns a 404 status code' do + expect(response).to have_http_status(:not_found) + end + end + + context 'when the file does not exist cannot be resolved in the UI' do + before do + conflict_for_path('files/ruby/regexp.rb') + end + + it 'returns a 404 status code' do + expect(response).to have_http_status(:not_found) + end + end + + context 'with an existing file' do + let(:path) { 'files/ruby/regex.rb' } + + before do + conflict_for_path(path) + end + + it 'returns a 200 status code' do + expect(response).to have_http_status(:ok) + end + + it 'returns the file in JSON format' do + content = MergeRequests::Conflicts::ListService.new(merge_request_with_conflicts) + .file_for_path(path, path) + .content + + expect(json_response).to include('old_path' => path, + 'new_path' => path, + 'blob_icon' => 'file-text-o', + 'blob_path' => a_string_ending_with(path), + 'blob_ace_mode' => 'ruby', + 'content' => content) + end + end + end + + context 'POST resolve_conflicts' do + let!(:original_head_sha) { merge_request_with_conflicts.diff_head_sha } + + def resolve_conflicts(files) + post :resolve_conflicts, + namespace_id: merge_request_with_conflicts.project.namespace.to_param, + project_id: merge_request_with_conflicts.project, + id: merge_request_with_conflicts.iid, + format: 'json', + files: files, + commit_message: 'Commit message' + end + + context 'with valid params' do + before do + resolved_files = [ + { + 'new_path' => 'files/ruby/popen.rb', + 'old_path' => 'files/ruby/popen.rb', + 'sections' => { + '2f6fcd96b88b36ce98c38da085c795a27d92a3dd_14_14' => 'head' + } + }, { + 'new_path' => 'files/ruby/regex.rb', + 'old_path' => 'files/ruby/regex.rb', + 'sections' => { + '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_9_9' => 'head', + '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_21_21' => 'origin', + '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_49_49' => 'origin' + } + } + ] + + resolve_conflicts(resolved_files) + end + + it 'creates a new commit on the branch' do + expect(original_head_sha).not_to eq(merge_request_with_conflicts.source_branch_head.sha) + expect(merge_request_with_conflicts.source_branch_head.message).to include('Commit message') + end + + it 'returns an OK response' do + expect(response).to have_http_status(:ok) + end + end + + context 'when sections are missing' do + before do + resolved_files = [ + { + 'new_path' => 'files/ruby/popen.rb', + 'old_path' => 'files/ruby/popen.rb', + 'sections' => { + '2f6fcd96b88b36ce98c38da085c795a27d92a3dd_14_14' => 'head' + } + }, { + 'new_path' => 'files/ruby/regex.rb', + 'old_path' => 'files/ruby/regex.rb', + 'sections' => { + '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_9_9' => 'head' + } + } + ] + + resolve_conflicts(resolved_files) + end + + it 'returns a 400 error' do + expect(response).to have_http_status(:bad_request) + end + + it 'has a message with the name of the first missing section' do + expect(json_response['message']).to include('6eb14e00385d2fb284765eb1cd8d420d33d63fc9_21_21') + end + + it 'does not create a new commit' do + expect(original_head_sha).to eq(merge_request_with_conflicts.source_branch_head.sha) + end + end + + context 'when files are missing' do + before do + resolved_files = [ + { + 'new_path' => 'files/ruby/regex.rb', + 'old_path' => 'files/ruby/regex.rb', + 'sections' => { + '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_9_9' => 'head', + '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_21_21' => 'origin', + '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_49_49' => 'origin' + } + } + ] + + resolve_conflicts(resolved_files) + end + + it 'returns a 400 error' do + expect(response).to have_http_status(:bad_request) + end + + it 'has a message with the name of the missing file' do + expect(json_response['message']).to include('files/ruby/popen.rb') + end + + it 'does not create a new commit' do + expect(original_head_sha).to eq(merge_request_with_conflicts.source_branch_head.sha) + end + end + + context 'when a file has identical content to the conflict' do + before do + content = MergeRequests::Conflicts::ListService.new(merge_request_with_conflicts) + .file_for_path('files/ruby/popen.rb', 'files/ruby/popen.rb') + .content + + resolved_files = [ + { + 'new_path' => 'files/ruby/popen.rb', + 'old_path' => 'files/ruby/popen.rb', + 'content' => content + }, { + 'new_path' => 'files/ruby/regex.rb', + 'old_path' => 'files/ruby/regex.rb', + 'sections' => { + '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_9_9' => 'head', + '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_21_21' => 'origin', + '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_49_49' => 'origin' + } + } + ] + + resolve_conflicts(resolved_files) + end + + it 'returns a 400 error' do + expect(response).to have_http_status(:bad_request) + end + + it 'has a message with the path of the problem file' do + expect(json_response['message']).to include('files/ruby/popen.rb') + end + + it 'does not create a new commit' do + expect(original_head_sha).to eq(merge_request_with_conflicts.source_branch_head.sha) + end + end + end +end diff --git a/spec/controllers/projects/merge_requests/creations_controller_spec.rb b/spec/controllers/projects/merge_requests/creations_controller_spec.rb new file mode 100644 index 00000000000..f9d8f0f5fcf --- /dev/null +++ b/spec/controllers/projects/merge_requests/creations_controller_spec.rb @@ -0,0 +1,120 @@ +require 'spec_helper' + +describe Projects::MergeRequests::CreationsController do + let(:project) { create(:project) } + let(:user) { project.owner } + let(:fork_project) { create(:forked_project_with_submodules) } + + before do + fork_project.team << [user, :master] + + sign_in(user) + end + + describe 'GET new' do + context 'merge request that removes a submodule' do + render_views + + it 'renders new merge request widget template' do + get :new, + namespace_id: fork_project.namespace.to_param, + project_id: fork_project, + merge_request: { + source_branch: 'remove-submodule', + target_branch: 'master' + } + + expect(response).to be_success + end + end + end + + describe 'GET pipelines' do + before do + create(:ci_pipeline, sha: fork_project.commit('remove-submodule').id, + ref: 'remove-submodule', + project: fork_project) + end + + it 'renders JSON including serialized pipelines' do + get :pipelines, + namespace_id: fork_project.namespace.to_param, + project_id: fork_project, + merge_request: { + source_branch: 'remove-submodule', + target_branch: 'master' + }, + format: :json + + expect(response).to be_ok + expect(json_response).to have_key 'pipelines' + expect(json_response['pipelines']).not_to be_empty + end + end + + describe 'GET diff_for_path' do + def diff_for_path(extra_params = {}) + params = { + namespace_id: project.namespace.to_param, + project_id: project, + format: 'json' + } + + get :diff_for_path, params.merge(extra_params) + end + + let(:existing_path) { 'files/ruby/feature.rb' } + + context 'when both branches are in the same project' do + it 'disables diff notes' do + diff_for_path(old_path: existing_path, new_path: existing_path, merge_request: { source_branch: 'feature', target_branch: 'master' }) + + expect(assigns(:diff_notes_disabled)).to be_truthy + end + + it 'only renders the diffs for the path given' do + expect(controller).to receive(:render_diff_for_path).and_wrap_original do |meth, diffs| + expect(diffs.diff_files.map(&:new_path)).to contain_exactly(existing_path) + meth.call(diffs) + end + + diff_for_path(old_path: existing_path, new_path: existing_path, merge_request: { source_branch: 'feature', target_branch: 'master' }) + end + end + + context 'when the source branch is in a different project to the target' do + let(:other_project) { create(:project) } + + before do + other_project.team << [user, :master] + end + + context 'when the path exists in the diff' do + it 'disables diff notes' do + diff_for_path(old_path: existing_path, new_path: existing_path, merge_request: { source_project: other_project, source_branch: 'feature', target_branch: 'master' }) + + expect(assigns(:diff_notes_disabled)).to be_truthy + end + + it 'only renders the diffs for the path given' do + expect(controller).to receive(:render_diff_for_path).and_wrap_original do |meth, diffs| + expect(diffs.diff_files.map(&:new_path)).to contain_exactly(existing_path) + meth.call(diffs) + end + + diff_for_path(old_path: existing_path, new_path: existing_path, merge_request: { source_project: other_project, source_branch: 'feature', target_branch: 'master' }) + end + end + + context 'when the path does not exist in the diff' do + before do + diff_for_path(old_path: 'files/ruby/nopen.rb', new_path: 'files/ruby/nopen.rb', merge_request: { source_project: other_project, source_branch: 'feature', target_branch: 'master' }) + end + + it 'returns a 404' do + expect(response).to have_http_status(404) + end + end + end + end +end diff --git a/spec/controllers/projects/merge_requests/diffs_controller_spec.rb b/spec/controllers/projects/merge_requests/diffs_controller_spec.rb new file mode 100644 index 00000000000..53fe2bdb189 --- /dev/null +++ b/spec/controllers/projects/merge_requests/diffs_controller_spec.rb @@ -0,0 +1,160 @@ +require 'spec_helper' + +describe Projects::MergeRequests::DiffsController do + let(:project) { create(:project) } + let(:user) { project.owner } + let(:merge_request) { create(:merge_request_with_diffs, target_project: project, source_project: project) } + + before do + sign_in(user) + end + + describe 'GET show' do + def go(extra_params = {}) + params = { + namespace_id: project.namespace.to_param, + project_id: project, + id: merge_request.iid, + format: 'json' + } + + get :show, params.merge(extra_params) + end + + context 'with default params' do + context 'for the same project' do + before do + go + end + + it 'renders the diffs template to a string' do + expect(response).to render_template('projects/merge_requests/diffs/_diffs') + expect(json_response).to have_key('html') + end + end + + context 'with forked projects with submodules' do + render_views + + let(:project) { create(:project) } + let(:fork_project) { create(:forked_project_with_submodules) } + let(:merge_request) { create(:merge_request_with_diffs, source_project: fork_project, source_branch: 'add-submodule-version-bump', target_branch: 'master', target_project: project) } + + before do + fork_project.build_forked_project_link(forked_to_project_id: fork_project.id, forked_from_project_id: project.id) + fork_project.save + merge_request.reload + go + end + + it 'renders' do + expect(response).to be_success + expect(response.body).to have_content('Subproject commit') + end + end + end + + context 'with ignore_whitespace_change' do + before do + go(w: 1) + end + + it 'renders the diffs template to a string' do + expect(response).to render_template('projects/merge_requests/diffs/_diffs') + expect(json_response).to have_key('html') + end + end + + context 'with view' do + before do + go(view: 'parallel') + end + + it 'saves the preferred diff view in a cookie' do + expect(response.cookies['diff_view']).to eq('parallel') + end + end + end + + describe 'GET diff_for_path' do + def diff_for_path(extra_params = {}) + params = { + namespace_id: project.namespace.to_param, + project_id: project, + id: merge_request.iid, + format: 'json' + } + + get :diff_for_path, params.merge(extra_params) + end + + let(:existing_path) { 'files/ruby/popen.rb' } + + context 'when the merge request exists' do + context 'when the user can view the merge request' do + context 'when the path exists in the diff' do + it 'enables diff notes' do + diff_for_path(old_path: existing_path, new_path: existing_path) + + expect(assigns(:diff_notes_disabled)).to be_falsey + expect(assigns(:new_diff_note_attrs)).to eq(noteable_type: 'MergeRequest', + noteable_id: merge_request.id) + end + + it 'only renders the diffs for the path given' do + expect(controller).to receive(:render_diff_for_path).and_wrap_original do |meth, diffs| + expect(diffs.diff_files.map(&:new_path)).to contain_exactly(existing_path) + meth.call(diffs) + end + + diff_for_path(old_path: existing_path, new_path: existing_path) + end + end + + context 'when the path does not exist in the diff' do + before do + diff_for_path(old_path: 'files/ruby/nopen.rb', new_path: 'files/ruby/nopen.rb') + end + + it 'returns a 404' do + expect(response).to have_http_status(404) + end + end + end + + context 'when the user cannot view the merge request' do + before do + project.team.truncate + diff_for_path(old_path: existing_path, new_path: existing_path) + end + + it 'returns a 404' do + expect(response).to have_http_status(404) + end + end + end + + context 'when the merge request does not exist' do + before do + diff_for_path(id: merge_request.iid.succ, old_path: existing_path, new_path: existing_path) + end + + it 'returns a 404' do + expect(response).to have_http_status(404) + end + end + + context 'when the merge request belongs to a different project' do + let(:other_project) { create(:empty_project) } + + before do + other_project.team << [user, :master] + diff_for_path(old_path: existing_path, new_path: existing_path, project_id: other_project) + end + + it 'returns a 404' do + expect(response).to have_http_status(404) + end + end + end +end diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb index 6817c2652fd..2fce4b7a85f 100644 --- a/spec/controllers/projects/merge_requests_controller_spec.rb +++ b/spec/controllers/projects/merge_requests_controller_spec.rb @@ -14,53 +14,6 @@ describe Projects::MergeRequestsController do sign_in(user) end - describe 'GET new' do - context 'merge request that removes a submodule' do - render_views - - let(:fork_project) { create(:forked_project_with_submodules) } - - before do - fork_project.team << [user, :master] - end - - context 'when rendering HTML response' do - it 'renders new merge request widget template' do - submit_new_merge_request - - expect(response).to be_success - end - end - - context 'when rendering JSON response' do - before do - create(:ci_pipeline, sha: fork_project.commit('remove-submodule').id, - ref: 'remove-submodule', - project: fork_project) - end - - it 'renders JSON including serialized pipelines' do - submit_new_merge_request(format: :json) - - expect(response).to be_ok - expect(json_response).to have_key 'pipelines' - expect(json_response['pipelines']).not_to be_empty - end - end - end - - def submit_new_merge_request(format: :html) - get :new, - namespace_id: fork_project.namespace.to_param, - project_id: fork_project, - merge_request: { - source_branch: 'remove-submodule', - target_branch: 'master' - }, - format: format - end - end - describe 'GET commit_change_content' do it 'renders commit_change_content template' do get :commit_change_content, @@ -486,7 +439,7 @@ describe Projects::MergeRequestsController do delete :destroy, namespace_id: project.namespace, project_id: project, id: merge_request.iid expect(response).to have_http_status(302) - expect(controller).to set_flash[:notice].to(/The merge request was successfully deleted\./).now + expect(controller).to set_flash[:notice].to(/The merge request was successfully deleted\./) end it 'delegates the update of the todos count cache to TodoService' do @@ -497,234 +450,6 @@ describe Projects::MergeRequestsController do end end - describe 'GET diffs' do - def go(extra_params = {}) - params = { - namespace_id: project.namespace.to_param, - project_id: project, - id: merge_request.iid - } - - get :diffs, params.merge(extra_params) - end - - it_behaves_like "loads labels", :diffs - - context 'with default params' do - context 'as html' do - before do - go(format: 'html') - end - - it 'renders the diff template' do - expect(response).to render_template('diffs') - end - end - - context 'as json' do - before do - go(format: 'json') - end - - it 'renders the diffs template to a string' do - expect(response).to render_template('projects/merge_requests/show/_diffs') - expect(json_response).to have_key('html') - end - end - - context 'with forked projects with submodules' do - render_views - - let(:project) { create(:project) } - let(:fork_project) { create(:forked_project_with_submodules) } - let(:merge_request) { create(:merge_request_with_diffs, source_project: fork_project, source_branch: 'add-submodule-version-bump', target_branch: 'master', target_project: project) } - - before do - fork_project.build_forked_project_link(forked_to_project_id: fork_project.id, forked_from_project_id: project.id) - fork_project.save - merge_request.reload - go(format: 'json') - end - - it 'renders' do - expect(response).to be_success - expect(response.body).to have_content('Subproject commit') - end - end - end - - context 'with ignore_whitespace_change' do - context 'as html' do - before do - go(format: 'html', w: 1) - end - - it 'renders the diff template' do - expect(response).to render_template('diffs') - end - end - - context 'as json' do - before do - go(format: 'json', w: 1) - end - - it 'renders the diffs template to a string' do - expect(response).to render_template('projects/merge_requests/show/_diffs') - expect(json_response).to have_key('html') - end - end - end - - context 'with view' do - before do - go(view: 'parallel') - end - - it 'saves the preferred diff view in a cookie' do - expect(response.cookies['diff_view']).to eq('parallel') - end - end - end - - describe 'GET diff_for_path' do - def diff_for_path(extra_params = {}) - params = { - namespace_id: project.namespace.to_param, - project_id: project - } - - get :diff_for_path, params.merge(extra_params) - end - - context 'when an ID param is passed' do - let(:existing_path) { 'files/ruby/popen.rb' } - - context 'when the merge request exists' do - context 'when the user can view the merge request' do - context 'when the path exists in the diff' do - it 'enables diff notes' do - diff_for_path(id: merge_request.iid, old_path: existing_path, new_path: existing_path) - - expect(assigns(:diff_notes_disabled)).to be_falsey - expect(assigns(:new_diff_note_attrs)).to eq(noteable_type: 'MergeRequest', - noteable_id: merge_request.id) - end - - it 'only renders the diffs for the path given' do - expect(controller).to receive(:render_diff_for_path).and_wrap_original do |meth, diffs| - expect(diffs.diff_files.map(&:new_path)).to contain_exactly(existing_path) - meth.call(diffs) - end - - diff_for_path(id: merge_request.iid, old_path: existing_path, new_path: existing_path) - end - end - - context 'when the path does not exist in the diff' do - before do - diff_for_path(id: merge_request.iid, old_path: 'files/ruby/nopen.rb', new_path: 'files/ruby/nopen.rb') - end - - it 'returns a 404' do - expect(response).to have_http_status(404) - end - end - end - - context 'when the user cannot view the merge request' do - before do - project.team.truncate - diff_for_path(id: merge_request.iid, old_path: existing_path, new_path: existing_path) - end - - it 'returns a 404' do - expect(response).to have_http_status(404) - end - end - end - - context 'when the merge request does not exist' do - before do - diff_for_path(id: merge_request.iid.succ, old_path: existing_path, new_path: existing_path) - end - - it 'returns a 404' do - expect(response).to have_http_status(404) - end - end - - context 'when the merge request belongs to a different project' do - let(:other_project) { create(:empty_project) } - - before do - other_project.team << [user, :master] - diff_for_path(id: merge_request.iid, old_path: existing_path, new_path: existing_path, project_id: other_project) - end - - it 'returns a 404' do - expect(response).to have_http_status(404) - end - end - end - - context 'when source and target params are passed' do - let(:existing_path) { 'files/ruby/feature.rb' } - - context 'when both branches are in the same project' do - it 'disables diff notes' do - diff_for_path(old_path: existing_path, new_path: existing_path, merge_request: { source_branch: 'feature', target_branch: 'master' }) - - expect(assigns(:diff_notes_disabled)).to be_truthy - end - - it 'only renders the diffs for the path given' do - expect(controller).to receive(:render_diff_for_path).and_wrap_original do |meth, diffs| - expect(diffs.diff_files.map(&:new_path)).to contain_exactly(existing_path) - meth.call(diffs) - end - - diff_for_path(old_path: existing_path, new_path: existing_path, merge_request: { source_branch: 'feature', target_branch: 'master' }) - end - end - - context 'when the source branch is in a different project to the target' do - let(:other_project) { create(:project) } - - before do - other_project.team << [user, :master] - end - - context 'when the path exists in the diff' do - it 'disables diff notes' do - diff_for_path(old_path: existing_path, new_path: existing_path, merge_request: { source_project: other_project, source_branch: 'feature', target_branch: 'master' }) - - expect(assigns(:diff_notes_disabled)).to be_truthy - end - - it 'only renders the diffs for the path given' do - expect(controller).to receive(:render_diff_for_path).and_wrap_original do |meth, diffs| - expect(diffs.diff_files.map(&:new_path)).to contain_exactly(existing_path) - meth.call(diffs) - end - - diff_for_path(old_path: existing_path, new_path: existing_path, merge_request: { source_project: other_project, source_branch: 'feature', target_branch: 'master' }) - end - end - - context 'when the path does not exist in the diff' do - before do - diff_for_path(old_path: 'files/ruby/nopen.rb', new_path: 'files/ruby/nopen.rb', merge_request: { source_project: other_project, source_branch: 'feature', target_branch: 'master' }) - end - - it 'returns a 404' do - expect(response).to have_http_status(404) - end - end - end - end - end - describe 'GET commits' do def go(format: 'html') get :commits, @@ -734,23 +459,11 @@ describe Projects::MergeRequestsController do format: format end - it_behaves_like "loads labels", :commits + it 'renders the commits template to a string' do + go format: 'json' - context 'as html' do - it 'renders the show template' do - go - - expect(response).to render_template('show') - end - end - - context 'as json' do - it 'renders the commits template to a string' do - go format: 'json' - - expect(response).to render_template('projects/merge_requests/show/_commits') - expect(json_response).to have_key('html') - end + expect(response).to render_template('projects/merge_requests/_commits') + expect(json_response).to have_key('html') end end @@ -759,106 +472,17 @@ describe Projects::MergeRequestsController do create(:ci_pipeline, project: merge_request.source_project, ref: merge_request.source_branch, sha: merge_request.diff_head_sha) - end - - context 'when using HTML format' do - it_behaves_like "loads labels", :pipelines - end - context 'when using JSON format' do - before do - get :pipelines, - namespace_id: project.namespace.to_param, - project_id: project, - id: merge_request.iid, - format: :json - end - - it 'responds with serialized pipelines' do - expect(json_response).not_to be_empty - end - end - end - - describe 'GET conflicts' do - context 'when the conflicts cannot be resolved in the UI' do - before do - allow_any_instance_of(Gitlab::Conflict::Parser) - .to receive(:parse).and_raise(Gitlab::Conflict::Parser::UnmergeableFile) - - get :conflicts, - namespace_id: merge_request_with_conflicts.project.namespace.to_param, - project_id: merge_request_with_conflicts.project, - id: merge_request_with_conflicts.iid, - format: 'json' - end - - it 'returns a 200 status code' do - expect(response).to have_http_status(:ok) - end - - it 'returns JSON with a message' do - expect(json_response.keys).to contain_exactly('message', 'type') - end + get :pipelines, + namespace_id: project.namespace.to_param, + project_id: project, + id: merge_request.iid, + format: :json end - context 'with valid conflicts' do - before do - get :conflicts, - namespace_id: merge_request_with_conflicts.project.namespace.to_param, - project_id: merge_request_with_conflicts.project, - id: merge_request_with_conflicts.iid, - format: 'json' - end - - it 'matches the schema' do - expect(response).to match_response_schema('conflicts') - end - - it 'includes meta info about the MR' do - expect(json_response['commit_message']).to include('Merge branch') - expect(json_response['commit_sha']).to match(/\h{40}/) - expect(json_response['source_branch']).to eq(merge_request_with_conflicts.source_branch) - expect(json_response['target_branch']).to eq(merge_request_with_conflicts.target_branch) - end - - it 'includes each file that has conflicts' do - filenames = json_response['files'].map { |file| file['new_path'] } - - expect(filenames).to contain_exactly('files/ruby/popen.rb', 'files/ruby/regex.rb') - end - - it 'splits files into sections with lines' do - json_response['files'].each do |file| - file['sections'].each do |section| - expect(section).to include('conflict', 'lines') - - section['lines'].each do |line| - if section['conflict'] - expect(line['type']).to be_in(%w(old new)) - expect(line.values_at('old_line', 'new_line')).to contain_exactly(nil, a_kind_of(Integer)) - else - if line['type'].nil? - expect(line['old_line']).not_to eq(nil) - expect(line['new_line']).not_to eq(nil) - else - expect(line['type']).to eq('match') - expect(line['old_line']).to eq(nil) - expect(line['new_line']).to eq(nil) - end - end - end - end - end - end - - it 'has unique section IDs across files' do - section_ids = json_response['files'].flat_map do |file| - file['sections'].map { |section| section['id'] }.compact - end - - expect(section_ids.uniq).to eq(section_ids) - end + it 'responds with serialized pipelines' do + expect(json_response['pipelines']).not_to be_empty + expect(json_response['count']['all']).to eq 1 end end @@ -913,215 +537,6 @@ describe Projects::MergeRequestsController do end end - describe 'GET conflict_for_path' do - def conflict_for_path(path) - get :conflict_for_path, - namespace_id: merge_request_with_conflicts.project.namespace.to_param, - project_id: merge_request_with_conflicts.project, - id: merge_request_with_conflicts.iid, - old_path: path, - new_path: path, - format: 'json' - end - - context 'when the conflicts cannot be resolved in the UI' do - before do - allow_any_instance_of(Gitlab::Conflict::Parser) - .to receive(:parse).and_raise(Gitlab::Conflict::Parser::UnmergeableFile) - - conflict_for_path('files/ruby/regex.rb') - end - - it 'returns a 404 status code' do - expect(response).to have_http_status(:not_found) - end - end - - context 'when the file does not exist cannot be resolved in the UI' do - before do - conflict_for_path('files/ruby/regexp.rb') - end - - it 'returns a 404 status code' do - expect(response).to have_http_status(:not_found) - end - end - - context 'with an existing file' do - let(:path) { 'files/ruby/regex.rb' } - - before do - conflict_for_path(path) - end - - it 'returns a 200 status code' do - expect(response).to have_http_status(:ok) - end - - it 'returns the file in JSON format' do - content = MergeRequests::Conflicts::ListService.new(merge_request_with_conflicts) - .file_for_path(path, path) - .content - - expect(json_response).to include('old_path' => path, - 'new_path' => path, - 'blob_icon' => 'file-text-o', - 'blob_path' => a_string_ending_with(path), - 'blob_ace_mode' => 'ruby', - 'content' => content) - end - end - end - - context 'POST resolve_conflicts' do - let!(:original_head_sha) { merge_request_with_conflicts.diff_head_sha } - - def resolve_conflicts(files) - post :resolve_conflicts, - namespace_id: merge_request_with_conflicts.project.namespace.to_param, - project_id: merge_request_with_conflicts.project, - id: merge_request_with_conflicts.iid, - format: 'json', - files: files, - commit_message: 'Commit message' - end - - context 'with valid params' do - before do - resolved_files = [ - { - 'new_path' => 'files/ruby/popen.rb', - 'old_path' => 'files/ruby/popen.rb', - 'sections' => { - '2f6fcd96b88b36ce98c38da085c795a27d92a3dd_14_14' => 'head' - } - }, { - 'new_path' => 'files/ruby/regex.rb', - 'old_path' => 'files/ruby/regex.rb', - 'sections' => { - '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_9_9' => 'head', - '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_21_21' => 'origin', - '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_49_49' => 'origin' - } - } - ] - - resolve_conflicts(resolved_files) - end - - it 'creates a new commit on the branch' do - expect(original_head_sha).not_to eq(merge_request_with_conflicts.source_branch_head.sha) - expect(merge_request_with_conflicts.source_branch_head.message).to include('Commit message') - end - - it 'returns an OK response' do - expect(response).to have_http_status(:ok) - end - end - - context 'when sections are missing' do - before do - resolved_files = [ - { - 'new_path' => 'files/ruby/popen.rb', - 'old_path' => 'files/ruby/popen.rb', - 'sections' => { - '2f6fcd96b88b36ce98c38da085c795a27d92a3dd_14_14' => 'head' - } - }, { - 'new_path' => 'files/ruby/regex.rb', - 'old_path' => 'files/ruby/regex.rb', - 'sections' => { - '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_9_9' => 'head' - } - } - ] - - resolve_conflicts(resolved_files) - end - - it 'returns a 400 error' do - expect(response).to have_http_status(:bad_request) - end - - it 'has a message with the name of the first missing section' do - expect(json_response['message']).to include('6eb14e00385d2fb284765eb1cd8d420d33d63fc9_21_21') - end - - it 'does not create a new commit' do - expect(original_head_sha).to eq(merge_request_with_conflicts.source_branch_head.sha) - end - end - - context 'when files are missing' do - before do - resolved_files = [ - { - 'new_path' => 'files/ruby/regex.rb', - 'old_path' => 'files/ruby/regex.rb', - 'sections' => { - '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_9_9' => 'head', - '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_21_21' => 'origin', - '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_49_49' => 'origin' - } - } - ] - - resolve_conflicts(resolved_files) - end - - it 'returns a 400 error' do - expect(response).to have_http_status(:bad_request) - end - - it 'has a message with the name of the missing file' do - expect(json_response['message']).to include('files/ruby/popen.rb') - end - - it 'does not create a new commit' do - expect(original_head_sha).to eq(merge_request_with_conflicts.source_branch_head.sha) - end - end - - context 'when a file has identical content to the conflict' do - before do - content = MergeRequests::Conflicts::ListService.new(merge_request_with_conflicts) - .file_for_path('files/ruby/popen.rb', 'files/ruby/popen.rb') - .content - - resolved_files = [ - { - 'new_path' => 'files/ruby/popen.rb', - 'old_path' => 'files/ruby/popen.rb', - 'content' => content - }, { - 'new_path' => 'files/ruby/regex.rb', - 'old_path' => 'files/ruby/regex.rb', - 'sections' => { - '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_9_9' => 'head', - '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_21_21' => 'origin', - '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_49_49' => 'origin' - } - } - ] - - resolve_conflicts(resolved_files) - end - - it 'returns a 400 error' do - expect(response).to have_http_status(:bad_request) - end - - it 'has a message with the path of the problem file' do - expect(json_response['message']).to include('files/ruby/popen.rb') - end - - it 'does not create a new commit' do - expect(original_head_sha).to eq(merge_request_with_conflicts.source_branch_head.sha) - end - end - end - describe 'POST assign_related_issues' do let(:issue1) { create(:issue, project: project) } let(:issue2) { create(:issue, project: project) } diff --git a/spec/controllers/projects/milestones_controller_spec.rb b/spec/controllers/projects/milestones_controller_spec.rb index 84a61b2784e..bb5a340cd96 100644 --- a/spec/controllers/projects/milestones_controller_spec.rb +++ b/spec/controllers/projects/milestones_controller_spec.rb @@ -31,6 +31,40 @@ describe Projects::MilestonesController do end end + describe "#index" do + context "as html" do + before do + get :index, namespace_id: project.namespace.id, project_id: project.id + end + + it "queries only projects milestones" do + milestones = assigns(:milestones) + + expect(milestones.count).to eq(1) + expect(milestones.where(project_id: nil)).to be_empty + end + end + + context "as json" do + let!(:group) { create(:group, :public) } + let!(:group_milestone) { create(:milestone, group: group) } + let!(:group_member) { create(:group_member, group: group, user: user) } + + before do + project.update(namespace: group) + get :index, namespace_id: project.namespace.id, project_id: project.id, format: :json + end + + it "queries projects milestones and groups milestones" do + milestones = assigns(:milestones) + + expect(milestones.count).to eq(2) + expect(milestones.where(project_id: nil).first).to eq(group_milestone) + expect(milestones.where(group_id: nil).first).to eq(milestone) + end + end + end + describe "#destroy" do it "removes milestone" do expect(issue.milestone_id).to eq(milestone.id) diff --git a/spec/controllers/projects/notes_controller_spec.rb b/spec/controllers/projects/notes_controller_spec.rb index 45f4cf9180d..3b88d5b0d7d 100644 --- a/spec/controllers/projects/notes_controller_spec.rb +++ b/spec/controllers/projects/notes_controller_spec.rb @@ -131,7 +131,7 @@ describe Projects::NotesController do before do sign_in(user) - project.team << [user, :developer] + project.add_developer(user) end it "returns status 302 for html" do @@ -165,6 +165,66 @@ describe Projects::NotesController do expect(response).to have_http_status(302) end end + + context 'when creating a commit comment from an MR fork' do + let(:project) { create(:project) } + + let(:fork_project) do + create(:project).tap do |fork| + create(:forked_project_link, forked_to_project: fork, forked_from_project: project) + end + end + + let(:merge_request) do + create(:merge_request, source_project: fork_project, target_project: project, source_branch: 'feature', target_branch: 'master') + end + + let(:existing_comment) do + create(:note_on_commit, note: 'a note', project: fork_project, commit_id: merge_request.commit_shas.first) + end + + def post_create(extra_params = {}) + post :create, { + note: { note: 'some other note' }, + namespace_id: project.namespace, + project_id: project, + target_type: 'merge_request', + target_id: merge_request.id, + note_project_id: fork_project.id, + in_reply_to_discussion_id: existing_comment.discussion_id + }.merge(extra_params) + end + + context 'when the note_project_id is not correct' do + it 'returns a 404' do + post_create(note_project_id: Project.maximum(:id).succ) + + expect(response).to have_http_status(404) + end + end + + context 'when the user has no access to the fork' do + it 'returns a 404' do + post_create + + expect(response).to have_http_status(404) + end + end + + context 'when the user has access to the fork' do + let(:discussion) { fork_project.notes.find_discussion(existing_comment.discussion_id) } + + before do + fork_project.add_developer(user) + + existing_comment + end + + it 'creates the note' do + expect { post_create }.to change { fork_project.notes.count }.by(1) + end + end + end end describe 'DELETE destroy' do diff --git a/spec/controllers/projects/pages_domains_controller_spec.rb b/spec/controllers/projects/pages_domains_controller_spec.rb index 33853c4b9d0..920189be381 100644 --- a/spec/controllers/projects/pages_domains_controller_spec.rb +++ b/spec/controllers/projects/pages_domains_controller_spec.rb @@ -46,7 +46,7 @@ describe Projects::PagesDomainsController do post(:create, request_params.merge(pages_domain: pages_domain_params)) end.to change { PagesDomain.count }.by(1) - expect(response).to redirect_to(namespace_project_pages_path(project.namespace, project)) + expect(response).to redirect_to(project_pages_path(project)) end end @@ -56,7 +56,7 @@ describe Projects::PagesDomainsController do delete(:destroy, request_params.merge(id: pages_domain.domain)) end.to change { PagesDomain.count }.by(-1) - expect(response).to redirect_to(namespace_project_pages_path(project.namespace, project)) + expect(response).to redirect_to(project_pages_path(project)) end end diff --git a/spec/controllers/projects/pipeline_schedules_controller_spec.rb b/spec/controllers/projects/pipeline_schedules_controller_spec.rb index f8f95dd9bc8..41bf5580993 100644 --- a/spec/controllers/projects/pipeline_schedules_controller_spec.rb +++ b/spec/controllers/projects/pipeline_schedules_controller_spec.rb @@ -1,6 +1,8 @@ require 'spec_helper' describe Projects::PipelineSchedulesController do + include AccessMatchersForController + set(:project) { create(:empty_project, :public) } let!(:pipeline_schedule) { create(:ci_pipeline_schedule, project: project) } @@ -17,6 +19,14 @@ describe Projects::PipelineSchedulesController do expect(response).to render_template(:index) end + it 'avoids N + 1 queries' do + control_count = ActiveRecord::QueryRecorder.new { visit_pipelines_schedules }.count + + create_list(:ci_pipeline_schedule, 2, project: project) + + expect { visit_pipelines_schedules }.not_to exceed_query_limit(control_count) + end + context 'when the scope is set to active' do let(:scope) { 'active' } @@ -36,20 +46,321 @@ describe Projects::PipelineSchedulesController do end end - describe 'GET edit' do - let(:user) { create(:user) } + describe 'GET #new' do + set(:user) { create(:user) } before do - project.add_master(user) - + project.add_developer(user) sign_in(user) end - it 'loads the pipeline schedule' do - get :edit, namespace_id: project.namespace.to_param, project_id: project, id: pipeline_schedule.id + it 'initializes a pipeline schedule model' do + get :new, namespace_id: project.namespace.to_param, project_id: project expect(response).to have_http_status(:ok) - expect(assigns(:schedule)).to eq(pipeline_schedule) + expect(assigns(:schedule)).to be_a_new(Ci::PipelineSchedule) + end + end + + describe 'POST #create' do + describe 'functionality' do + set(:user) { create(:user) } + + before do + project.add_developer(user) + sign_in(user) + end + + let(:basic_param) do + attributes_for(:ci_pipeline_schedule) + end + + context 'when variables_attributes has one variable' do + let(:schedule) do + basic_param.merge({ + variables_attributes: [{ key: 'AAA', value: 'AAA123' }] + }) + end + + it 'creates a new schedule' do + expect { go } + .to change { Ci::PipelineSchedule.count }.by(1) + .and change { Ci::PipelineScheduleVariable.count }.by(1) + + expect(response).to have_http_status(:found) + + Ci::PipelineScheduleVariable.last.tap do |v| + expect(v.key).to eq("AAA") + expect(v.value).to eq("AAA123") + end + end + end + + context 'when variables_attributes has two variables and duplicted' do + let(:schedule) do + basic_param.merge({ + variables_attributes: [{ key: 'AAA', value: 'AAA123' }, { key: 'AAA', value: 'BBB123' }] + }) + end + + it 'returns an error that the keys of variable are duplicated' do + expect { go } + .to change { Ci::PipelineSchedule.count }.by(0) + .and change { Ci::PipelineScheduleVariable.count }.by(0) + + expect(assigns(:schedule).errors['variables']).not_to be_empty + end + end + end + + describe 'security' do + let(:schedule) { attributes_for(:ci_pipeline_schedule) } + + it { expect { go }.to be_allowed_for(:admin) } + it { expect { go }.to be_allowed_for(:owner).of(project) } + it { expect { go }.to be_allowed_for(:master).of(project) } + it { expect { go }.to be_allowed_for(:developer).of(project) } + it { expect { go }.to be_denied_for(:reporter).of(project) } + it { expect { go }.to be_denied_for(:guest).of(project) } + it { expect { go }.to be_denied_for(:user) } + it { expect { go }.to be_denied_for(:external) } + it { expect { go }.to be_denied_for(:visitor) } + end + + def go + post :create, namespace_id: project.namespace.to_param, project_id: project, schedule: schedule + end + end + + describe 'PUT #update' do + describe 'functionality' do + set(:user) { create(:user) } + let!(:pipeline_schedule) { create(:ci_pipeline_schedule, project: project, owner: user) } + + before do + project.add_developer(user) + sign_in(user) + end + + context 'when a pipeline schedule has no variables' do + let(:basic_param) do + { description: 'updated_desc', cron: '0 1 * * *', cron_timezone: 'UTC', ref: 'patch-x', active: true } + end + + context 'when params include one variable' do + let(:schedule) do + basic_param.merge({ + variables_attributes: [{ key: 'AAA', value: 'AAA123' }] + }) + end + + it 'inserts new variable to the pipeline schedule' do + expect { go }.to change { Ci::PipelineScheduleVariable.count }.by(1) + + pipeline_schedule.reload + expect(response).to have_http_status(:found) + expect(pipeline_schedule.variables.last.key).to eq('AAA') + expect(pipeline_schedule.variables.last.value).to eq('AAA123') + end + end + + context 'when params include two duplicated variables' do + let(:schedule) do + basic_param.merge({ + variables_attributes: [{ key: 'AAA', value: 'AAA123' }, { key: 'AAA', value: 'BBB123' }] + }) + end + + it 'returns an error that variables are duplciated' do + go + + expect(assigns(:schedule).errors['variables']).not_to be_empty + end + end + end + + context 'when a pipeline schedule has one variable' do + let(:basic_param) do + { description: 'updated_desc', cron: '0 1 * * *', cron_timezone: 'UTC', ref: 'patch-x', active: true } + end + + let!(:pipeline_schedule_variable) do + create(:ci_pipeline_schedule_variable, + key: 'CCC', pipeline_schedule: pipeline_schedule) + end + + context 'when adds a new variable' do + let(:schedule) do + basic_param.merge({ + variables_attributes: [{ key: 'AAA', value: 'AAA123' }] + }) + end + + it 'adds the new variable' do + expect { go }.to change { Ci::PipelineScheduleVariable.count }.by(1) + + pipeline_schedule.reload + expect(pipeline_schedule.variables.last.key).to eq('AAA') + end + end + + context 'when adds a new duplicated variable' do + let(:schedule) do + basic_param.merge({ + variables_attributes: [{ key: 'CCC', value: 'AAA123' }] + }) + end + + it 'returns an error' do + expect { go }.not_to change { Ci::PipelineScheduleVariable.count } + + pipeline_schedule.reload + expect(assigns(:schedule).errors['variables']).not_to be_empty + end + end + + context 'when updates a variable' do + let(:schedule) do + basic_param.merge({ + variables_attributes: [{ id: pipeline_schedule_variable.id, value: 'new_value' }] + }) + end + + it 'updates the variable' do + expect { go }.not_to change { Ci::PipelineScheduleVariable.count } + + pipeline_schedule_variable.reload + expect(pipeline_schedule_variable.value).to eq('new_value') + end + end + + context 'when deletes a variable' do + let(:schedule) do + basic_param.merge({ + variables_attributes: [{ id: pipeline_schedule_variable.id, _destroy: true }] + }) + end + + it 'delete the existsed variable' do + expect { go }.to change { Ci::PipelineScheduleVariable.count }.by(-1) + end + end + + context 'when deletes and creates a same key simultaneously' do + let(:schedule) do + basic_param.merge({ + variables_attributes: [{ id: pipeline_schedule_variable.id, _destroy: true }, + { key: 'CCC', value: 'CCC123' }] + }) + end + + it 'updates the variable' do + expect { go }.not_to change { Ci::PipelineScheduleVariable.count } + + pipeline_schedule.reload + expect(pipeline_schedule.variables.last.key).to eq('CCC') + expect(pipeline_schedule.variables.last.value).to eq('CCC123') + end + end + end + end + + describe 'security' do + let(:schedule) { { description: 'updated_desc' } } + + it { expect { go }.to be_allowed_for(:admin) } + it { expect { go }.to be_allowed_for(:owner).of(project) } + it { expect { go }.to be_allowed_for(:master).of(project) } + it { expect { go }.to be_allowed_for(:developer).of(project).own(pipeline_schedule) } + it { expect { go }.to be_denied_for(:reporter).of(project) } + it { expect { go }.to be_denied_for(:guest).of(project) } + it { expect { go }.to be_denied_for(:user) } + it { expect { go }.to be_denied_for(:external) } + it { expect { go }.to be_denied_for(:visitor) } + + context 'when a developer created a pipeline schedule' do + let(:developer_1) { create(:user) } + let!(:pipeline_schedule) { create(:ci_pipeline_schedule, project: project, owner: developer_1) } + + before do + project.add_developer(developer_1) + end + + it { expect { go }.to be_allowed_for(developer_1) } + it { expect { go }.to be_denied_for(:developer).of(project) } + it { expect { go }.to be_allowed_for(:master).of(project) } + end + + context 'when a master created a pipeline schedule' do + let(:master_1) { create(:user) } + let!(:pipeline_schedule) { create(:ci_pipeline_schedule, project: project, owner: master_1) } + + before do + project.add_master(master_1) + end + + it { expect { go }.to be_allowed_for(master_1) } + it { expect { go }.to be_allowed_for(:master).of(project) } + it { expect { go }.to be_denied_for(:developer).of(project) } + end + end + + def go + put :update, namespace_id: project.namespace.to_param, + project_id: project, id: pipeline_schedule, + schedule: schedule + end + end + + describe 'GET #edit' do + describe 'functionality' do + let(:user) { create(:user) } + + before do + project.add_master(user) + sign_in(user) + end + + it 'loads the pipeline schedule' do + get :edit, namespace_id: project.namespace.to_param, project_id: project, id: pipeline_schedule.id + + expect(response).to have_http_status(:ok) + expect(assigns(:schedule)).to eq(pipeline_schedule) + end + end + + describe 'security' do + it { expect { go }.to be_allowed_for(:admin) } + it { expect { go }.to be_allowed_for(:owner).of(project) } + it { expect { go }.to be_allowed_for(:master).of(project) } + it { expect { go }.to be_allowed_for(:developer).of(project).own(pipeline_schedule) } + it { expect { go }.to be_denied_for(:reporter).of(project) } + it { expect { go }.to be_denied_for(:guest).of(project) } + it { expect { go }.to be_denied_for(:user) } + it { expect { go }.to be_denied_for(:external) } + it { expect { go }.to be_denied_for(:visitor) } + end + + def go + get :edit, namespace_id: project.namespace.to_param, project_id: project, id: pipeline_schedule.id + end + end + + describe 'GET #take_ownership' do + describe 'security' do + it { expect { go }.to be_allowed_for(:admin) } + it { expect { go }.to be_allowed_for(:owner).of(project) } + it { expect { go }.to be_allowed_for(:master).of(project) } + it { expect { go }.to be_allowed_for(:developer).of(project).own(pipeline_schedule) } + it { expect { go }.to be_denied_for(:reporter).of(project) } + it { expect { go }.to be_denied_for(:guest).of(project) } + it { expect { go }.to be_denied_for(:user) } + it { expect { go }.to be_denied_for(:external) } + it { expect { go }.to be_denied_for(:visitor) } + end + + def go + post :take_ownership, namespace_id: project.namespace.to_param, project_id: project, id: pipeline_schedule.id end end @@ -65,7 +376,7 @@ describe Projects::PipelineSchedulesController do end it 'does not delete the pipeline schedule' do - expect(response).not_to have_http_status(:ok) + expect(response).to have_http_status(:not_found) end end diff --git a/spec/controllers/projects/pipelines_controller_spec.rb b/spec/controllers/projects/pipelines_controller_spec.rb index 734532668d3..c8de275ca3e 100644 --- a/spec/controllers/projects/pipelines_controller_spec.rb +++ b/spec/controllers/projects/pipelines_controller_spec.rb @@ -8,6 +8,7 @@ describe Projects::PipelinesController do let(:feature) { ProjectFeature::DISABLED } before do + stub_not_protect_default_branch project.add_developer(user) project.project_feature.update( builds_access_level: feature) @@ -158,7 +159,7 @@ describe Projects::PipelinesController do context 'when builds are enabled' do let(:feature) { ProjectFeature::ENABLED } - + it 'retries a pipeline without returning any content' do expect(response).to have_http_status(:no_content) expect(build.reload).to be_retried @@ -175,7 +176,7 @@ describe Projects::PipelinesController do describe 'POST cancel.json' do let!(:pipeline) { create(:ci_pipeline, project: project) } let!(:build) { create(:ci_build, :running, pipeline: pipeline) } - + before do post :cancel, namespace_id: project.namespace, project_id: project, @@ -185,7 +186,7 @@ describe Projects::PipelinesController do context 'when builds are enabled' do let(:feature) { ProjectFeature::ENABLED } - + it 'cancels a pipeline without returning any content' do expect(response).to have_http_status(:no_content) expect(pipeline.reload).to be_canceled diff --git a/spec/controllers/projects/project_members_controller_spec.rb b/spec/controllers/projects/project_members_controller_spec.rb index f2b59ba82ca..8671d7a78dd 100644 --- a/spec/controllers/projects/project_members_controller_spec.rb +++ b/spec/controllers/projects/project_members_controller_spec.rb @@ -5,11 +5,10 @@ describe Projects::ProjectMembersController do let(:project) { create(:empty_project, :public, :access_requestable) } describe 'GET index' do - it 'should have the settings/members address with a 302 status code' do + it 'should have the project_members address with a 200 status code' do get :index, namespace_id: project.namespace, project_id: project - expect(response).to have_http_status(302) - expect(response.location).to include namespace_project_settings_members_path(project.namespace, project) + expect(response).to have_http_status(200) end end @@ -50,7 +49,7 @@ describe Projects::ProjectMembersController do access_level: Gitlab::Access::GUEST expect(response).to set_flash.to 'Users were successfully added.' - expect(response).to redirect_to(namespace_project_settings_members_path(project.namespace, project)) + expect(response).to redirect_to(project_project_members_path(project)) end it 'adds no user to members' do @@ -62,7 +61,7 @@ describe Projects::ProjectMembersController do access_level: Gitlab::Access::GUEST expect(response).to set_flash.to 'Message' - expect(response).to redirect_to(namespace_project_settings_members_path(project.namespace, project)) + expect(response).to redirect_to(project_project_members_path(project)) end end end @@ -111,7 +110,7 @@ describe Projects::ProjectMembersController do id: member expect(response).to redirect_to( - namespace_project_settings_members_path(project.namespace, project) + project_project_members_path(project) ) expect(project.members).not_to include member end @@ -183,7 +182,7 @@ describe Projects::ProjectMembersController do project_id: project expect(response).to set_flash.to 'Your access request to the project has been withdrawn.' - expect(response).to redirect_to(namespace_project_path(project.namespace, project)) + expect(response).to redirect_to(project_path(project)) expect(project.requesters).to be_empty expect(project.users).not_to include user end @@ -202,7 +201,7 @@ describe Projects::ProjectMembersController do expect(response).to set_flash.to 'Your request for access has been queued for review.' expect(response).to redirect_to( - namespace_project_path(project.namespace, project) + project_path(project) ) expect(project.requesters.exists?(user_id: user)).to be_truthy expect(project.users).not_to include user @@ -253,7 +252,7 @@ describe Projects::ProjectMembersController do id: member expect(response).to redirect_to( - namespace_project_settings_members_path(project.namespace, project) + project_project_members_path(project) ) expect(project.members).to include member end @@ -290,7 +289,7 @@ describe Projects::ProjectMembersController do expect(project.team_members).to include member expect(response).to set_flash.to 'Successfully imported' expect(response).to redirect_to( - namespace_project_settings_members_path(project.namespace, project) + project_project_members_path(project) ) end end diff --git a/spec/controllers/projects/prometheus_controller_spec.rb b/spec/controllers/projects/prometheus_controller_spec.rb new file mode 100644 index 00000000000..eddf7275975 --- /dev/null +++ b/spec/controllers/projects/prometheus_controller_spec.rb @@ -0,0 +1,59 @@ +require('spec_helper') + +describe Projects::PrometheusController do + let(:user) { create(:user) } + let!(:project) { create(:empty_project) } + + let(:prometheus_service) { double('prometheus_service') } + + before do + allow(controller).to receive(:project).and_return(project) + allow(project).to receive(:prometheus_service).and_return(prometheus_service) + + project.add_master(user) + sign_in(user) + end + + describe 'GET #active_metrics' do + context 'when prometheus metrics are enabled' do + context 'when data is not present' do + before do + allow(prometheus_service).to receive(:matched_metrics).and_return({}) + end + + it 'returns no content response' do + get :active_metrics, project_params(format: :json) + + expect(response).to have_http_status(204) + end + end + + context 'when data is available' do + let(:sample_response) { { some_data: 1 } } + + before do + allow(prometheus_service).to receive(:matched_metrics).and_return(sample_response) + end + + it 'returns no content response' do + get :active_metrics, project_params(format: :json) + + expect(response).to have_http_status(200) + expect(json_response).to eq(sample_response.deep_stringify_keys) + end + end + + context 'when requesting non json response' do + it 'returns not found response' do + get :active_metrics, project_params + + expect(response).to have_http_status(404) + end + end + end + end + + def project_params(opts = {}) + opts.reverse_merge(namespace_id: project.namespace, project_id: project) + end +end diff --git a/spec/controllers/projects/registry/tags_controller_spec.rb b/spec/controllers/projects/registry/tags_controller_spec.rb new file mode 100644 index 00000000000..a823516830e --- /dev/null +++ b/spec/controllers/projects/registry/tags_controller_spec.rb @@ -0,0 +1,48 @@ +require 'spec_helper' + +describe Projects::Registry::TagsController do + let(:user) { create(:user) } + let(:project) { create(:empty_project, :private) } + + before do + sign_in(user) + stub_container_registry_config(enabled: true) + end + + context 'when user has access to registry' do + before do + project.add_developer(user) + end + + describe 'POST destroy' do + context 'when there is matching tag present' do + before do + stub_container_registry_tags(repository: /image/, tags: %w[rc1 test.]) + end + + let(:repository) do + create(:container_repository, name: 'image', project: project) + end + + it 'makes it possible to delete regular tag' do + expect_any_instance_of(ContainerRegistry::Tag).to receive(:delete) + + destroy_tag('rc1') + end + + it 'makes it possible to delete a tag that ends with a dot' do + expect_any_instance_of(ContainerRegistry::Tag).to receive(:delete) + + destroy_tag('test.') + end + end + end + end + + def destroy_tag(name) + post :destroy, namespace_id: project.namespace, + project_id: project, + repository_id: repository, + id: name + end +end diff --git a/spec/controllers/projects/services_controller_spec.rb b/spec/controllers/projects/services_controller_spec.rb index 4dc227a36d4..5a9d8a75f3e 100644 --- a/spec/controllers/projects/services_controller_spec.rb +++ b/spec/controllers/projects/services_controller_spec.rb @@ -79,7 +79,7 @@ describe Projects::ServicesController do put :update, namespace_id: project.namespace.id, project_id: project.id, id: service.id, service: { active: true } - expect(response).to redirect_to(namespace_project_settings_integrations_path(project.namespace, project)) + expect(response).to redirect_to(project_settings_integrations_path(project)) expect(flash[:notice]).to eq 'HipChat activated.' end end diff --git a/spec/controllers/projects/settings/members_controller_spec.rb b/spec/controllers/projects/settings/members_controller_spec.rb deleted file mode 100644 index 076d6cd9c6e..00000000000 --- a/spec/controllers/projects/settings/members_controller_spec.rb +++ /dev/null @@ -1,14 +0,0 @@ -require('spec_helper') - -describe Projects::Settings::MembersController do - let(:project) { create(:empty_project, :public, :access_requestable) } - - describe 'GET show' do - it 'renders show with 200 status code' do - get :show, namespace_id: project.namespace, project_id: project - - expect(response).to have_http_status(200) - expect(response).to render_template(:show) - end - end -end diff --git a/spec/controllers/projects/snippets_controller_spec.rb b/spec/controllers/projects/snippets_controller_spec.rb index ec0b7f8c967..cc444f31797 100644 --- a/spec/controllers/projects/snippets_controller_spec.rb +++ b/spec/controllers/projects/snippets_controller_spec.rb @@ -148,7 +148,7 @@ describe Projects::SnippetsController do { spam_log_id: spam_logs.last.id, recaptcha_verification: true }) - expect(response).to redirect_to(Snippet.last) + expect(response).to redirect_to(project_snippet_path(project, Snippet.last)) end end end @@ -228,7 +228,7 @@ describe Projects::SnippetsController do { spam_log_id: spam_logs.last.id, recaptcha_verification: true }) - expect(response).to redirect_to(snippet) + expect(response).to redirect_to(project_snippet_path(project, snippet)) end end end @@ -273,7 +273,7 @@ describe Projects::SnippetsController do { spam_log_id: spam_logs.last.id, recaptcha_verification: true }) - expect(response).to redirect_to(snippet) + expect(response).to redirect_to(project_snippet_path(project, snippet)) end end end diff --git a/spec/controllers/projects/variables_controller_spec.rb b/spec/controllers/projects/variables_controller_spec.rb index 1ecfe48475c..da06fcb7cfb 100644 --- a/spec/controllers/projects/variables_controller_spec.rb +++ b/spec/controllers/projects/variables_controller_spec.rb @@ -15,13 +15,13 @@ describe Projects::VariablesController do post :create, namespace_id: project.namespace.to_param, project_id: project, variable: { key: "one", value: "two" } - expect(flash[:notice]).to include 'Variables were successfully updated.' - expect(response).to redirect_to(namespace_project_settings_ci_cd_path(project.namespace, project)) + expect(flash[:notice]).to include 'Variable was successfully created.' + expect(response).to redirect_to(project_settings_ci_cd_path(project)) end end context 'variable is invalid' do - it 'shows an alert flash message' do + it 'renders show' do post :create, namespace_id: project.namespace.to_param, project_id: project, variable: { key: "..one", value: "two" } @@ -35,7 +35,6 @@ describe Projects::VariablesController do context 'updating a variable with valid characters' do before do - variable.project_id = project.id project.variables << variable end @@ -44,7 +43,7 @@ describe Projects::VariablesController do id: variable.id, variable: { key: variable.key, value: 'two' } expect(flash[:notice]).to include 'Variable was successfully updated.' - expect(response).to redirect_to(namespace_project_variables_path(project.namespace, project)) + expect(response).to redirect_to(project_variables_path(project)) end it 'renders the action #show if the variable key is invalid' do diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb index 240a81367d0..192cca45d99 100644 --- a/spec/controllers/projects_controller_spec.rb +++ b/spec/controllers/projects_controller_spec.rb @@ -211,24 +211,43 @@ describe ProjectsController do let(:admin) { create(:admin) } let(:project) { create(:project, :repository) } - let(:new_path) { 'renamed_path' } - let(:project_params) { { path: new_path } } before do sign_in(admin) end - it "sets the repository to the right path after a rename" do - controller.instance_variable_set(:@project, project) + context 'when only renaming a project path' do + it "sets the repository to the right path after a rename" do + expect { update_project path: 'renamed_path' } + .to change { project.reload.path } - put :update, - namespace_id: project.namespace, - id: project.id, - project: project_params + expect(project.path).to include 'renamed_path' + expect(assigns(:repository).path).to include project.path + expect(response).to have_http_status(302) + end + end - expect(project.repository.path).to include(new_path) - expect(assigns(:repository).path).to eq(project.repository.path) - expect(response).to have_http_status(302) + context 'when project has container repositories with tags' do + before do + stub_container_registry_config(enabled: true) + stub_container_registry_tags(repository: /image/, tags: %w[rc1]) + create(:container_repository, project: project, name: :image) + end + + it 'does not allow to rename the project' do + expect { update_project path: 'renamed_path' } + .not_to change { project.reload.path } + + expect(controller).to set_flash[:alert].to(/container registry tags/) + expect(response).to have_http_status(200) + end + end + + def update_project(**parameters) + put :update, + namespace_id: project.namespace.path, + id: project.path, + project: parameters end end @@ -482,7 +501,7 @@ describe ProjectsController do it 'redirects to the canonical path (testing non-show action)' do get :refs, namespace_id: 'foo', id: 'bar' - expect(response).to redirect_to(refs_namespace_project_path(namespace_id: public_project.namespace, id: public_project)) + expect(response).to redirect_to(refs_project_path(public_project)) expect(controller).to set_flash[:notice].to(project_moved_message(redirect_route, public_project)) end end diff --git a/spec/controllers/sent_notifications_controller_spec.rb b/spec/controllers/sent_notifications_controller_spec.rb index 917bd44c91b..5d2734b8827 100644 --- a/spec/controllers/sent_notifications_controller_spec.rb +++ b/spec/controllers/sent_notifications_controller_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -describe SentNotificationsController, type: :controller do +describe SentNotificationsController do let(:user) { create(:user) } let(:project) { create(:empty_project) } let(:sent_notification) { create(:sent_notification, project: project, noteable: issue, recipient: user) } @@ -23,7 +23,7 @@ describe SentNotificationsController, type: :controller do end it 'sets the flash message' do - expect(controller).to set_flash[:notice].to(/unsubscribed/).now + expect(controller).to set_flash[:notice].to(/unsubscribed/) end it 'redirects to the login page' do @@ -83,12 +83,12 @@ describe SentNotificationsController, type: :controller do end it 'sets the flash message' do - expect(controller).to set_flash[:notice].to(/unsubscribed/).now + expect(controller).to set_flash[:notice].to(/unsubscribed/) end it 'redirects to the issue page' do expect(response) - .to redirect_to(namespace_project_issue_path(project.namespace, project, issue)) + .to redirect_to(project_issue_path(project, issue)) end end @@ -109,12 +109,12 @@ describe SentNotificationsController, type: :controller do end it 'sets the flash message' do - expect(controller).to set_flash[:notice].to(/unsubscribed/).now + expect(controller).to set_flash[:notice].to(/unsubscribed/) end it 'redirects to the merge request page' do expect(response) - .to redirect_to(namespace_project_merge_request_path(project.namespace, project, merge_request)) + .to redirect_to(project_merge_request_path(project, merge_request)) end end end diff --git a/spec/controllers/sessions_controller_spec.rb b/spec/controllers/sessions_controller_spec.rb index bf922260b2f..a22fd8eaf9b 100644 --- a/spec/controllers/sessions_controller_spec.rb +++ b/spec/controllers/sessions_controller_spec.rb @@ -1,9 +1,11 @@ require 'spec_helper' describe SessionsController do + include DeviseHelpers + describe '#new' do before do - @request.env['devise.mapping'] = Devise.mappings[:user] + set_devise_mapping(context: @request) end context 'when auto sign-in is enabled' do @@ -34,7 +36,7 @@ describe SessionsController do describe '#create' do before do - @request.env['devise.mapping'] = Devise.mappings[:user] + set_devise_mapping(context: @request) end context 'when using standard authentications' do @@ -47,7 +49,7 @@ describe SessionsController do end end - context 'when using valid password', :redis do + context 'when using valid password', :clean_gitlab_redis_shared_state do include UserActivitiesHelpers let(:user) { create(:user) } @@ -257,7 +259,7 @@ describe SessionsController do describe '#new' do before do - @request.env['devise.mapping'] = Devise.mappings[:user] + set_devise_mapping(context: @request) end it 'redirects correctly for referer on same host with params' do diff --git a/spec/controllers/snippets_controller_spec.rb b/spec/controllers/snippets_controller_spec.rb index 430d1208cd1..475ceda11fe 100644 --- a/spec/controllers/snippets_controller_spec.rb +++ b/spec/controllers/snippets_controller_spec.rb @@ -186,8 +186,8 @@ describe SnippetsController do end context 'when the snippet description contains a file' do - let(:picture_file) { '/temp/secret56/picture.jpg' } - let(:text_file) { '/temp/secret78/text.txt' } + let(:picture_file) { '/system/temp/secret56/picture.jpg' } + let(:text_file) { '/system/temp/secret78/text.txt' } let(:description) do "Description with picture: ![picture](/uploads#{picture_file}) and "\ "text: [text.txt](/uploads#{text_file})" @@ -208,8 +208,8 @@ describe SnippetsController do snippet = subject expected_description = "Description with picture: "\ - "![picture](/uploads/personal_snippet/#{snippet.id}/secret56/picture.jpg) and "\ - "text: [text.txt](/uploads/personal_snippet/#{snippet.id}/secret78/text.txt)" + "![picture](/uploads/system/personal_snippet/#{snippet.id}/secret56/picture.jpg) and "\ + "text: [text.txt](/uploads/system/personal_snippet/#{snippet.id}/secret78/text.txt)" expect(snippet.description).to eq(expected_description) end @@ -341,7 +341,7 @@ describe SnippetsController do { spam_log_id: spam_logs.last.id, recaptcha_verification: true }) - expect(response).to redirect_to(snippet) + expect(response).to redirect_to(snippet_path(snippet)) end end end diff --git a/spec/controllers/uploads_controller_spec.rb b/spec/controllers/uploads_controller_spec.rb index 01a0659479b..96f719e2b82 100644 --- a/spec/controllers/uploads_controller_spec.rb +++ b/spec/controllers/uploads_controller_spec.rb @@ -102,7 +102,7 @@ describe UploadsController do subject expect(response.body).to match '\"alt\":\"rails_sample\"' - expect(response.body).to match "\"url\":\"/uploads/temp" + expect(response.body).to match "\"url\":\"/uploads/system/temp" end it 'does not create an Upload record' do @@ -119,7 +119,7 @@ describe UploadsController do subject expect(response.body).to match '\"alt\":\"doc_sample.txt\"' - expect(response.body).to match "\"url\":\"/uploads/temp" + expect(response.body).to match "\"url\":\"/uploads/system/temp" end it 'does not create an Upload record' do diff --git a/spec/controllers/users_controller_spec.rb b/spec/controllers/users_controller_spec.rb index 842d82cdbe9..7aeb6efd86d 100644 --- a/spec/controllers/users_controller_spec.rb +++ b/spec/controllers/users_controller_spec.rb @@ -80,9 +80,9 @@ describe UsersController do it 'renders calendar' do sign_in(user) - get :calendar, username: user.username + get :calendar, username: user.username, format: :json - expect(response).to render_template('calendar') + expect(response).to have_http_status(200) end context 'forked project' do |